Java代理模式: JDK代理与CGLIB代理
一、JDK动态代理
1.1 介绍
JDK动态代理机制的核心是InvocationHandler
接口和Proxy
类。
Proxy类主要是用来生成一个代理对象,使用的是newProxyInstance()
方法:
1 | public static Object newProxyInstance(ClassLoader loader, |
这个方法一共有三个参数:
- loader:类加载器,用于加载代理对象。
- interface:被代理类实现的一些接口,可以指定某些具体的接口来进行代理。
- h:实现了
InvocationHandler
接口的对象。
要实现动态代理还需要实现InvocationHandler
来自定义处理逻辑,当动态代理对象调用了一个方法时,这个方法的调用就会被转发到实现InvocationHandler
的invoke
方法来调用。
1 | public interface InvocationHandler { |
同样invoke()
方法有三个参数:
- proxy:动态生成的代理类。
- method:与代理类对象调用的方法相对应。
- args:当前method方法的参数。
可以通过Proxy
类的newProxyInstance()
创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的invoke()
方法。
1.2 使用步骤
定义一个接口及其实现类:
比如我们现在定义一个HelloService
类:1
2
3public interface HelloService {
String sayHello(String name);
}再定义它的实现类:
1
2
3
4
5
6public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
System.out.println("Hello, " + name)
return name;
}
}自定义
InvocationHandler
并重写invoke()
方法,在invoke()
方法里我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MyInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
/**
* 可以通过判断method的name来对不同method做不同处理
*/
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}通过
Proxy.newProxyInstance()
方法创建代理对象:
一般用工厂类来封装:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 可以自定义需要代理的接口,例子的写法默认全部代理
* 比如:
* Class<?>[] interfaces = { HelloService.class };
* return Proxy.newProxyInstance(
* target.getClass().getClassLoader(),
* interfaces, // 只指定你写在interfaces里的接口
* new DebugInvocationHandler(target)
* );
*/
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
}
}实际使用:
1
2HelloService helloService = (HelloService) JdkProxyFactory.getProxy(new HelloServiceImpl());
helloService.send("java");控制台会输出:
1
2
3before method sayHello
Hello, java
after method sayHello
二、CGLIB动态代理
2.1 介绍
JDK动态代理有一个致命问题是只能代理实现了接口的类,为了解决这个问题,我们可以使用CGLIB动态代理来避免。
CGLIB是一个基于ASM字节码的生成库,允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。在SpringBoot的AOP模块中,如果目标对象实现了接口,默认使用JDK代理,否则使用CGLIB动态代理。
在CGLIB动态代理机制中,MethodInterceptor
接口和Enhancer
类是核心。
需要自定义MethodInterceptor
并且重写intercept()
方法用于拦截增强被代理类的方法:
1 | public interface MethodInterceptor extends Callback{ |
这个方法有四个参数:
- obj:被代理的对象(需要增强的对象)。
- method:被拦截的方法(需要增强的方法)。
- args:方法的参数。
- proxy:用于调用原始方法。
可以通过Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor
中的intercept()
方法。
2.2 使用步骤
定义一个类:
这里定义类HelloService
:1
2
3
4
5
6public class HelloService {
public String sayHello(String name) {
System.out.println("Hello, " + name)
return name;
}
}自定义
MethodInterceptor
并且重写intercept()
方法,和JDK动态代理中的invoke
方法类似:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class MyMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象本身(注意不是原始对象,如果使用method.invoke(o, args)会导致循环调用)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 高性能的方法调用机制,避免反射开销
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
System.out.println("after method " + method.getName());
return object;
}
}注意这里的
o
已经是继承了原始类的子类,所以会产生递归调用,需要使用包含了原始类method指针的methodProxy
来进行父类方法调用。通过
Enhancer
类的create()
创建代理类:
也是通过工厂类来包装:1
2
3
4
5
6
7
8
9public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(clazz.getClassLoader());
enhancer.setSuperclass(clazz); // 设置被代理类
enhancer.setCallback(new MyMethodInterceptor());
return enhancer.create();
}
}注意被代理类是父类,因为代理类会继承它。
实际使用:
1
2HelloService helloService = (HelloService) CglibProxyFactory.getProxy(HelloService.class);
helloService.sayHello("java");控制台会输出:
1
2
3before method sayHello
Hello, java
after method sayHello
三、两种模式的对比
- JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。 另外, CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法,private方法也无法代理。
- 就二者的效率来说,大部分情况都是JDK动态代理更优秀,随着JDK版本的升级,这个优势更加明显。