你确定不了解下 Java 中反射黑魔法吗?

2023-05-16

前言

反射在Java 中算是黑魔法的存在了。
用一句话来形容「反其道而行之」

很多限制在反射面前,就是形同虚设。
例如我们设置了一个类的成员变量是 private, 目的就是为了不让外部可以随意修改访问。但是呢,使用反射就可以,你说牛不牛。

正因为反射技术的灵活性,所以在各大框架中被频繁的使用,所以在学习的过程中,了解反射的意义对后续框架的学习有很大的帮助。

具体是这么做到的?还是其他更巧妙的用法?想知道的话,就接着看下去吧~

What:反射是什么?

在java中,运行时,只要给定类的名字,能够知道这个类的所有信息,可以构造出指定对象,可以调用它的任意一个属性和方法。

这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

基于这个机制,反射的作用是

  • 动态的加载类,动态的获取类的信息(属性,方法,构造器)
  • 动态的构造对象
  • 动态调用类和对象的任意方法,构造器
  • 获取泛型信息
  • 处理注解
  • 动态代理

How:如何使用反射?

Java 反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。

咱们下面,使用反射获取Person 这个类,来为大家一一演示下。

public class Person {

    public String name;
    private int age;
    private List<String> favorities;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void addFavorite(String favorite) {
        if (favorities == null) {
            favorities = new ArrayList<>();
        }
        favorities.add(favorite);
    }

    private void changeAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", favorities=" + favorities +
                '}';
    }

}

获取反射中的Class对象

获取 Class 类对象有三种方法

  • 使用 Class.forName 静态方法。(需要给出该类的全路径名:包名+类名)
  • 使用 .class 方法。
  • 使用类对象的 getClass() 方法。
            //第一种,使用 Class.forName 静态方法。
            Class  clazz1 = Class.forName("reflect.Person");
            //第二种,使用 .class 方法。
            Class  clazz2 = Person.class;
            //第三种,使用类对象的 getClass() 方法。
            Person person =  new Person("浅浅",18);
            Class  clazz3 = person.getClass();

其中

  • 基本类型只可以通过 .class 方法获得Class对象。

通过反射创建类对象

很多文章说主要有两种方式:

  • 通过 Class 对象的 newInstance() 方法、
  • 通过 Constructor 对象的 newInstance() 方法。

两者的区别是通过 Class 对象则只能使用默认的无参数构造方法,通过 Constructor 对象创建类对象可以选择特定构造方法。

其实, class.newInstance() 内部实现也是通过 Constructor。而且现在已经标记为 @Deprecated(since="9"),建议使用 Constructor 。

            //获取class对象
            Class clazz = Class.forName("reflect.Person");

            //如果有默认构造方法,就使用这个
            Person p1 = (Person) clazz.getConstructor().newInstance();

            //指定构造方法
            Person p2 = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 1);

通过反射获取类的成员变量

  • 成员变量
            //获取class对象
            Class clazz = Class.forName("reflect.Person");
            //构造对象
            Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
            
            //获取指定变量 name
            Field nameField = clazz.getField("name");
            //修改变量的值
            nameField.set(p,"张三");
            //获取指定变量 age 
            Field ageField = clazz.getDeclaredField("age");
            //age 是private变量,需要先设置可用
            ageField.setAccessible(true);
            //修改变量的值
            ageField.setInt(p,17);

需要注意的是:

  • getField() : 只能获取 public 修饰的变量,如果是私有变量,会报错NoSuchField
  • getDeclaredField() : 可用于获取全部变量,在不知道变量的权限修饰符的时候,建议使用 getDeclaredField(), 比较保险。

修改变量的值的方法分为两类

  • 引用类型
    只有一个方法 set(Object obj, Object value)
  • 基本类型
    为每个基本类型,都提供了一个方法,例如 setInt(Object obj, int i)setLong(Object obj, long l)
    大家使用时,根据不同的类型,选择不同的方法。

通过反射获取类的方法

            //获取class对象
            Class clazz = Class.forName("reflect.Person");
            //构造对象
            Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
            //获取指定方法 "addFavorite"
            Method method = clazz.getMethod("addFavorite", String.class);
            //调用对象方法
            method.invoke(p, "篮球");

            //获取指定方法 "changeAge"
            Method privateMethod = clazz.getDeclaredMethod("changeAge", int.class);
            //changeAge 是private方法,需要先设置可用
            privateMethod.setAccessible(true);
            privateMethod.invoke(p, 17);

