Spring对AOP的实现包括以下3种方式:
- 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
- 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
- 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。
在本篇文章中,我们主要讲解前两种方式。
- 基于AspectJ的AOP注解式开发
- 定义目标类以及目标方法
// 目标类 public class OrderService { // 目标方法 public void generate(){ System.out.println("订单已生成!"); } }
- 定义切面类
// 切面类 @Aspect public class MyAspect { }
注解@Aspect会告诉Spring该类是一个注解类
- 目标类和切面类都纳入spring bean管理
- 在目标类OrderService上添加**@Component**注解。
在切面类MyAspect类上添加**@Component**注解。
- 在目标类OrderService上添加**@Component**注解。
- 在spring配置文件中添加组建扫描
<!--开启组件扫描--> <context:component-scan base-package="com.xxx.spring6.service"/>
这里是单单扫描com.xxx.spring6.service中的所有类,如果想扩大,我们完全可以扫描com.xxx下的所有包
- 在切面类中添加通知
// 切面类 @Aspect @Component public class MyAspect { // 这就是需要增强的代码(通知) public void advice(){ System.out.println("我是一个通知"); } }
- 在通知上添加切点表达式
// 切面类 @Aspect @Component public class MyAspect { // 切点表达式 @Before("execution(* com.xxx.spring6.service.OrderService.*(..))") // 这就是需要增强的代码(通知) public void advice(){ System.out.println("我是一个通知"); } }
其中,注解@Before表示前置通知(具体的通知类型在上一篇有讲过AOP基础部分
,下面也会有方法中的通知注解)
- 在spring配置文件中启用自动代理
<!--开启组件扫描--> <context:component-scan base-package="com.xxx.spring6.service"/> <!--开启自动代理--> <aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:aspectj-autoproxy proxy-target-class="true"/> 开启自动代理之后,凡是带有@Aspect注解的bean都会生成代理对象。
proxy-target-class="true" 表示采用cglib动态代理。
proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类(AOP的底层就是动态代理,关于动态代理的内容可查看代理机制)
- 定义目标类以及目标方法
- 通知类型
- 前置通知:@Before 目标方法执行之前的通知
- 后置通知:@AfterReturning 目标方法执行之后的通知
- 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
- 异常通知:@AfterThrowing 发生异常之后执行的通知
- 最终通知:@After 放在finally语句块中的通知
- 接下来,编写程序来测试这几个通知的执行顺序
读者可自行测试,其中异常通知需要产生异常才能触发,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,**后置通知**和**环绕通知的结束部分**不会执行// 切面类 @Component @Aspect public class MyAspect { @Around("execution(* com.xxx.spring6.service.OrderService.*(..))") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); // 执行目标方法。 proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); } @Before("execution(* com.xxx.spring6.service.OrderService.*(..))") public void beforeAdvice(){ System.out.println("前置通知"); } @AfterReturning("execution(* com.xxx.spring6.service.OrderService.*(..))") public void afterReturningAdvice(){ System.out.println("后置通知"); } @AfterThrowing("execution(* com.xxx.spring6.service.OrderService.*(..))") public void afterThrowingAdvice(){ System.out.println("异常通知"); } @After("execution(* com.xxx.spring6.service.OrderService.*(..))") public void afterAdvice(){ System.out.println("最终通知"); } }
- 切面的先后顺序
- 我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高
- 优化使用切点表达式
- 上面的切点表达式重复写了多次,没有得到复用,同时如果要修改切点表达式,需要修改多处,难维护
- 我们可以将切点表达式单独的定义出来,在需要的位置引入即可
// 切面类 @Component @Aspect @Order(2) public class MyAspect { @Pointcut("execution(* com.xxx.spring6.service.OrderService.*(..))") public void pointcut(){} @Around("pointcut()") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); // 执行目标方法。 proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); } @Before("pointcut()") public void beforeAdvice(){ System.out.println("前置通知"); } @AfterReturning("pointcut()") public void afterReturningAdvice(){ System.out.println("后置通知"); } @AfterThrowing("pointcut()") public void afterThrowingAdvice(){ System.out.println("异常通知"); } @After("pointcut()") public void afterAdvice(){ System.out.println("最终通知"); } }
使用@Pointcut注解来定义独立的切点表达式。
注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置
- 基于XML配置方式的AOP
- 编写目标类
// 目标类 public class VipService { public void add(){ System.out.println("保存vip信息。"); } }
- 编写切面类,并且编写通知
// 负责计时的切面类 public class TimerAspect { public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long begin = System.currentTimeMillis(); //执行目标 proceedingJoinPoint.proceed(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } }
- 编写spring配置文件
<!--纳入spring bean管理--> <bean id="vipService" class="com.xxx.spring6.service.VipService"/> <bean id="timerAspect" class="com.xxx.spring6.service.TimerAspect"/> <!--aop配置--> <aop:config> <!--切点表达式--> <aop:pointcut id="p" expression="execution(* com.xxx.spring6.service.VipService.*(..))"/> <!--切面--> <aop:aspect ref="timerAspect"> <!--切面=通知 + 切点--> <aop:around method="time" pointcut-ref="p"/> </aop:aspect> </aop:config> </beans>
- 编写目标类