通知类型,基于注解的AOP
环境:
Idea:2019.3.1
系统:windows10 家庭版
Jdk: 8
spring:5.0.3 release
spring文档
项目代码
一、通知类型
1.前置通知
作用:
切入点方法执行前执行
标签:
<aop:before></aop:before>
示例:
<aop:before method="beforePrintLog" pointcut="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
2.后置通知
作用:
切入点方法执行后执行
标签:
<aop:after-returning></aop:after-returning>
示例:
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())"></aop:after-returning>
3.异常通知
作用:
切入点方法异常时执行
标签:
<aop:after-throwing></aop:after-throwing>
示例:
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())"></aop:after-throwing>
注意:异常通知和后置通知同时只能有一个执行,能想到,如果后置通知执行了,就代表切入点方法没有问题,那么异常通知就不会执行,而如果切入点方法出现异常,那么异常通知就会执行,而此时不会执行到后置方法
4.最终通知
作用:
不管切入点方法是否成功执行都会执行
标签:
<aop:after></aop:after>
示例:
<aop:after method="afterPrintLog" pointcut="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())"></aop:after>
5.环绕通知
标签:
<aop:around></aop:around>
示例:
<aop:around method="aroundPrintLog" pointcut="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())></aop:around>
这里要停顿下,我们这样配置了环绕通知后,运行切入点方法可以看到只有环绕通知执行了,而切入点方法没执行
这里我们对比一下我们之前写的动态代理增强的方法
可以明确地知道,动态代理的环绕通知中有明确的切入点方法的调用,而我们配置的环绕通知方法中没有
解决方法:
spring框架为我们提供了一个接口,ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时spring框架会为我们提供该接口的实现类供我们使用
/*
* rtValue:切入点方法的返回值
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
//明确调用切入点方法
try {
//得到方法执行所需参数
Object [] args = pjp.getArgs();
//明确调用业务层方法
rtValue = pjp.proceed(args);
return rtValue;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("Logger类中的arountPrintLog方法开始执行");
return rtValue;
}
spring中的环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
6.补充
标签:<aop:pointcut></aop:pointcut>
作用:
可以将通知的pointcut表达式写在外边,就不会让通知标签太长影响阅读
此标签写在<aop:aspect/>标签内部,只能够该切面使用它也可以写在<aop:aspect/>外面,此时所有切面均可使用,但是它要在使用它的切面之前配置,不然会报错
属性:
id:是point唯一标识
expression:pointcut表达式
示例
<aop:aspect>
<aop:pointcut id="pc1" expression="execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())"/>
<aop:before method="beforePrintLog" pointcut-ref="pc1"></aop:before>
</aop:aspect>
二、基于注解的AOP
1.修改配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器的时候要扫描的包-->
<context:component-scan base-package="com.cbw"></context:component-scan>
<!--配置spring开启注解AOP的支持-->
<!-- 写上就支持,不写就不支持,也就是spring默认不支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
2.配置Logger类
- 因为Logger类不是那三层的,所以我们就是用@Component注解
- 使用@Aspect注解,表明这是一个切面类
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") @Aspect public class Logger {
- 用@Before注解前置通知
这5种注解的参数都是pointcut表达式/** * 前置通知 */ @Before("pct1()") public void beforePrintLog(){ System.out.println("前置通知:Logger类中的printLog方法开始执行"); }
- 用@AfterReturning注解后置通知
/**
* 后置通知
*/
@AfterReturning("pct1()")
public void afterReturningPrintLog(){
System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始执行");
}
- 用@AfterThrowing注解异常通知
/**
* 异常通知
*/
@AfterThrowing("pct1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始执行");
}
- 用@After注解最终通知
/**
* 最终通知
*/
@After("pct1()")
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始执行");
}
- 用@Around注解环绕通知
@Around("pct1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
}
- 用@Pointcut注解,提供pointcut表达式,前面通知注解参数就是填写这个方法的名称,()不能掉
@Pointcut("execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())")
private void pct1(){}
- 完整代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(public void com.cbw.service.impl.AccountServiceImpl.saveAccount())")
private void pct1(){}
/**
* 前置通知
*/
@Before("pct1()")
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的printLog方法开始执行");
}
/**
* 后置通知
*/
@AfterReturning("pct1()")
public void afterReturningPrintLog(){
System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始执行");
}
/**
* 异常通知
*/
@AfterThrowing("pct1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始执行");
}
/**
* 最终通知
*/
@After("pct1()")
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始执行");
}
/**
* 环绕通知
*/
@Around("pct1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
//明确调用切入点方法
try {
//得到方法执行所需参数
Object [] args = pjp.getArgs();
System.out.println("前置通知执行了.....");
//明确调用业务层方法
rtValue = pjp.proceed(args);
System.out.println("后置通知执行了.....");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知执行了.....");
throwable.printStackTrace();
}finally {
System.out.println("最终通知执行了.....");
}
System.out.println("Logger类中的arountPrintLog方法开始执行");
return rtValue;
}
}
3.测试
我们首先将环绕通知的注解注释掉,先测试前四种
可是,我们发现,最终通知却在后置通知前面执行,我们试试异常通知
- 在saveAccount方法中加入int i=1/0;
然后发现,异常通知还是在最终通知后面,这是为什么呢?
其实这是基于注解的springaop中确实存在的通知顺序问题,老版本的spring中并没有改良,至少我这个版本是这样的
虽然有这个问题,但是我们还是能使用环绕通知来解决的,环绕通知能够让我们自己决定通知的顺序,我们将前四个通知的注解注释掉,然后把环绕通知注解恢复,再执行saveAccount方法
可以看到,顺序没问题了
4.纯注解方式
//他是一个bean对象
@Component("logger")
//他是一个切面对象
@Aspect
//扫描包
@ComponentScan("com.cbw")
//设置对注解支持
@EnableAspectJAutoProxy
public class Logger {
}
再删除xml文件,别忘了,创建容器的方法修改成ApplicationContext ac = new AnnotationConfigApplicationContext(Logger.class);
执行结果