Java动态代理

什么是代理

代理是一种软件设计模式,通过不直接访问被代理对象的方式,而访问代理对象的方法。代理既可以做原对象的增强也可以做原对象的替代。

代理适用的场景

  • 设计模式中有一个设计原则是开闭原则,在不修改原来代码的基础上,我们可以采用代理的方式对类进行功能增强

  • 在RPC框架中,通过代理接口来模拟访问远程服务的方法

  • Spring的AOP机制就是采用动态代理的机制来实现切面编程

静态代理与动态代理的区别

我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制

动态代理:InvocationHandler角色的由来

image

有上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler

动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法

image

在面向对象的编程之中,如果我们想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:

  • 一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。

  • 还有比较隐晦的方式,就是通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能

在Java中提供了两种常见的创建动态代理的机制,就是按照以上两种设计思路,JDK动态代理(实现接口)和CGlib动态代理(继承类)

JDK动态代理(通过接口)

解析

比如现在想为RealSubject这个类创建一个动态代理对象,JDK主要会做以下工作:

1.获取 RealSubject上的所有接口列表;
2.确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
3.根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
4.将对应的字节码转换为对应的class 对象;
5.创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
6.Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象;

JDK通过java.lang.reflect.Proxy包来支持动态代理,一般情况下,我们使用下面的newProxyInstance()

1
2
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

而对于InvocationHandler,我们需要实现它的invoke(),在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法

1
2
//在代理实例上处理方法调用并返回结果
Object invoke(Object proxy,Method method,Object[] args)

通过观察生成的动态代理类具有以下几个特点:

1.继承自 java.lang.reflect.Proxy,实现了 Rechargable,Vehicle 这两个ElectricCar实现的接口;
2.类中的所有方法都是final的;
3.所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法;

image

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//继承InvocationHandler实现invoke
public class JdkDynamicProxy implements InvocationHandler {
private Object target ;
public JdkDynamicProxy(Object target ) {
this.target=target;
}
//每次创建代理对象就会执行下面的方法,proxy是本代理对象,method是当前执行的法,可以在这里过滤获取目标方法,参数三是该方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
before();
Object result = method.invoke(target, args);//调用invoke,传入标对象和参数进行执行之前的方法
after();
return result;
}
private void before() {
System.out.println("before");
}
private void after() {
System.out.println("after");
}
}

public static void main(String[] args) {
Hello helloImpl = new HelloImpl();
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(helloImpl);
//创建代理对象,使用到静态工具方法Proxy.neweProxyInstace(类加载器,接类,增类);返回值为代理的对象
Hello helloProxy = (Hello) Proxy.newProxyInstanc(helloImpl.getClass().getClassLoader(), helloImpl.getClass(.getInterfaces(), jdkDynamicProxy);
helloProxy.say("hello");
}

CGlib动态代理(通过类继承)

解析

JDK中提供的生成动态代理类的机制有个鲜明的特点是:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。如果某个类没有实现接口,那么这个类就不能同JDK产生动态代理了,而可以使用CGLIB(Code Generation Library),通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。和JDK动态代理一样,底层是处理字节码,cglib创建某个类A的动态代理类的模式是:

1.查找A上的所有非final的public类型的方法定义;
2.将这些方法的定义转换成字节码;
3.将组成的字节码转换成相应的代理的class对象;
4.实现MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class CGLibProxy implements MethodInterceptor {
/***
* 快速创建动态代理对象
* @param cls 需要代理的字节码对象
* @return 代理对象
*/
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls,this);
}
/***
* 拦截的方法
* @param obj 原对象
* @param method 源对象中的方法
* @param args 源方法中的参数
* @param proxy 方法代理
*
* @return 返回代理对象
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
//执行原方法
Object result = proxy.invokeSuper(obj, args);
after();
return result;
}
private void before() {
System.out.println("before");
}
private void after() {
System.out.println("after");
}
}

public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class);
helloProxy.say("hello");
}

原理区别

  • java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

  • 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

Spring在选择用JDK还是CGLib的依据

  • 当Bean实现接口时,Spring就会用JDK的动态代理

  • 当Bean没有实现接口时,Spring使用CGLib来实现

参考文献