Spring 的 AOP 概述和底层实现
1. 什么是 AOP
- AOP (Aspect Oriented Programing),即面向切面编程
- AOP 采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
- Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
简单的来说就是:
生成和目标类一样的类或者继承目标类,生成子类,我们都把该类叫做代理类,然后通过代理方式在代理类中添加方法,以达到对目标类中的方法进行增强的目的。
2. AOP 相关术语
Joinpoint(连接点):
所谓连接点是指那些可以被拦截的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
比如增删改查这些方法都可以增强,这些方法称为是连接点。
Pointcut(切入点):
指的是真正被拦截的点。
比如我们只想对类中的 save()
方法进行增强(做权限校验),save
方法称为是切入点。
Advice(通知/增强):
指拦截到 Joinpoint 之后所要做的事。
通知分为前置通知(方法执行之前)、后置通知(方法执行之后)、异常通知、最终通知、环绕通知(切面要完成的功能)
比如对 save
方法要进行权限校验,权限校验的方法称为是通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):
代理(被增强)的目标对象。
Weaving(织入):
是指把增强(Advice)应用到目标对象(Target)来创建新的代理对象的过程。
比如将权限校验应用到 UserDaoImpl
的 save
方法的这个过程。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
3. Spring 的底层实现原理
两种实现:
- JDK 动态代理
- CGLIB生成代理
JDK 动态代理:
public class MyJdkProxy implements InvocationHandler{
private UserDao userDao;
public MyJdkProxy(UserDao userDao) {
this.userDao = userDao;
}
public Object createProxy() {
// 获得目标类
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对 save 进行增强
if ("save".equals(method.getName())) {
System.out.println("权限校验...");
// args 参数化,UserDao中方法列表
return method.invoke(userDao, args);
}
// invoke 调用 UserDao 中的方法
return method.invoke(userDao, args);
}
}
其中:
Proxy.newProxyInstance();
方法需要传入三个参数:类的加载器,类实现的接口,InvocationHandler
的接口。三个参数:
userDao.getClass().getClassLoader()
userDao.getClass().getInterfaces()
- 通过匿名内部类或使用
implements InvocationHandler
接口,再通过 this 实例化
然后在测试类中 new 代理类,这样我们在测试类中调用实现类中的方法就相当于变成了调用动态代理类中的 invoke 方法。
注意:
JDK 动态代理是对实现接口类实行代理,建立一个实现目标类中方法的代理类。(面向接口)
CGLIB生成代理:
public class MyCglibProxy implements MethodInterceptor{
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao) {
this.productDao = productDao;
}
public Object createProxy() {
//1. 创建核心类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(productDao.getClass());
//3. 设置回调
enhancer.setCallback(this);
//4. 生成代理(子类)
Object proxy = enhancer.create();
return proxy;
}
//回调函数中实例化 MethodInterceptor 接口方法(intercept, 类似于 invoke 方法),通过 this
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("权限校验=========");
}
// invokeSuper 调用父类 ProductDao 方法
return methodProxy.invokeSuper(proxy, args);
}
}
- 对于不使用接口的业务类,无法使用 JDK 动态代理
- CGLIB 采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题
注意:
CGLIB 实现和 JDK 动态代理不同,是生成一个类来继承目标类。
4. 代理知识总结
- 程序中应优先对接口创建代理,便于程序解耦维护
- 标记为 final 的方法,不能被代理,因为无法进行覆盖
- JDK 动态代理,是针对接口生成子类,接口中方法不能使用 final 修饰
- CGLIB 是针对目标类生成子类,因此类或方法不能使用 final
- Spring 只支持方法连接点,不提供属性连接
附:
spring 核心包有四个:
- spring-context
- spring-beans
- spring-expression
- spring-core