Spring AOP 相关
- Maven配置
<dependency>
<!-- Spring base dependency -->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<!-- aspectj-style annotation support -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
- Spring xml配置
<aop:aspectj-autoproxy /> // 开启对aspectj-style的注解支持
- AOP 的发展历程
3.1 静态代理
Proxy设计模式, 通过在原类基础上添加一个代理类, 在执行原类的方法的同时,执行一些额外的操作,同时保持接口一致。因为通常一次代理只是针对一个接口实现额外的功能,如果工程量一大代理类的数量就会很多。
class Teacher {
void teach(Student stu){
System.out.println("I'm teaching: " + stu.toString());
}
}
class TeacherProxy {
private Teacher teacher;
void teach(Student stu){
prepare();
teacher.teach(stu);
}
void prepare(){
// do some work
}
}
3.2 动态代理
3.2.1 基于JDK动态代理
JDK提供了接口 InvocationHandler,虽然提高了一定程度的灵活性,将众多拓展业务通过一个通用的InvocationHandler来处理,但是,由于只支持接口方法,所以存在很多短板 (实际上还包括被代理类中继承自Object类中的几个方法,toString(), hashCode(), equals() 通过重写这些方法可以测试)
// 原因在于
Proxy.ProxyClassFactory #
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces)
// 在这个方法中,是对传入的interfaces 接口类数组进行遍历,通过反射,创建代理类的方法
/**
* @param proxy 代理对象,不是被代理的对象, 代理对象不是原对象,是根据传入的接口方法新生成的类的实例,理解Runtime中的 proxy 对象需要看源码
*
* @param method 代理对象被调用的接口方法, 可能是对象实现的接口
* 的父接口的方法
* @param args 被调用方法的实参列表,如果没有参数那么为空,如果是基本类型那么为对应的包装类
* return 被调用方法的返回值。不要求与原方法声明的类型一致,比如基本类型在这里只能返回包装类,但是必须兼容;如果返回null 而原方法返回的是基本类型,则抛 NullPointerException,如果类型不兼容会抛 ClassCastException
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
相关的调用方法是 Proxy 类中的
/**
* @param 指定一个类加载器
* @param 被代理类实现的接口列表,JDK只会代理接口方法
* @param 指定一个InvocationHandler 处理被代理的方法
* @return 返回一个代理对象
**/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
如何使用
ITestBean tb = new TestBean();
tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(),
tb.getClass().getInterfaces(), new TestBeanHander(tb)); // 这句用接口引用指向,不会报错
TestBean tmp = (TestBean) tb; // 强制转换为实现类,将抛出类强制转换异常
一些需要注意的细节
测试代码
输出
3.2.2 基于CGLib 动态代理
CGlib 的原理是为被代理类创建一个子类(通过 asm 库修改字节码, 子类中包括了父类,即被代理类的所有方法), 并在子类中采用方法拦截技术(修改了asm字节码)拦截父类方法的调用,织入横切逻辑。重量级单例对象,推荐使用CGLib, 虽然创建耗时耗资源,但是方法调用更快
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("后置代理");
return result;
}
4 Spring AOP 与 AspectJ
Spring AOP本身只实现了joinPoint 为方法的情况,AspectJ 有一套完整的很强大的 AOP,在Spring中可以 选择使用 AspectJ
4.1 AOP的概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象。代码中通过定义Aspect来实现AOP
3、连接点(joinpoint)
可以理解为,原有业务逻辑被切面切成多个小块,比如说,一个方法被横切,这个方法被称为joinpoint, 在joinpoint处将会有pointcut以定义额外的逻辑。joinpoint的所有类型请参考AspectJ的文档,Spring AOP中只支持方法类型
4、切点(pointcut)
匹配 join point 的谓词(a predicate that matches join points). 一个切点包含一个或多个连接点,一个切面会操作一个或多个切点
5、通知/增强(advice)
advice 定义具体的额外的业务逻辑,advice是与joinpoint相关联的,但是不是直接相关,而是与通过pointcut筛选/匹配出来的pointcut相关联,毕竟不是所有的pointcut我们都想去增强
6、目标对象
代理的目标对象
7、织入(weave)
对方法的增强被称为weave
8、引入(introduction)
对类的增强被称为introduction,如,添加新的接口(和对应的实现)
4.2 Spring AOP 大致等效的 JDK proxy 实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// BeforeAdvice
Object retVal = null;
try {
// AroundAdvice
retVal = method.invoke(target, args);
// AroundAdvice
// AfterReturningAdvice
}
catch (Throwable e) {
// AfterThrowingAdvice
}
finally {
// AfterAdvice
}
return retVal;
}
4.3 五种advice
4.3.1 before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
4.3.2 after return advice, 在一个 join point 正常返回后执行的 advice
4.3.3 after throwing advice, 当一个 join point 抛出异常后执行的 advice
4.3.4 after (finally) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
4.3.5 around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
4.4 写pointcut表达式,写实际业务逻辑
推荐阅读:
理解Spring AOP
理解JAVA动态代理