同样提供了 getMethod()getDeclaredMethod(),和 Field 作用一样,不赘述了,

调用方法的方式只有一种 invoke(Object obj, Object... args)

Why:反射为什么可以拿到所有的类信息

要想知道这个问题,需要从类的加载机制说起。

当我们写完一个类,它首先是一个 java 文件,首先会被编译成.class 文件,然后在运行时,要使用到这个类的时候,jvm 会开始加载这个 .class 文件到内存中。
**.class **是个二进制文件,里面包含了类的全部信息。当加载到内存的时候,分为两个部分:

  • 方法区:存储类运行时数据结构。
  • 堆:创建相应的 Class 对象

所谓类运行时数据结构,其实就是Java类在 JVM 内存中的一个快照,包括常量池、类字段、类方法等。

Class 对象就是相当于是一个入口,放在堆中,方便去程序员访问方法区的类运行时结构信息。
在这里插入图片描述

也就是说,只要类被加载到 JVM 内存,就可以获取它全部的信息了。

那类加载的时机是什么呢?

其实,虚拟机规范中并没有强制约束何时进行加载,但是规定了5种情况必须对类进行「初始化」,其中就有一条关于反射的:

使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。

那类初始化一定会先执行加载吗

类的生命周期如下:
在这里插入图片描述

其中还规定了 加载、验证、准备、初始化,卸载这5个阶段的先后顺序是确定的。

所以初始化一定会触发加载的。

反射的机制就是基于以上基础。

小问题:同一个类的 Class 对象有几个呢?

还记得上文「获取反射中的Class对象」创建的 clazz1,clazz2,clazz3 吗?你觉得这是3个不同的对象吗?

System.out.println(clazz1 == clazz2); 会输出什么?

System.out.println(clazz2 == clazz3); 会输出什么?

答案是都是同个对象,所以输出的都是 true。
原因是什么呢?我们马上来揭晓~

Java 中类的加载都是由类加载器实现的。在我们开发人员眼中,类加载器可以分为以下几种:

  • 启动类加载器:Bootstrap ClassLoader
    它用来加载Java核心类库。使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。
  • 扩展类加载器:Extension ClassLoader
    java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库
  • 应用程序类加载器:Application Classloader
    程序中默认的类加载器,咱们程序写的类,默认都是由它加载完成的。
  • 自定义加载器:User ClassLoader
    咱们自己如果有特别需求,实现的加载器

他们的之间的关系是:

在这里插入图片描述

jvm 对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。

将这个过程详细说说的话,就是:

  • 第一步:如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。

  • 第二步:如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader

  • 第三步:如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常

这就是我们常说的「双亲委派模型」类加载机制。这样做的好处是,加载器之间具备带有优先级的层次关系,每个加载器负责的 classpath 是不同的,而且每加载一个类,全部的加载器范围判定都是从上到下的,所以可以保证一个Class文件只能被同一类加载器加载一次,所以在堆中的Class都是同个对象。

Class 是如何存储反射数据的?

在Class里有个关键的属性叫做reflectionData,这里主要存的是每次从jvm里获取到的一些类属性,比如方法,字段等。避免每次都要从 JVM 里去获取数据。

这个属性主要是SoftReference的,内存不足的的情况下有可能会被回收。

ReflectionData 中的属性是按需懒加载的。当调用了某个反射方法获取属性时,才会将当前属性数据填充到ReflectionData 的成员变量中。

public final class Class<T>{
   //反射数据数据结构
    private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Cached names
        String simpleName;
        String canonicalName;
        static final String NULL_SENTINEL = new String();

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

   //反射数据软应用对象
    private transient volatile SoftReference<ReflectionData<T>> reflectionData;
    
    
   
}

而且需要说明的是,我们每次获取反射的Constructor/Method/Field时,都是重新生成的对象。无论是获取全部,还是某个指定的对象,都会执行下 copy() 动作。

以Method为例:

    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
        }
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        //执行Copy动作
        return getReflectionFactory().copyMethod(method);
    }

