反射及其原理

250 阅读5分钟

反射的概念

反射是指在java运行状态中,动态获取类的内容、创建对象、动态调用对象方法及操作属性的一种机制。

  • 优点
    增加代码灵活性,避免固有逻辑代码不够灵活
    增加代码简洁性和易读性,提高代码可复用率
  • 缺点
    调用反射创建对象性能消耗较大
    内部暴露和安全隐患问题,破坏单例模式

反射的性能消耗

public class Reflect {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int i = 0;
        while (i++<100000){
            getInstanceByRef("com.marx.test.Generics");
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+ (end - start));
    }

    private static void getInstanceByRef(String s) {
        try{
            Class<?> clazz = Class.forName(s);
            clazz.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • Class.forName 源码
@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass(); //调用native方法得到caller对象
    //通过caller对象和类加载器调用forName0
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

这里调用了一个native修饰的本地方法,通过jni接口调用

/** 
 * 官方有说明,jvm的开发者认为这些方法危险,不希望开发者调用,就把这种危险的方法用 
 * @CallerSensitive修饰,并在“jvm”级别检查。这个注解限制只有通过启动类加载器
 *  BootstrapClassLoader或者 扩展类加载器 ExtClassLoader 这两种类加载器加载的
 * 类可以调用。 见 https://openjdk.org/jeps/176
 */
@CallerSensitive
public static native Class<?> getCallerClass();
private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller)
    throws ClassNotFoundException;
//navtive方法调用JNI,参数传入一个className
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
                              jboolean initialize, jobject loader, jclass caller)
{
    char *clname;
    jclass cls = 0;
    char buf[128];
    jsize len;
    jsize unicode_len;
    
    //检查className不能为null
    if (classname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return 0;
    }

    len = (*env)->GetStringUTFLength(env, classname);
    unicode_len = (*env)->GetStringLength(env, classname);
    if (len >= (jsize)sizeof(buf)) {
        clname = malloc(len + 1);
        if (clname == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return NULL;
        }
    } else {
        clname = buf;
    }
    (*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);

    //检查权限类名是否允许
    if (VerifyFixClassname(clname) == JNI_TRUE) {
        /* 将类名.替换成 / 进行加载 */
        (*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }
    //检查类名是否是期待值
    if (!VerifyClassname(clname, JNI_TRUE)) {  
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }
    //使用caller创建class 这里调用的JVM方法 需要去jvm源码中查看
    cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);

 done:
    if (clname != buf) {
        free(clname);
    }
    return cls;
}
// Not used; JVM_FindClassFromCaller replaces this.
JVM_ENTRY(jclass, JVM_FindClassFromClassLoader(JNIEnv* env, const char* name,
                                               jboolean init, jobject loader,
                                               jboolean throwError))
  JVMWrapper3("JVM_FindClassFromClassLoader %s throw %s", name,
               throwError ? "error" : "exception");
  // Java libraries should ensure that name is never null...
  if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    if (throwError) {
      THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
    } else {
      THROW_MSG_0(vmSymbols::java_lang_ClassNotFoundException(), name);
    }
  }
  
  
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
  
  Handle h_loader(THREAD, JNIHandles::resolve(loader));
 
  jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
                                               Handle(), throwError, THREAD);

  if (TraceClassResolution && result != NULL) {
    trace_class_resolution(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(result)));
  }
  return result;
JVM_END

实际上调用的JVM_FindClassFromCaller 是这个方法

