前言
前段时间看了Spring AOP的源码,其底层就是通过JDK与CGLIB动态代理实现的,因此在这里对两种代理方式进行实践和总结。
准备工作
首先创建一个代理创建器ProxyCreator
接口,我们将分别实现JdkProxyCreator
与CglibProxyCreator
,通过实现getProxy()
方法返回一个代理对象:1
2
3public interface ProxyCreator {
Object getProxy();
}
创建一个UserService
接口,提供登陆login()
和退出logout()
两个方法,并创建一个它的实现类UserServiceImpl
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface UserService {
void login(int userId);
void logout(int userId);
}
public class UserServiceImpl implements UserService {
public void login(int userId) {
System.out.println("用户" + userId + "登陆成功");
}
public void logout(int userId) {
System.out.println("用户" + userId + "退出成功");
}
}
接下来,我们将分为JDK与CGLIB这两种动态代理方式,为上面的登陆与退出两个方法前后打印日志。
JDK动态代理
首先,创建一个JdkProxyCreator
并实现ProxyCreator
和InvocationHandler
接口: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
36public class JdkProxyCreator implements ProxyCreator, InvocationHandler {
private Object target;
public JdkProxyCreator(Object target) {
Class<?>[] interfaces = target.getClass().getInterfaces();
if(interfaces.length == 0){
throw new IllegalArgumentException("目标对象必须实现接口");
}
this.target = target;
}
public Object getProxy() {
Class<?> clazz = target.getClass();
// 该类本身实现了 InvocationHandler 接口,所以将自身作为参数传入
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("login".equals(method.getName())){
System.out.println("登陆前日志打印...");
} else if("logout".equals(method.getName())){
System.out.println("退出前日志打印...");
}
Object res = method.invoke(target, args); // 调用目标方法
if("login".equals(method.getName())){
System.out.println("登陆后日志打印...");
} else if("logout".equals(method.getName())){
System.out.println("退出后日志打印...");
}
return res;
}
}
可以看出,创建代理对象的核心其实就是这一行代码:Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
,其中第一个参数是目标对象的类加载器,第二个参数是目标对象实现的接口,第三个参数则是一个InvocationHandler
,因为该类已经实现了这个接口,且打印日志的逻辑都封装在了该接口的invoke
方法中了,所以直接传入this
即可。
测试代码:1
2
3
4
5
6
7
8
9public class JdkProxyTest {
public static void main(String[] args) {
ProxyCreator proxyCreator = new JdkProxyCreator(new UserServiceImpl());
UserService userServiceProxy = (UserService) proxyCreator.getProxy();
System.out.println("代理对象的类型:" + userServiceProxy.getClass());
userServiceProxy.login(1);
userServiceProxy.logout(1);
}
}
控制台输出:1
2
3
4
5
6
7代理对象的类型:class com.sun.proxy.$Proxy0
登陆前日志打印...
用户1登陆成功
登陆后日志打印...
退出前日志打印...
用户1退出成功
退出后日志打印...
CGLIB动态代理
CGLIB是一款优秀的Java字节码生成框架,它可以生成并操纵Java字节码。因此,CGLIB的动态代理其实就是使用字节码技术为目标类创建子类,并且在子类中拦截父类方法的调用,并且顺势织入横切逻辑。
首先,在pom.xml中引入CGLIB的依赖:1
2
3
4
5
6
7
8<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
然后创建一个MethodInterceptor
接口的实现类,将打印日志的操作封装在intercept()
方法中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class UserMethodInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if("login".equals(method.getName())){
System.out.println("登陆前日志打印...");
} else if("logout".equals(method.getName())){
System.out.println("退出前日志打印...");
}
Object res = methodProxy.invokeSuper(o, objects);
if("login".equals(method.getName())){
System.out.println("登陆后日志打印...");
} else if("logout".equals(method.getName())){
System.out.println("退出后日志打印...");
}
return res;
}
}
其中,Object res = methodProxy.invokeSuper(o, objects);
这一行将会调用父类的实现,也就是目标对象的原始方法。这里要注意的是一定不要写成了method.invoke(o, objects);
,否则会造成死循环。
接着,创建一个CglibProxyCreator
实现ProxyCreator
接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class CglibProxyCreator implements ProxyCreator {
private Object target;
private MethodInterceptor methodInterceptor;
public CglibProxyCreator(Object target, MethodInterceptor methodInterceptor) {
this.target = target;
this.methodInterceptor = methodInterceptor;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(methodInterceptor);
return enhancer.create();
}
}
不同于JDK动态代理使用Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
的方式,这里创建了一个Enhancer
对象,并且设置了目标对象的类作为父类,还设置了之前的方法拦截器UserMethodInterceptor
(其实这里也可以让该类本身实现MethodInterceptor
接口),最后通过enhancer.create()
方法返回代理对象。
测试代码:1
2
3
4
5
6
7
8
9public class CglibProxyTest {
public static void main(String[] args) {
ProxyCreator proxyCreator = new CglibProxyCreator(new UserServiceImpl(), new UserMethodInterceptor());
UserService userServiceProxy = (UserService) proxyCreator.getProxy();
System.out.println("代理对象的类型:" + userServiceProxy.getClass());
userServiceProxy.login(2);
userServiceProxy.logout(2);
}
}
控制台输出:1
2
3
4
5
6
7代理对象的类型:class cn.hecenjie.UserServiceImpl$$EnhancerByCGLIB$$76be9bea
登陆前日志打印...
用户2登陆成功
登陆后日志打印...
退出前日志打印...
用户2退出成功
退出后日志打印...
从上面的输出可以看出,代理对象其实是目标对象的一个子类。
总结
JDK代理要求目标对象有实现接口,而CGLIB则不需要。从性能上来说,CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。