前言

前段时间看了Spring AOP的源码,其底层就是通过JDK与CGLIB动态代理实现的,因此在这里对两种代理方式进行实践和总结。

准备工作

首先创建一个代理创建器ProxyCreator接口,我们将分别实现JdkProxyCreatorCglibProxyCreator,通过实现getProxy()方法返回一个代理对象:

1
2
3
public interface ProxyCreator {
Object getProxy();
}

创建一个UserService接口,提供登陆login()和退出logout()两个方法,并创建一个它的实现类UserServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public 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并实现ProxyCreatorInvocationHandler接口:

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
public 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
9
public 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
20
public 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
18
public 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
9
public 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方式要更为合适一些。