一、Spring AOP的所有实现方案
1. 基于代理的AOP
1.1 Java动态代理
- 适用场景:适用于代理接口实例。
- 实现方式:通过
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口,为接口生成动态代理对象。 - 优点:实现简单,易于理解。
- 缺点:只能代理接口,不能代理类。
1.2 CGLIB代理
- 适用场景:适用于代理类实例。
- 实现方式:通过CGLIB库(一个强大的、高性能的代码生成库),在运行时动态生成目标类的子类,从而实现对类的代理。
- 优点:可以代理类,不局限于接口。
- 缺点:相对于Java动态代理,性能可能略低,且生成的代理类较多,可能会增加类加载器的负担。
2. @AspectJ注解驱动的切面
-
适用场景:适用于复杂的AOP场景,需要细粒度控制切面和通知。
-
实现方式:通过定义切面类并使用
@Aspect
注解标注,在切面类中定义切点(使用AspectJ切点表达式)和通知(使用@Before
、@After
、@Around
等注解)。 -
优点:
- 强大的切点表达式语言,支持复杂的切点定义。
- 灵活的通知类型,包括前置通知、后置通知、环绕通知等。
- 易于理解和维护,代码结构清晰。
-
缺点:需要额外学习AspectJ切点表达式语言。
3. 纯POJO切面
- 适用场景:适用于简单的AOP需求,不需要复杂的切点定义和通知类型。
- 实现方式:通过实现Spring AOP的某个通知接口(如
MethodBeforeAdvice
、AfterReturningAdvice
等),并将其实例注册为Spring容器中的bean,然后在配置文件中或通过注解指定切点和通知。 - 优点:实现简单,不需要额外的库或注解。
- 缺点:灵活性较低,不支持AspectJ的切点表达式和环绕通知等高级特性。
4. 注入式AspectJ切面
-
适用场景:适用于需要在编译时或加载时织入切面的场景。
-
实现方式:通过AspectJ编译器或加载时织入器(如LTW - Load Time Weaving),将切面代码直接织入到目标类中。
-
优点:
- 支持编译时和加载时织入,性能较高。
- 完整的AspectJ特性支持,包括切点表达式、环绕通知等。
-
缺点:
- 需要额外的配置和依赖项(如AspectJ编译器或加载时织入器)。
- 增加了项目的复杂度。
5. BeanNameAutoProxyCreator实现AOP
- 适用场景:适用于需要根据Bean名称自动创建代理的场景。
- 实现方式:通过配置
BeanNameAutoProxyCreator
bean,并指定需要代理的bean名称或模式,Spring容器会自动为这些bean创建代理。 - 优点:自动化程度高,减少了手动配置的工作量。
- 缺点:灵活性较低,需要事先知道需要代理的bean名称。
总结
Spring AOP提供了多种实现方案,包括基于代理的AOP(Java动态代理和CGLIB代理)、@AspectJ注解驱动的切面、纯POJO切面、注入式AspectJ切面和BeanNameAutoProxyCreator实现AOP。开发者可以根据项目的具体需求和复杂度选择合适的实现方案。对于大多数Spring项目而言,@AspectJ注解驱动的切面因其灵活性和易用性而成为首选。
本文主要讨论纯POJO切面和aspectJ的实现。
二、AOP实现之aspectJ
1、先定义两个个业务接口
@RestController
@RequestMapping("test")
public class TestController {
@GetMapping("/a")
public ResponseResult test1() {
System.out.println("业务A执行业务方法");
return ResponseResult.sucessResult();
}
@GetMapping ("/b")
public ResponseResult test2() {
System.out.println("业务B执行业务方法");
return ResponseResult.sucessResult();
}
}
2、定义切面类
写法一
@Aspect
@Component
public class MyAspect {
@Around("execution(* com.example.controller.*.*(..))")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before");
Object obj = joinPoint.proceed();
System.out.println("after");
return obj;
}
@Before("execution(* com.example.controller.*.*(..))")
public void doBefore(JoinPoint joinPoint) throws Throwable {
System.err.println("这是一个前置通知,在方法调用之前被执行!!!");
}
@After("execution(* com.example.controller.*.*(..))")
public void doAfter(JoinPoint joinPoint) throws Throwable {
System.err.println("这是一个后置通知,在方法调用之后被执行!!!");
}
/**
* @param result:需要与 returning 指定的值保存一致,两者需要同时使用!
* result参数的类型需要与目标方法的返回值类型保存一致,如果两者不一致则不会执行该通知方法!
* 如果不确定目标方法返回的类型,则可以使用 Object
*/
@AfterReturning(value = "execution(* com.example.controller.*.*(..))",
returning = "result")
public void afterReturning(Object result) {
System.out.println("返回通知:afterReturning = " + result);
}
/**
* @param ex:需要与 throwing 指定的值保存一致,两者需要同时使用!
*/
@AfterThrowing(value = "execution(* com.example.controller.*.*(..))",
throwing = "ex")
public void afterThrowing(Throwable ex) {
if(ex instanceof ArithmeticException) {
System.out.println("异常通知:afterThrowing");
}
}
}
写法二
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.controller.*.*(..))")
private void pointCut() {
}
@Around("pointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before");
Object obj = joinPoint.proceed();
System.out.println("after");
return obj;
}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
System.err.println("这是一个前置通知,在方法调用之前被执行!!!");
}
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) throws Throwable {
System.err.println("这是一个后置通知,在方法调用之后被执行!!!");
}
/**
* @param result:需要与 returning 指定的值保存一致,两者需要同时使用!
* result参数的类型需要与目标方法的返回值类型保存一致,如果两者不一致则不会执行该通知方法!
* 如果不确定目标方法返回的类型,则可以使用 Object
*/
@AfterReturning(value = "pointCut()",
returning = "result")
public void afterReturning(Object result) {
System.out.println("返回通知:afterReturning = " + result);
}
/**
* @param ex:需要与 throwing 指定的值保存一致,两者需要同时使用!
*/
@AfterThrowing(value = "pointCut()",
throwing = "ex")
public void afterThrowing(Throwable ex) {
if(ex instanceof ArithmeticException) {
System.out.println("异常通知:afterThrowing");
}
}
}
上面两种写法效果是一样的,但是推荐第二种写法,将切点PointCut和通知Advice分开书写,减少重复定义。
另外切面类必须注入到容器之中。
3、切面类组成
(1)切点Pointcut
表示哪些类或方法需要被拦截,即定义切点。
* com.example.controller..*.*(..)
切点表达式
第一个*号:表示返回类型, *号表示所有的类型
包名: 表示需要拦截的包名,后面的..表示当前包和当前包的所有子com.example.controller包、子孙包下所有类的方法。
第二个*号: 表示类名,*号表示所有的类。
*(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
如果又多个表达式可以用||连接,例如
@Pointcut("execution(public * com. . . .controller. .*(..)) || execution(public * com. . .controller. . (..))")
切点也可以是某个自定义注解,把注解加在指定的方法上面也可以实现业务方法增强,例如
@Before("@annotation(com.example.annotation.Myannotation)")
(2)通知Advice
表示实际增强的逻辑入口,即当方法被拦截时需要执行的代码。
@Before :标识一个前置增强方法,相当于BeforeAdvice的功能
@AfterReturning :后置增强,相当于AfterReturningAdvice,方法退出时执行
@AfterThrowing :异常抛出增强,相当于ThrowsAdvice
@After :final增强,不管是抛出异常或者正常退出都会执行
@Around :环绕增强,相当于MethodInterceptor
4、aop执行顺序
三、纯POJO切面(Advisor)
- Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。
- Advisor是一个接口,通常由Advice和Pointcut两个组件组成。
1、定义Advice
Advice接口有多种实现,如MethodInterceptor、BeforeAdvice、AfterAdvice等,分别代表不同的通知类型。
你可以根据需要选择适当的Advice实现,并定义具体的增强逻辑。例如
public class MyAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// 前置逻辑
System.out.println("xxxxxxxxxxxxxxxx");
}
}
2、定义Advisor
@Configuration
public class AopConfig {
@Bean
public Advisor myAdvisor() {
// 创建切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.controller.*.*(..))");
// 创建通知
MyAdvice myAdvice = new MyAdvice();
// 创建Advisor
return new DefaultPointcutAdvisor(pointcut, myAdvice);
}
}
AspectJ 提供了更广泛和灵活的 AOP 支持,包括编译时和加载时织入,以及更丰富的注解和表达式语言。Spring AOP(通过 Advisor)主要支持运行时织入。效率上面AspectJ 更高。