前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从Class源码看反射

从Class源码看反射

作者头像
Liusy
发布2020-09-01 16:17:04
7810
发布2020-09-01 16:17:04
举报
文章被收录于专栏:Liusy01Liusy01

日常敲码中,如果想要在程序运行阶段访问某个类的所有信息,并支持修改类的状态或者行为的话,肯定会用到反射,而反射靠的就是Class类。Java的动态代理也用到了这个东西,所以了解其基本操作在苦逼的CRUD中会添加一丝丝乐趣(有点意思)。

首先来看看Class的操作有哪些?

public final class Class<T> {}

上述代码可知,Class是一个由final修饰的泛型类,所以并不能直接通过new Class()获取其实例。那么应该如何获取呢?

//直接通过类的静态变量来获取
Class<Integer> intClass = Integer.class;

//通过实例变量的getClass方法
Integer integer = new Integer(0);
Class<? extends Integer> aClass = integer.getClass();

//通过Class.forName("类的全限定名")
Class<?> aClass1 = Class.forName("java.lang.Integer");

上述三种就是获取某个Class的class实例的方式,需要注意的是,JVM只会加载一个Class实例,也就是说上述三种方式获取到的class实例都是一样的。

而在运用反射的时候,Class.forName是最常用的一种方式。而Class.forName底层会指向forName0这个本地方法

(1)name:类的全限定名

(2)initialize:是否初始化这个类

(3)loader:类加载器

(4)caller:调用Class.forName所在类的Class,比如A类代码块里有Class.forName,那么caller就是A的class实例。

通过Class类可以获取类的实例,构造方法,字段,成员方法,接口等信息。获取之后可以通过API进行相应的操作。

接下来看一下获取到class实例之后怎么获取当前类的实例以及构造方法。

上述两种方式都是调用默认的无参构造进行实例化对象,那么怎么通过公共或私有的有参构造获取实例呢?

//属性
int a;
String b ;
//公共有参构造
public ClassSource(int a) {
    this.a = a;
}
//私有有参构造
private ClassSource(String b) {
    this.b = b;
}
public static void main(String[] args){
    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //获取public有参构造
    Constructor<?> constructor = aClass.getConstructor(int.class);
    ClassSource o1 = (ClassSource) constructor.newInstance(2);
    System.out.println("属性【a】的值为:"+ o1.a);
    //获取private有参构造
    Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class);
    //这个有猫腻
    constructor1.setAccessible(true);
    ClassSource o2 = (ClassSource) constructor1.newInstance("abc");
    System.out.println("属性【b】的值为:"+ o2.b);
}

上述代码运行的结果如下:

而获取私有构造函数最重要的是要setAccessible(true)这个方法,点进去看一下,这个方法最后是给override字段赋值的,可用于类的字段、方法以及构造函数。

public void setAccessible(boolean flag) throws SecurityException {
      //这个“安全管理器”,允许应用程序在进行某些不安全或敏感操作
      //之前执行安全策略,如果不允许执行,则通过抛异常阻止操作。 
      SecurityManager sm = System.getSecurityManager();
      if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
      setAccessible0(this, flag);
}
  
private static void setAccessible0(AccessibleObject obj,
                                   boolean flag){
      if (obj instanceof Constructor && flag == true) {
          Constructor<?> c = (Constructor<?>)obj;
          if (c.getDeclaringClass() == Class.class) {
              //如果是Class.class,则抛异常
          }
      }
      //最终是设置此属性
      obj.override = flag;
}    

//访问级别是否可以被覆盖,可用于字段,方法,构造函数 
boolean override;

需要注意的是,在代码中用反射操作当前类私有字段、私有方法或其他私有属性时,并不需要setAccessible(true),例如在A类中用反射操作A类的私有属性。只有在A类中操作其他类的私有属性才需要设置setAccessible(true)。

不管是Class.newInstance还是Constructor.newInstance,底层调用的都是ConstructorAccessor(构造函数访问器)接口的newInstance方法。

(1)Class.newInstance

//标识所有公共属性
public static final int PUBLIC = 0;
//标识所有私有属性
public static final int DECLARED = 1;
public T newInstance(){
    //省略部分代码 
    // 检查有没有存在已加载过的构造器
    if (cachedConstructor == null) {
        //省略部分代码 
        try {
            Class<?>[] empty = {};
                //获取默认私有无参构造
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                //省略部分代码 
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw (InstantiationException)
                new InstantiationException(getName()).initCause(e);
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    //省略部分代码 
    try {
            //调用无参构造的newInstance方法
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        //省略部分代码 
        return null;
    }
}
 
 private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) 
{
    //获取构造函数,重点看这个方法,参数是false
    Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
    //省略匹配构造函数参数的代码    
}
 private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
    //检查是否已被初始化,如果否,则进行安全检查
    checkInitted();
    Constructor<T>[] res;
    //反射的数据
    ReflectionData<T> rd = reflectionData();
    if (rd != null) {
        res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
        if (res != null) return res;
    }
    // 检查是否是接口 
    if (isInterface()) {
        //代码省略
    } else {
       //底层调用的是本地方法 
        res = getDeclaredConstructors0(publicOnly);
    }
    if (rd != null) {
        if (publicOnly) {
            rd.publicConstructors = res;
        } else {
            rd.declaredConstructors = res;
        }
    }
    return res;
}

如上,可以看到Class.newInstance底层是调用无参构造的newInstance方法。

(2)Constructor.newInstance

public T newInstance(Object ... initargs){
        //override就是是否可覆盖级别访问
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

public interface ConstructorAccessor {
    Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
}

可以看到,Class.newInstance调用的是无参构造的newInstance,而构造函数调用的是ConstructorAccessor接口类的newInstance,所以最终调用的都是ConstructorAccessor接口类的newInstance。

最终调用的是NativeConstructorAccessorImpl的newInstance方法。

【注】以下仅演示操作方法,源码不贴,防止篇幅太长。

获取实例以及构造方法之后,来看一下如何访问,修改类字段信息。

 //public属性
public int a;
//private属性
private String b;
public static void main(String[] args) throws Exception {

    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //获取实例
    Object instance = aClass.newInstance();
    //根据字段名获取公共属性并进行赋值
    Field a = aClass.getField("a");
    a.set(instance,2);
    System.out.println("a的值为:"+a.get(instance));
    //根据字段名获取私有属性并进行赋值
    Field b = aClass.getDeclaredField("b");
    b.setAccessible(true);
    b.set(instance,"abc");
    System.out.println("b的值为"+b.get(instance));
}

上述代码运行结果为:

接下来瞧一下如何利用class访问类的成员方法:

public static void main(String[] args) throws Exception {

    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
    //获取实例
    Object instance = aClass.newInstance();

    //获取公共方法并调用
    Method sayPublicHello = aClass.getMethod("sayPublicHello");
    sayPublicHello.invoke(instance);

    //获取私有方法并调用
    Method sayPrivateHello = aClass.getDeclaredMethod("sayPrivateHello", int.class);
    sayPrivateHello.setAccessible(true);
    sayPrivateHello.invoke(instance,1);
}

public void sayPublicHello(){
    System.out.println("sayPublicHello");
}

private void sayPrivateHello(int param){
    System.out.println("sayPrivateHello,参数:"+param);
}

上述代码运行结果为:

还可以使用“==”判断数据类型

Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
System.out.println(aClass == ClassSource.class);

之前遇到过用了很多反射的代码,看起来很恶心。但其实了解过后反而觉得反射是个很强大的东西,包括JDK动态代理,spring AOP,事务底层都是用的反射,研究之后有利于了解更多底层的知识。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Liusy01 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档