JDK动态代理的实现原理及应用

源码解析

源码

重要的前导知识:

  • JDK动态代理类都继承于Proxy这个类 (可以调用代理类的getSuperClass()验证)
  • 这也就解释了为什么在反射获取属性时获取不到InvocationHandler属性,因为它定义在Proxy这个类里,且是protected修饰的,子类可以使用,但是通过反射无法获取,因为getDeclaredFields()只能获取本类中的所有属性,getFields()只能获取本类和父类中public修饰的属性 获取InvocationHandler属性必须调用Proxy内部定义的getInvocationHandler()方法才能获取

我们创建代理类

Object obj = Proxy.newProxyInstance(ClassLoader,Interfaces,InvocationHandler);

调用代理类的方法时默认就调用了我们原始类的方法并且加入了我们新增的功能,那时如何调用的我们传入的InvocationHandler类的方法呢?

1.根据我们传入的类加载器和接口帮我们创建一个代理类

 Class<?> cl = getProxyClass0(loader, intfs);

2.得到了代理类后,先去获取代理类的构造器,传入了一个参数constructorParams,点开之后发现是一个InvocationHandler.classs,也就是说代理类一定有一个构造器参数是InvocationHandler,也就是说在创建代理类对象的时候会传入我们自定义的InvocationHandler,也就说明在这个代理类中一定有一个属性是InvocationHandler,大致的流程我们也就掌握了,也就是说我们创建的代理类肯定有跟目标类同名的方法,因为我们实现的是同样的接口,然后你写的InvocationHandler类传给我,我在我的同名方法里调你的方法就可以实现动态代理

final Constructor<?> cons = cl.getConstructor(constructorParams);

private static final Class<?>[] constructorParams ={InvocationHandler.class};

3.使用构造器就为我们创建了一个代理类的对象

 return cons.newInstance(new Object[]{h});

我们的疑问,当我们调用代理类的方法

InvocationHandler接口解析

  • 先看源码是如何定义的 这就是一个为原始类增加额外功能的一个接口
public Object invoke(Object proxy, Method method, Object[] args)
  • 我们是如何使用的
public class AddFeatures implements InvocationHandler {

    private Object obj;

    public bind(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		
       ...增加的功能
   		Object result = method.invoke(obj,args);
       ...增加的功能
      return result;
}

我们是写一个实现类实现这个接口,然后重写接口中的方法,然后传入这个类,然后为我们创建代理对象时会传入这个类,在代理类的同名方法中调用这个类的这个方法,就完成了类似的代理功能。那么问题来了,

1我们在代理类每个同名方法中都调用这个方法,不会导致调用的时候都是同一个方法吗?

class A {
    B b = new B();
    
    test(){
        b.invoke(xx,xx,xx);
    }
    
    test1(){
        b.invoke(xx,xx,xx);
    }
}
class B implents InvocationHandle{
    invoke();
}

我们先看这个invoke方法具体是如何定义的?

  • 参数:Object proxy,Method method,Object args[]

​ proxy 这个表示代理类的对象

​ method 表示一个方法,也就是时候我们在调用时要传入一个方法

​ args[] 表示是方法的参数 即反射时调用方法要传入参数

​ 返回值:Object 这个表示原始方法的返回值

我们是如何实现的?


....额外功能
Object result = method.invoke(obj,args); //通过反射执行我们原始类的一个方法 
....额外功能
return result; //返回结果

内部的核心就是调用原始类对象的方法,原始类对象我们可以得到,参数和method是不确定的,也就是说我们执行原始类的哪个方法是不确定的,也就解决了第一个问题,虽然我们的代理类调用的都是invoke方法,但是在内部执行原始类哪个的方法是不确定的,如果我们可以在代理类同名的方法中调用invoke时,内部也执行目标类的同名方法,也就解决了这个问题,现在关键问题找到了,就是在代理类不同的方法内部如何才能调用不同的方法,即传入的method这个参数不同?

我们先来猜测是不是我们在创建代理类的时候内部就有Method类型的属性,刚好就是接口中的方法? 事实上,就是这样

接下里我们就需要探究代理列的内部结构到底是这样的

代理类的内部结构及原理

Proxy类中也为我们提供了这样的一个方法,就是只传入类加载器和接口为我们创建一个代理类,事实上动态代理的源码的第一步就是这个

  Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
		// 通过反射获取内部的结构
    ...
}

​ 打印的信息:

方法
    //1实现接口中的方法