copyMethod() 最终会调用 Method 类的 copy() 方法

    Method copy() {
        if (this.root != null)
            throw new IllegalArgumentException("Can not copy a non-root Method");

        Method res = new Method(clazz, name, parameterTypes, returnType,
                                exceptionTypes, modifiers, slot, signature,
                                annotations, parameterAnnotations, annotationDefault);
        res.root = this;
        res.methodAccessor = methodAccessor;
        return res;
    }

由此可见,我们每次通过调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,所以不宜多调哦,如果调用频繁最好缓存起来。
同样的道理,也适用于 Constructor和 Method.

Why: setAccessible() 是如何修改访问权限的?

实际上并没有真正修改Method/Filed/Constructor的访问权限,而是通过一个 boolean 值数据 override 跳过权限检查的。

权限的检查,都封装在一个 AccessibleObject的类中,Method/Filed/Constructor 都继承了它。其中提供了 setAccessible()方法来进行权限检查开关设置。

public class AccessibleObject implements AnnotatedElement {

    //默认为false  
    boolean override;

    /**
     * setAccessible()最终会跳到这个方法
     * 修改override的值
     */
    boolean setAccessible0(boolean flag) {
        this.override = flag;
        return flag;
    }
    
}

以Method.invoke 为例,其中就判断了 override属性。

 public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        //override == true,就会不执行权限检查
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz,
                        Modifier.isStatic(modifiers) ? null : obj.getClass(),
                        modifiers);
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

Why: 为什么说反射的性能很差?

我们平常很多框架都使用了反射,而反射中最多使用的就是 Method ,所以我们就从这里分析。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理:

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        //检查权限
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz,
                        Modifier.isStatic(modifiers) ? null : obj.getClass(),
                        modifiers);
        }
        MethodAccessor ma = methodAccessor;  
        if (ma == null) {
            //获取MethodAccessor
            ma = acquireMethodAccessor();
        }
        //实际上调用了 MethodAccessor
        return ma.invoke(obj, args);
    }

MethodAccessor 实际上是一个接口,的到底是哪个类对象,所以我们需要进入 acquireMethodAccessor() 方法中看看。

    private MethodAccessor acquireMethodAccessor() {
        //是否存在对应的 MethodAccessor 对象,
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
             //存在就复用之前的对象
            methodAccessor = tmp;
        } else {
            // 否则通过ReflectionFactory 创建一个新的。
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }

关键的实现是 reflectionFactory.newMethodAccessor()方法。

 public MethodAccessor newMethodAccessor(Method method) {
        //检查是否初始化
        checkInitted();

        if (Reflection.isCallerSensitive(method)) {
            Method altMethod = findMethodForReflection(method);
            if (altMethod != null) {
                method = altMethod;
            }
        }

        // use the root Method that will not cache caller class
        Method root = langReflectAccess().getRoot(method);
        if (root != null) {
            method = root;
        }

        //如果是不使用Inflation机制
        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            //生成新的 MethodAccessor
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
        //否则使用NativeMethodAccessorImpl
            NativeMethodAccessorImpl acc =
                new NativeMethodAccessorImpl(method);
            DelegatingMethodAccessorImpl res =
                new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }

条件语句上出现了 noInflation,这是个啥呢,从初始化方法 checkInitted() 中可以看到都是从JVM的参数设置中读取的,默认 noInflation = false,也就是说 Inflation 机制开关设置,默认是开启的。

    private static void checkInitted() {
        //省略......

        Properties props = GetPropertyAction.privilegedGetProperties();
        // Inflation 机制开关设置,默认开启,noInflation 默认是false
        String val = props.getProperty("sun.reflect.noInflation");
        if (val != null && val.equals("true")) {
            noInflation = true;
        }
        // Inflation 机制的阈值参数设置,默认是15
        val = props.getProperty("sun.reflect.inflationThreshold");
        if (val != null) {
            try {
                inflationThreshold = Integer.parseInt(val);
            } catch (NumberFormatException e) {
                throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
            }
        }
         //省略......
    }