// 通过特定的类加载器在caller 保护域中查找名字相同的类
JVM_ENTRY(jclass, JVM_FindClassFromCaller (JNIEnv* env, const char* name,
                                          jboolean init, jobject loader,
                                          jclass caller))
  JVMWrapper2("JVM_FindClassFromCaller %s throws ClassNotFoundException", name);
  // 为了确保这个name不能为空
  if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    THROW_MSG_0(vmSymbols::java_lang_ClassNotFoundException(), name);
  }
  
  //TempNewSymbol C语言中类似  HashTable的结构 将找到名称相同的类放入结果集中
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
   
  oop loader_oop = JNIHandles::resolve(loader);//获取到加载器
  oop from_class = JNIHandles::resolve(caller);//获取caller
  oop protection_domain = NULL; //保护域
  /** 
   * 这里是因为类加载器拥有所有的权限,但是VM中没有对应的权限校验方法,所以如果加载器为null 就不做包的    * 权限校验,否则会发生空指针异常。 即使java代码中传递了null,由于没有做安全管理caller也一样会通过,为    * 了性能做了这个判断
   */
  if (from_class != NULL && loader_oop != NULL) {//如果加载器为null 并且caller传过来的参数不为null
      // 解释下: 保护域是我自己取得,实际上这个东西是为了保护源码的一组权限集合
    protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();//获取保护域
  }

  Handle h_loader(THREAD, loader_oop);//处理加载器
  Handle h_prot(THREAD, protection_domain);//处理保护域
  //根据运行环境、类路径、初始化信息、加载器、端口加载到指定的类文件
  jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
                                               h_prot, false, THREAD);
  //没找到
  if (TraceClassResolution && result != NULL) {
    trace_class_resolution(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(result)));
  }
  //找到了返回
  return result;
JVM_END

返回结果后调用java的 newInstance方法 创建实例对象又做了一堆校验;大体的就是安全管理器、构造方法、和 caller&权限之类的校验这里就不展开说了

@CallerSensitive
public T newInstance()
    throws InstantiationException, IllegalAccessException
{
    if (System.getSecurityManager() != null) {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
    }

    // NOTE: the following code may not be strictly correct under
    // the current Java memory model.

    // Constructor lookup
    if (cachedConstructor == null) {
        if (this == Class.class) {
            throw new IllegalAccessException(
                "Can not call newInstance() on the Class for java.lang.Class"
            );
        }
        try {
            Class<?>[] empty = {};
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
            // Disable accessibility checks on the constructor
            // since we have to do the security check here anyway
            // (the stack depth is wrong for the Constructor's
            // security check to work)
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                            c.setAccessible(true);
                            return null;
                        }
                    });
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw (InstantiationException)
                new InstantiationException(getName()).initCause(e);
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    // Security check (same as in java.lang.reflect.Constructor)
    int modifiers = tmpConstructor.getModifiers();
    if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
        Class<?> caller = Reflection.getCallerClass();
        if (newInstanceCallerCache != caller) {
            Reflection.ensureMemberAccess(caller, this, null, modifiers);
            newInstanceCallerCache = caller;
        }
    }
    // Run constructor
    try {
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        // Not reached
        return null;
    }
}

总结: 因此反射的性能不高,创建的过程是很慢的,需要一大堆的安全校验和JNI调用

反射的使用

  • Class对象的组成

image.png

//类名创建类对象不会对类进行初始化,这里也可以使用已经创建过的虚拟机中的对象 new Generics().getClass()
Class<Generics> clazz_1 = Generics.class;
//会对类进行初始化  MySQL使用的就是这种方式加载数据库驱动
Class<?> clazz_2 = Class.forName("xxxx.xxxx.xxx");
//获取类的加载器加载类
//public Class<?> loadClass(String name) throws ClassNotFoundException {
//        return loadClass(name, false);//loadClass内部调用的方法resolve传入false 也不会对类初始化
//    }
Class<?> clazz_3 = Generics.class.getClassLoader().loadClass("xxx.xxx.xxx");

clazz_1.getModifiers();//修饰符
clazz_1.getPackage();//包名
clazz_1.getName();//类全名
clazz_1.getSimpleName();//类简单名
clazz_1.getClassLoader();//类加载器
clazz_1.getInterfaces();//接口
clazz_1.getSuperclass();//父类
clazz_1.getAnnotations();//注解
clazz_1.getFields();//类的public属性 包括父类的
clazz_1.getDeclaredFields();//当前类定义的属性

Field field = clazz_1.getField("aaa");
field.setAccessible(true);//可以设置私有属性强制访问