spring AOP
先看一个例子,增强 spring bean 的方法,使用 JDK 动态代理的方式
@Component
public class Test implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 放到单例池的是代理对象
* @param bean 原来的 bean
* @param beanName 原来的 bean 名称
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 如果 bean 是 User 才增强
if (bean instanceof User) {
User user = (User) applicationContext.getBean("user");
// 返回代理对象( JDK 动态代理)
return Proxy.newProxyInstance(
user.getClass().getClassLoader(),
user.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
System.out.println("增强前.....");
// 执行 bean 本身的方法(没有过滤方法,就是增强所有方法)
Object invoke = method.invoke(user, args);
System.out.println("增强后.....");
return invoke;
}
);
}
// 如果 bean 不是 User 直接返回原来的
return bean;
}
}
spring 的 AOP 具体实现功能更强大,但也是利用 BeanPostProcessor 为 bean 生成代理对象从而达到增强 bean 的目的
AOP 是一种思想,实现方式不唯一,有 AspectJ 和 Advisor,spring 的声明式事物使用的是 Advisor,但是 AspectJ 使用更广泛
AspectJ 更强大更灵活,Advisor 相对功能单一但实现起来更方便,开发人员基本不会使用 Advisor ,所以这里只介绍 AspectJ
AspectJ 概念
| 概念 | 单词 | 解释 |
|---|---|---|
| 目标对象 | Target | 被增强方法所在的对象 |
| 代理对象 | Proxy | 代理想想,后面实际调用的对象 |
| 连接点 | Joinpoint | 目标对象中可以被增强的方法(就是bean的所有方法) |
| 切入点/切点 | Pointcut | 目标对象实际被增强的方法(bean实际增强的方法) |
| 通知 | Advice | 增强的代码,分为多种类型 |
| 切面 | Aspect | 通知+切入点(bean原来的方法+增强的东西) |
| 织入 | Weaving | 将通知和切入点组合的过程 |
通知
| 类型 | xml 配置方式 | 执行时机 | 备注 |
|---|---|---|---|
| 前置 | <aop:before> | 目标方法执行前 | |
| 后置 | <aop:after-returning> | 目标方法执行后 | 目标方法异常不执行 |
| 环绕 | <aop:around> | 目标方法执行前后都执行 | 目标方法异常,后置不执行 |
| 异常 | <aop:after-throwing> | 目标方法发生异常时执行 | 不发生异常不执行 |
| 最终 | <aop:after> | 目标方法执行后 | 不管目标方法是否异常都执行 |
切点表达式
execution([访问修饰符] 返回值类型 包名.类名.方法名(方法参数))
- 返回值类型、某一级包名、类名、方法名 可以用 * 表示任意,不限定
- 包名中使用单点表示单个子级,双点表示所有子级(com.style.spring.xml,com..xml)
- 参数可以使用..表示任意参数
示例
@Aspect
@Component
public class TestAspect {
// 切点表达式
@Pointcut("execution (* com.dtyunxi.icommerce.svr.rest..*.*(..))")
public void pointCut(){}
// 这又不是静态方法为什么可以这样调方法?
// 这还在字符串里呢,咋可能调方法,这时规定的语法格式,可以理解为不是 java 代码
@Before("TestAspect.pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知");
System.out.println("目标对象:" + joinPoint.getTarget());
System.out.println("切点表达式:" + joinPoint.getStaticPart());
}
// 还可以省略类名,直接写方法名
// throwing 属性值要和方法参数明相等
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("发生异常" + e.getMessage());
}
@Before("pointCut()")
public void after(JoinPoint joinPoint) {
System.out.println("后置通知");
}
// ProceedingJoinPoint 是 JoinPoint 的子类,可以执行目标方法
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前置通知");
Object proceed = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕后置通知");
return proceed;
}
}