还多了一个inflationThreshold 参数,这是一个Int 类型的数据,默认是15,具体使用在 NativeMethodAccessorImpl 的 invoke方法中。

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
  // 如果native版本执行超过 -Dsun.reflect.inflationThreshold的的值,默认值是15,则设置DelegatingMethodAccessorImpl的delegate属性为java版本的MethodAccessor实现,即切换为java版本
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
          // 切换java版本
            this.parent.setDelegate(var3);
        }
        // native方法
        return invoke0(this.method, var1, var2);
    }

注意了,这个地方就是 Inflation 机制的精髓所在。

反射方法调用的版本实现有两个版本:Java 和 Native 。

  • Native:
    具体实现是NativeMethodAccessorImpl,实现最终调用native方法 invoke0() 。启动时相对较快,但是运行效率较慢。
  • Java
    具体实现是 MethodAccessorGeneratorX,通过ASM技术动态生成一个MethodAccessorImpl 的子类并实现 invoke方法。初始化慢,但是运行效率较快

所以综合两个实现,基本性能考虑,引申出 inflation 机制,即:初次加载字节码实现反射,使用花费时间更短的 native 实现。如果频繁使用,再动态生成一个类作为 java 实现,后续都切换为 java 实现。

另外这其中,还是使用了设计模式之代理模式:

  • MethodAccessor的实现都委托给 DelegatingMethodAccessorImpl
  • DelegatingMethodAccessorImpl 内部通过delegate属性来包装真正实现 invoke 方法的 MethodAccessorImpl
  • Inflation 机制切换实现方法,就是通过修改delegate属性来实现的。

有没有感受到它的妙处~

Java 版本的实现详解

未开启 Inflation 机制和超过native 方法调用次数阈值都是通过MethodAccessorGenerator.generateMethod() 来生成 Java 版的 MethodAccessor 的实现类。

方法很长,简单看一下:

    private MagicAccessorImpl generate(final Class<?> declaringClass,
                                       String name,
                                       Class<?>[] parameterTypes,
                                       Class<?>   returnType,
                                       Class<?>[] checkedExceptions,
                                       int modifiers,
                                       boolean isConstructor,
                                       boolean forSerialization,
                                       Class<?> serializationTargetClass)
    {
        ByteVector vec = ByteVectorFactory.create();
        //字节码工具实现
        asm = new ClassFileAssembler(vec);
        this.declaringClass = declaringClass;
        this.parameterTypes = parameterTypes;
        this.returnType = returnType;
        this.modifiers = modifiers;
        this.isConstructor = isConstructor;
        this.forSerialization = forSerialization;

        asm.emitMagicAndVersion();
        asm.emitShort(add(numCPEntries, S1));

        final String generatedName = generateName(isConstructor, forSerialization);
        asm.emitConstantPoolUTF8(generatedName);
        asm.emitConstantPoolClass(asm.cpi());
        thisClass = asm.cpi();
        if (isConstructor) {
            if (forSerialization) {
                asm.emitConstantPoolUTF8
                    ("jdk/internal/reflect/SerializationConstructorAccessorImpl");
            } else {
                asm.emitConstantPoolUTF8("jdk/internal/reflect/ConstructorAccessorImpl");
            }
        } else {
            asm.emitConstantPoolUTF8("jdk/internal/reflect/MethodAccessorImpl");
        }
        asm.emitConstantPoolClass(asm.cpi());
        superClass = asm.cpi();
        //省略......
        

核心逻辑主要是通过 ClassFileAssembler 类来生成字节码,会生成一个 class 文件,名称是 GeneratedMethodAccessorX,这个 X 是调用次数,会递增的。初始化慢的原因就在这里。

生成的 class 文件大概是这个样子的,想当于直接调用。

例如是实现 Person#addFavorite() 方法反射的时候,就会生成如下的 GeneratedMethodAccessorX

package sun.reflect;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
    public GeneratedMethodAccessor1() {
        super();
    }

   //invoke方法实现
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException {
        // prepare the target and parameters
        if (obj == null) throw new NullPointerException();
        try {
            //对象实例类型强转成 Person
            Person target = (Person) obj;
            if (args.length != 1) throw new IllegalArgumentException();
            String arg0 = (String) args[0];
        } catch (ClassCastException e) {
            throw new IllegalArgumentException(e.toString());
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e.toString());
        }
        // make the invocation
        try {
            //直接调用对象的方法 "addFavorite"
            target.addFavorite(arg0);
        } catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
    }
}