public final boolean com.sun.proxy.$Proxy4.isInsert(java.lang.String)
    //2 实现接口中的方法
public final java.lang.String com.sun.proxy.$Proxy4.isStr()
    
public final boolean com.sun.proxy.$Proxy4.equals(java.lang.Object)
public final java.lang.String com.sun.proxy.$Proxy4.toString()
public final int com.sun.proxy.$Proxy4.hashCode()

实现的接口
interface 反射.动态代理.UserService

属性
    //  将接口中方法作为属性值直接赋给了m4这个属性 值是接口中的一个方法,
private static java.lang.reflect.Method com.sun.proxy.$Proxy4.m4
public abstract java.lang.String 反射.动态代理.UserService.isStr()
    
       //  将接口中方法作为属性值直接赋给了m3这个属性
private static java.lang.reflect.Method com.sun.proxy.$Proxy4.m3
public abstract boolean 反射.动态代理.UserService.isInsert(java.lang.String)
    
    
private static java.lang.reflect.Method com.sun.proxy.$Proxy4.m1
public boolean java.lang.Object.equals(java.lang.Object)
    
private static java.lang.reflect.Method com.sun.proxy.$Proxy4.m2
public java.lang.String java.lang.Object.toString()
    
    
private static java.lang.reflect.Method com.sun.proxy.$Proxy4.m0
public native int java.lang.Object.hashCode()

构造器
public com.sun.proxy.$Proxy4(java.lang.reflect.InvocationHandler)

看到属性这,我们基本也就知道了,就是当我们在代理类的方法内部调用传入的InvocationHandler对象的invoke方法时,我们传入的第二个参数method就是代理类内部的一个属性,代理类将接口中的方法传入,让我们的原始类去执行对应的方法

也就是说,我们的代理类就是一个工具,真正实现代理功能的是InvocationHandler的实现类,我们的代理类就是帮我么执行了里面的方法而已,代理类只是帮我们保存了接口中的所有方法,在我们执行invoke时调用不同的方法而已

本质就是:

代理类中有接口中的各个方法(通过反射得到),通过传入的InvocationHandler中的invoke方法实现原始功能+额外功能,额外功能好说,那原始功能如何调用?

正常情况下 我们有了原始类的对象,直接可以通过对象调方法,但是我们要写很多方法才能实现,但是这里只有一个invoke方法,那么我们就可以使用反射,得到方法对象,通过方法的invoke执行原始方法

应用:通过反射动态修改注解

链接:

通过反射修改注解

注解的本质就是一个接口,

public class Test {

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

        Class<Person> clazz = Person.class;
        //获取Perons的name属性
        Field name = clazz.getDeclaredField("name");
        //获取属性上注解 注意 注解本质是接口,这里是JDK动态代理生成的代理对象
        Index annotation = name.getDeclaredAnnotation(Index.class);
        System.out.println(annotation.getClass());// 输出一下这个对象是哪个类创建的 --->class com.sun.proxy.$Proxy1 代理类

        //获取这个注解的属性值  调属性对象的方法 
        System.out.println(annotation.value());  //-- 1

/*
        如何修改注解的某个属性值呢? 我们发现注解并没有提供set方法 本身也就不可能在这个代理类中 根据动态代理的原理
        这个代理类的主要作用就是反射得到接口中的方法作为属性传递到 InvocationHandler对象的invoke方法中 去调用目标对象的方法的
        我们先debug看一下 一下这个代理类的内部结构有哪些 发现在代理类内部有一个属性是 AnnotationInvocationHandler是个类 实现了InvocationHandler接口
        这就应该就是动态代理传入的 InvocationHandler对象  我们点开看 发现了内部有一个memberValues属性 本质是一个Map 里面key是我们的注解属性名 value是注解的属性值
        致此 我们就找到了注解中的属性存储的位置
         所以说 我们修改注解的属性值就有了思路,先拿到注解代理类的  AnnotationInvocationHandler对象,然后获取它的Class对象,然后通过反射拿到它的属性,进而修改
*/

        //通过这个方法,可以获取某个动态代理对象的 InvocationHandler属性
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        //反射得到memberValues属性
        Field h1 = handler.getClass().getDeclaredField("memberValues");
        //访问设置为 true
        h1.setAccessible(true);
        //通过属性.get()方法传入对象 拿到map
        Object h = h1.get(handler);
        //强转为map
        Map map= (Map) h;
        //设置值为2
        map.put("value", "2");

       System.out.println(annotation.value());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shstart7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值