反射及其原理

目录

image.png

例子

  • 定义一个User类
public class User {
    private String name;
    private Integer age;
    // 省略get set
}
反射设置属性
public class Test {


    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("User");
        testReflection(clazz);
    }

    /**
     * 如果遇到多个类都有name属性并需要提供统一处理这时候就很好用
     * @param clazz
     */
    public static void testReflection(Class clazz) {
        try {
            if (clazz == null) {
                System.out.println("空 clazz");
                return;
            }
            //获得类的实例
            Object obj = clazz.newInstance();
            //获得 User 类中的指定属性对应的Field对象(每个属性对应一个Field对象)
            Field field = clazz.getDeclaredField("name");

            //取消属性的访问权限控制,即使private 属性也可以进行访问
            field.setAccessible(true);
            //调用 getter 方法获取属性值
            System.out.println(field.get(obj));
            //调用setter 方法给属性赋值
            field.set(obj, "scott");
            //调用 getter 方法获取对应属性修改后的值
            System.out.println(field.get(obj));
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("异常");
        }

    }


}
反射执行方法
public static void main(String[] args) throws Exception {
    Class clazz = User.class;
    Object obj = clazz.newInstance();


    //调用该对象的 setName方法
    Method method = clazz.getMethod("setName", new Class[]{String.class});
    Object result = method.invoke(obj, new Object[]{"scott"});       //  obj.setName("scott");
    System.out.println("返回值为:" + result);

    //调用对象的getName()方法
    Method method1 = clazz.getMethod("getName", new Class[]{});
    Object obj1 = method1.invoke(obj, new Object[]{});
    System.out.println("返回值为:" + obj1);

}

原理

  • 代码示例
Class<?> clazz = Class.forName("TestOOM");
TestOOM testOOM = (TestOOM) clazz1.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("test");
method.invoke(testOOM1);
  • 先看下method的invoke方法,Method实例对象维护了一个root引用。当调用Method.copy()进行方法拷贝时,root指向了被拷贝的对象,通过维护root引用意图使相同的方法始终保持只有一个methodAccessor实例。ma.invoke会先通过DelegatingMethodAccessorImpl.invoke,再到NativeMethodAccessorImpl.invoke,NativeMethodAccessorImpl最终调用native方法完成invoke()任务。
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{
    // 检查override,如果override为true,跳过检查
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    // private volatile MethodAccessor methodAccessor;
    MethodAccessor ma = methodAccessor;          
    if (ma == null) {
        // ma不存在则去获取
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }

    return tmp;
}

public MethodAccessor newMethodAccessor(Method method) {
    checkInitted();
    // 启动参数-Dsun.reflect.noInflation
    if (noInflation) {
        //省略部分部分
    } else {
        NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);
        // 委托模式
        DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);
        acc.setParent(res);
        return res;
    }
}

void setMethodAccessor(MethodAccessor accessor) {
    methodAccessor = accessor;
    // Propagate up
    if (root != null) {
        root.setMethodAccessor(accessor);
    }
}

// 每次获取Method都是new的,类似cow思想
Method copy() {
    Method res = new Method(clazz, name, parameterTypes, returnType,
                            exceptionTypes, modifiers, slot, signature,
                            annotations, parameterAnnotations, annotationDefault);
    res.root = this;
    res.methodAccessor = methodAccessor;
    return res;
}
  • NativeMethodAccessorImpl最终调用native方法完成invoke()任务。当numInvocations数量大于配置项sun.reflect.inflationThreshold(m默认15)即类膨胀阈值时, 使用MethodAccessorGenerator创建一个代理类对象,并且将被委托的NativeMethodAccessorImpl的parent。总体来说,当调用invoke()时,按照默认配置,Method首先创建一个DelegatingMethodAccessorImpl对象,并设置一个被委托的NativeMethodAccessorImpl对象,那么method.invoke()就被转换成DelegatingMethodAccessorImpl.invoke(),然后又被委托给NativeMethodAccessorImp.invoke()实现。当NativeMethodAccessorImp.invoke()调用次数超过一定热度时(默认15次),被委托方又被转换成代理类来实现。图中DelegatingMethodAccessorImpl用到代码模式,可能代理1,也可能代理2,代理1到一定条件时,代理1通过this.parent.setDelegate更换DelegatingMethodAccessorImpl代理的类。


    image.png
public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException {
    if (++numInvocations > ReflectionFactory.inflationThreshold()) {
        MethodAccessorImpl acc = (MethodAccessorImpl)
            new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        parent.setDelegate(acc);
    }

    return invoke0(method, obj, args);
}

// JNI调用 
private static native Object invoke0(Method m, Object obj, Object[] args);
  • 接下来看看getDeclaredMethod, 这里特别注意privateGetDeclaredMethods,其中涉及软引用,如果getDeclaredMethod用到软引用缓存的,上面的MethodAccessor就是同一个,即使并发导致生成多个GeneratedMethodAccessorXXX(GeneratedMethodAccessorXXX的类加载器其实是一个DelegatingClassLoader类加载器, 这样更方便卸载)也生成的不大;但是如果非软引用而是重新生成的,那就好大量有DelegatingClassLoader加载,GeneratedMethodAccessorXXX大量加载卸载的问题。
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    checkInitted();
    Method[] res;
    // 软引用
    ReflectionData<T> rd = reflectionData();
    if (rd != null) {
        res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
        if (res != null) return res;
    }
    // No cached value available; request value from VM
    res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
    if (rd != null) {
        if (publicOnly) {
            rd.declaredPublicMethods = res;
        } else {
            rd.declaredMethods = res;
        }
    }
    return res;
}
  • 总结: Method.invoke的逻辑,相关的method因此被SoftReference引用,因此很容易被回收,一旦被回收,那就创建一个新的Method对象,再调用其invoke方法,在调用到一定次数(15次)之后,就构建一个新的字节码类,伴随着GC的进行,同一个方法的字节码类不断构建,直到将Perm充满触发一次Full GC才得以释放

Class.forName 和 ClassLoader区别

  • 在java中Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。
  • Spring框架中的IOC的实现就是使用的ClassLoader,JDBC时通常是使用Class.forName()方法来加载数据库连接驱动。这是因为在JDBC规范中明确要求Driver(数据库驱动)类必须向DriverManager注册自己。

设计模式学习

  • DelegatingMethodAccessorImpl就是用到委托模式,setDelegate, parent等关键字还可以改变委托的对象。委托模式是一种更高级的代理模式,委托模式可以解决一种方案的多种实现之间自由切换,而代理模式只能根据传入的被代理对象来实现功能。
  • 结构型之代理模式
  • 装饰器模式

反射和序列化破坏单例解决方案

优化

  • 可以把查找到的缓存起来。反射调用时本身就有用弱引用进行缓存。

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354