总结起来,Java版的实现就是用空间换时间。

需要特别说明的是,加载Java版本 GeneratedMethodAccessorX的类加载器是专门提供的自定义加载器 DelegatingClassLoader,而且每个方法都会生成一个类加载器。

  static Class<?> defineClass(String name, byte[] bytes, int off, int len,
                                final ClassLoader parentClassLoader)
    {
        ClassLoader newLoader = AccessController.doPrivileged(
            new PrivilegedAction<ClassLoader>() {
                public ClassLoader run() {
                        return new DelegatingClassLoader(parentClassLoader);
                    }
                });
        return JLA.defineClass(newLoader, name, bytes, null, "__ClassDefiner__");
    }
    
//自定义加载器,内部没有额外实现,只是名字的区别
class DelegatingClassLoader extends ClassLoader {
    DelegatingClassLoader(ClassLoader parent) {
        super(parent);
    }
}

方法注释中,解释的也很清楚:

  • 安全风险:避免了在同一个加载器下,加载不同的字节码带来的未知风险。
  • 性能考虑:在某些情况下可以提前卸载这些生成的类,从而减少运行时间。(因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的)

最后

呕心沥血万字长文,让我对反射认识的更清晰了,同时也希望能够带给大家帮助~


最简单的易懂的技术干货,好玩的事情,都会在「浅浅同学的开发笔记」分享,快来呀~期待与你共同成长!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

你确定不了解下 Java 中反射黑魔法吗? 的相关文章

  • Spring应用中Eureka健康检查的问题

    我正在开发一个基于 Spring 的应用程序 其中包含多个微服务 我的一个微服务充当尤里卡服务器 到目前为止一切正常 在我所有其他微服务中 用 EnableEurekaClient 我想启用这样的健康检查 应用程序 yml eureka c
  • Mockito:如何通过模拟测试我的服务?

    我是模拟测试新手 我想测试我的服务方法CorrectionService correctPerson Long personId 实现尚未编写 但这就是它将执行的操作 CorrectionService将调用一个方法AddressDAO这将
  • 如何通过 javaconfig 使用 SchedulerFactoryBean.schedulerContextAsMap

    我使用 Spring 4 0 并将项目从 xml 移至 java config 除了访问 Service scheduleService 带注释的类来自QuartzJobBean executeInternal 我必须让它工作的 xml 位
  • 在内存中使用 byte[] 创建 zip 文件。 Zip 文件总是损坏

    我创建的 zip 文件有问题 我正在使用 Java 7 我尝试从字节数组创建一个 zip 文件 其中包含两个或多个 Excel 文件 应用程序始终完成 没有任何异常 所以 我以为一切都好 当我尝试打开 zip 文件后 Windows 7 出
  • 使用 LinkedList 实现下一个和上一个按钮

    这可能是一个愚蠢的问题 但我很难思考清楚 我编写了一个使用 LinkedList 来移动加载的 MIDI 乐器的方法 我想制作一个下一个和一个上一个按钮 以便每次单击该按钮时都会遍历 LinkedList 如果我硬编码itr next or
  • .properties 中的通配符

    是否存在任何方法 我可以将通配符添加到属性文件中 并且具有所有含义 例如a b c d lalalala 或为所有以结尾的内容设置一个正则表达式a b c anything 普通的 Java 属性文件无法处理这个问题 不 请记住 它实际上是
  • 动态选择端口号?

    在 Java 中 我需要获取端口号以在同一程序的多个实例之间进行通信 现在 我可以简单地选择一些固定的数字并使用它 但我想知道是否有一种方法可以动态选择端口号 这样我就不必打扰我的用户设置端口号 这是我的一个想法 其工作原理如下 有一个固定
  • 如何使用assertEquals 和 Epsilon 在 JUnit 中断言两个双精度数?

    不推荐使用双打的assertEquals 我发现应该使用带有Epsilon的形式 这是因为双打不可能100 严格 但无论如何我需要比较两个双打 预期结果和实际结果 但我不知道该怎么做 目前我的测试如下 Test public void te
  • org.apache.hadoop.security.AccessControlException:客户端无法通过以下方式进行身份验证:[TOKEN,KERBEROS] 问题

    我正在使用 java 客户端通过 Kerberos 身份验证安全访问 HDFS 我尝试打字klist在服务器上 它显示已经存在的有效票证 我收到的异常是客户端无法通过以下方式进行身份验证 TOKEN KERBEROS 帮助将不胜感激 这是一
  • 如何获取之前的URL?

    我需要调用我的网络应用程序的 URL 例如 如果有一个从 stackoverflow com 到我的网站 foo com 的链接 我需要 Web 应用程序 托管 bean 中的 stackoverflow 链接 感谢所有帮助 谢谢 并不总是
  • 将流转换为 IntStream

    我有一种感觉 我在这里错过了一些东西 我发现自己做了以下事情 private static int getHighestValue Map
  • 如何在用户输入数据后重新运行java代码

    嘿 我有一个基本的java 应用程序 显示人们是成年人还是青少年等 我从java开始 在用户输入年龄和字符串后我找不到如何制作它它们被归类为 我希望它重新运行整个过程 以便其他人可以尝试 的节目 我一直在考虑做一个循环 但这对我来说没有用
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • 为什么 Java 8 不允许非公共默认方法?

    让我们举个例子 public interface Testerface default public String example return Hello public class Tester implements Testerface
  • Cucumber 0.4.3 (cuke4duke) 与 java + maven gem 问题

    我最近开始为 Cucumber 安装一个示例项目 并尝试使用 maven java 运行它 我遵循了这个指南 http www goodercode com wp using cucumber tests with maven and ja
  • Eclipse 启动时崩溃;退出代码=13

    I am trying to work with Eclipse Helios on my x64 machine Im pretty sure now that this problem could occur with any ecli
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 包 javax.el 不存在

    我正在使用 jre6 eclipse 并导入 javax el 错误 包 javax el 不存在 javac 导入 javax el 过来 这不应该是java的一部分吗 谁能告诉我为什么会这样 谢谢 米 EL 统一表达语言 是 Java
  • 使用 svn 1.8.x、subclise 1.10 的 m2e-subclipse 连接器在哪里?

    我读到 m2e 的生产商已经停止生产 svn 1 7 以外的任何版本的 m2e 连接器 Tigris 显然已经填补了维护 m2e subclipse 连接器的空缺 Q1 我的问题是 使用 svn 1 8 x 的 eclipse 更新 url
  • Spring Boot 无法更新 azure cosmos db(MongoDb) 上的分片集合

    我的数据库中存在一个集合 documentDev 其分片键为 dNumber 样本文件 id 12831221wadaee23 dNumber 115 processed false 如果我尝试使用以下命令通过任何查询工具更新此文档 db

