JDK 动态代理和 CGLIB 动态代理

AOP(Aspect Oriented Programing)即面向切面编程,采取横向抽取机制,取代了传统纵向继承体系重复性代码,应用在性能监视、事务管理、安全检查、缓存。

AOP 术语

  • Joinpoint(连接点):指被拦截到的点。Spring 中指的是方法,Spring 只支持方法类型的连接点。
  • Pointcut(切入点):指要对哪些 Joinpoint 进行拦截。
  • Advice(通知/增强):指拦截到 Joinpoint 之后所要做的事情。通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)。
  • Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下 Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):代理的目标对象。
  • Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面):是切入点和通知(引介)的结合。

AOP 底层实现

JDK 动态代理

JDK 动态代理适用于使用接口类的业务。

定义一个接口 UserDao 及其实现类 UserDaoImpl,其中包含了 saveupgradefinddelete 四个方法。

1
2
3
4
5
6
7
8
package com.imtt.aop.demo1;

public interface UserDao {
public void save();
public void update();
public void find();
public void delete();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.imtt.aop.demo1;

public class UserDaoImpl implements UserDao{
public void save() {
System.out.println("保存用户");
}
public void update() {
System.out.println("修改用户");
}
public void find() {
System.out.println("查询用户");
}
public void delete() {
System.out.println("删除用户");
}
}

定义一个用来实现 JDK 动态代理的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.imtt.aop.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao) {
this.userDao = userDao;
}

public Object createProxy() {
return Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
}
}

createProxy 方法中使用 Proxy.newProxyInstance() 创建代理,其中有三个参数:

  1. 类加载器(Class Loader);
  2. 需要实现的接口数组;
  3. InvocationHandler 接口,所有动态代理类的方法调用,都会交由 InvocationHandler 接口实现类里的 invoke() 方法去处理;如果当前类实现了 InvocationHandler 接口可直接传入 this

接下来实现 invoke() 方法:

1
2
3
4
5
6
7
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("--->权限校验");
return method.invoke(userDao, args);
}
return method.invoke(userDao, args);
}

invoke() 方法有三个参数:

  1. 动态代理类的引用,通常情况下不需要它。但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,如方法列表,annotation等;
  2. 方法对象的引用,代表被动态代理类调用的方法;
  3. args 对象数组,代表被调用方法的参数。其中基本类型(int, long)会被装箱成对象类型(Interger, Long)。

上面的方法只对方法名为 save 的方法进行增强,如果不是,原封不动的返回:method.invoke(userDao, args)

代码写完了,进行单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imtt.aop.demo1;
import org.junit.Test;

public class SpringDemo1 {
@Test
public void demo1() {
UserDaoImpl userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
proxy.save();
proxy.delete();
proxy.find();
proxy.update();
}
}

通过代理类 proxy 来调用 save 等四个方法,预期打印结果如下:

1
2
3
4
5
-->权限校验
保存用户
删除用户
查询用户
修改用户

CGLIB 代理

如果不使用接口,JDK 动态代理就不行了,此时可以采用 CGLIB。CGLIB采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题。

使用 CGLIB 代理需要引入 CGLIB依赖包,也可以引入 Spring 的包。实际测试过程中引入了 Spring 的 core、beans、context、expression 四个包。

还是使用上面的 UserDao 类稍作修改,不再是一个接口,并且包含了与 UserDaoImpl 类中相同实现的四个方法。

接着定义一个用来实现 CGLIB 代理的类:

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
package com.imtt.aop.demo2;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {
private UserDao userDao;

public MyCglibProxy(UserDao userDao) {
this.userDao = userDao;
}

public Object createProxy() {
//1.创建核心类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(this.userDao.getClass());
//3.设置回调
enhancer.setCallback(this);
//4.生成代理
return enhancer.create();
}

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("-->权限校验");
return methodProxy.invokeSuper(proxy, args);
}
return methodProxy.invokeSuper(proxy, args);
}
}

createProxy 方法中使用 CGLIB 的 Enhancer 生成代理对象,设置回调时的参数为一个 MethodInterceptor 接口,这个接口中重要的是要实现 intercept 方法。

intercept 方法中通过调用 MethodProxy.invokeSuper() 方法将调用转发给原始对象。

最后来进行单元测试:

1
2
3
4
5
6
7
//other codes here.
UserDao userDao = new UserDao();
UserDao proxy = (UserDao) new MyCglibProxy(UserDao).createProxy();
proxy.save();
proxy.find();
proxy.update();
proxy.delete();

通过代理 proxy 来调用 save 等四个方法,预期打印结果如下:

1
2
3
4
5
-->权限校验
保存用户
删除用户
查询用户
修改用户
坚持原创技术分享,您的支持将鼓励我继续创作!