随机推荐

  • 【spring】spring有哪几种配置方式

    目录 一 说明二 配置方式三 配置示例3 1 基于xml配置文件3 2 基于注解的配置3 3 基于java的配置 一 说明 1 spring有三种重要的方法提供配置元数据 二 配置方式 1 基于xml配置文件 xff0c spring诞生的
  • 远程登录Windows的WSL子系统

    很多文章要么只有安装 wsl 和 ssh xff0c 要么只有设置端口转发 xff0c 完全不能一篇文章解决标题中的问题 由于最近不在办公室 xff0c 需要远程登录办公室的电脑 xff0c 这样可以节省很多时间去配置各种命令 回归正题 x
  • curl: (1) Protocol "'http" not supported or disabled in libcurl异常

    笔者在window中安装了curl 但是在使用过程中出现了错误 xff01 curl 1 Protocol 34 39 http 34 not supported or disabled in libcurl 最后经过排查 xff0c 发现
  • 获奖公布 | 征文——从高考到程序员

    每年的这几天 xff0c 空气中总会弥漫着紧张的味道 xff0c 2017 全国统一高考如期而至 朋友圈里的各种高考热文如流水般 xff0c 不停歇地出现在眼前 xff0c 难免会勾起自己曾经的青涩时光 还记得 xff0c 考试前 xff0
  • 消息队列

    一 什么是消息队列 我们可以把消息队列比作是一个存放消息的容器 xff0c 当我们需要使用消息的时候可以取出消息供自己使用 消息队列是分布式系统中重要的组件 xff0c 使用消息队列主要是为了通过异步处理提高系统性能和削峰 降低系统耦合性
  • ubuntu开机自启动(绝对好用)

    linux服务管理有两种方式service和systemctl lib systemd system 和 etc systemd system 存放所有可用的单元文件 systemctl test service start 比如需要开机启
  • networkx教程

    创建一个图 创建一个没有节点和边的空图 import networkx as nx G 61 nx Graph 根据定义 xff0c a span class pre Graph span 是节点 xff08 顶点 xff09 的集合以及确
  • java.lang.IllegalStateException异常产生的原因及解决办法

    错误类型大致为以下几种 xff1a java lang IllegalStateException xff1a Cannot forward a response that is already committed IllegalState
  • android测试:unresolved reference AndroidJUnit4

    如上 xff0c 如果你的测试文件在上面这个目录下 将依赖改为 androidTestImplementatio开始即可
  • hexo 绑定自己的域名

    前提 xff0c 你得有一个域名 xff0c 有些域名需要备案后才能用 在域名解析添加记录 如果你用你顶点域名 xff08 如 xff1a lookk cn xff0c 就添加一条主机记录为 64 的 xff0c 如果你用www子域名 xf
  • java 把List集合转换为json

    1 servlet List转json 需要jar包 xff0c 可以到我github下载 commons beanutils 1 7 0 jar commons collections 3 2 jar commons httpclient
  • Manjaro Gnome版设置默认文件管理器(inode/directory)为Nautilus

    自从安装Visual Studio Code以后不知道为什么默认的文件管理器会被改为这个玩意 xff0c 每次像打开应用程序目录这样的操作弹出的都是VS Code xff0c 而且仅仅显示上次使用VS Code时的会话 xff0c 并不会显
  • 在linux下安装redis集群的踩坑记录

    这里是引用 这里写自定义目录标题 环境软件及说明安装集群心得最新安装的redisredis conf 中bind 属性需要重新配置的情况Can I set the above configuration还是历史数据问题redis 密码相关
  • java ExecutorService的invokeAll方法有两种用法 +价格超时计算

    exec invokeAll tasks exec invokeAll tasks timeout unit 其中tasks是任务集合 xff0c timeout是超时时间 xff0c unit是时间单位 两者都会堵塞 xff0c 必须等待
  • CSDN日报20170616 ——《从裁缝到码农》

    程序人生 从裁缝到码农 作者 xff1a 修电脑的裁缝酱 我伸出颤抖的手去抓 xff0c 发现曾经遥不可及的梦想 xff0c 经过坚持和努力之后 xff0c 真的可以抓住 我把它抓在手心 xff0c 紧紧地 点击阅读全文 机器学习 一文了解
  • Android 之调节系统的亮度和音量

    主界面的布局非常简单 xff0c 就是三个按钮 xff0c home按钮 xff0c 返回主界面 xff0c volume按钮 xff0c 弹出音量进度条 xff0c brightness按钮 xff0c 弹出亮度进度条调节 主界面 xff
  • 用R语言对网络数据进行统计分析(五)

    R语言社交网络快速入门 一 7天入门社交网络 R语言社交网络快速入门 二 7天入门社交网络 R语言社交网络快速入门 三 7天入门社交网络 R语言社交网络快速入门 四 7天入门社交网络 R语言社交网络快速入门 五 7天入门社交网络 网络图特征
  • SSH远程登录并执行命令测试

    SSH 是 Linux 下进行远程连接的基本工具 xff0c 但是如果仅仅用它来登录那可是太浪费啦 xff01 SSH 命令可是完成远程操作的神器啊 xff0c 借助它我们可以把很多的远程操作自动化掉 xff01 下面就对 SSH 的远程操
  • CentOs7.5yum安装JDK1.8详细过程

    先查看有哪些可安装的 yum list java root 64 VM 16 35 centos yum list java Loaded plugins fastestmirror langpacks Loading mirror spe
  • 你确定不了解下 Java 中反射黑魔法吗?

    前言 反射在Java 中算是黑魔法的存在了 用一句话来形容 反其道而行之 很多限制在反射面前 xff0c 就是形同虚设 例如我们设置了一个类的成员变量是 private 目的就是为了不让外部可以随意修改访问 但是呢 xff0c 使用反射就可