声明:参考链接https://blog.csdn.net/qq_21033663/article/details/73137207
环境说明:Windows10、IntelliJ IDEA、SpringBoot
准备工作:在pom.xml中引入依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
注:下面的实例代码中还涉及到阿里的fastjson依赖,也需要引入;这里不再给出。
现有Controller:
编写AOP:
通过表达式(模糊)匹配来指定切点:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* AOP切面
*
* 以下几个增强的执行顺序是:
* 1.aroundAdvice(.proceed();语句之前的代码)
* 2.beforeAdvice
* 3.被增强方法
* 4.aroundAdvice(.proceed();语句之后的代码)
* 5.afterAdvice
* 6.afterReturningAdvice
* 7.afterThrowingAdvice(有异常时才会走,无异常不会走此方法)
*
* 注: 当被增强方法 或 afterAdvice 或 afterReturningAdvice抛出异常时,会被afterThrowingAdvice
* 捕获到异常,进而短路走 afterThrowingAdvice方法
*
* @author JustryDeng
* @date 2018/12/17 20:25
*/
@Aspect
@Configuration
public class AopConfig {
/**
* 将表达式【* com.aspire.controller.AopController.*(..))】所匹配的所有方法标记为切点,
* 切点名为 excudeAdvice()
*
* 注:execution里的表达式所涉及到的类名(除了基本类以外),其它的要用全类名;干脆不管是不
* 是基础类,都推荐使用全类名
*
* @author JustryDeng
* @date 2018/12/18 13:43
*/
@Pointcut("execution(* com.aspire.controller.AopController.*(..))")
public void excudeAdvice() {
}
/**
* 切点excudeAdvice()的前置增强方法
*
* @author JustryDeng
* @date 2018/12/18 13:47
*/
@Before(value = "excudeAdvice()")
public void beforeAdvice(JoinPoint joinPoint) {
Object[] paramArray = joinPoint.getArgs();
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " -> beforeAdvice获取到了被增强方法的参数了,为:" + paramArray);
}
/**
* 切点excudeAdvice()的后增强方法
*
* @author JustryDeng
* @date 2018/12/18 13:47
*/
@After("excudeAdvice()")
public void afterAdvice() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " -> 后置增强afterAdvice执行了");
}
/**
* 切点excudeAdvice()的后增强方法
*
* 注:当被增强方法 或 afterAdvice正常执行时,才会走此方法
* 注: returning指明获取到的(环绕增强返回的)返回值
*
* @author JustryDeng
* @date 2018/12/18 13:47
*/
@AfterReturning(value = "excudeAdvice()", returning = "map")
public void afterReturningAdvice(Map<String, Object> map) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " -> afterReturningAdvice获得了返回结果 map -> " + map);
}
/**
* 当被增强方法 或 afterAdvice 或 afterReturningAdvice抛出异常时,会被afterThrowingAdvice
* 捕获到异常,进而短路走 afterThrowingAdvice方法
*
* @author JustryDeng
* @date 2018/12/18 13:57
*/
@AfterThrowing(value = "excudeAdvice()", throwing ="ex")
public void afterThrowingAdvice(Exception ex) {
System.out.println("AfterThrowing捕获到了 --->" + ex);
}
/**
* 环绕增强 会在 被增强方法执行完毕后 第一个执行,
* 所以在绝大多数时候,我们都直接返回thisJoinPoint.proceed();的返回值;
* 如果此方法返回null,那么@AfterReturning方法获取到的返回值 就会是null
*/
@Around("excudeAdvice()")
public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint) {
String threadName = Thread.currentThread().getName();
Object obj = null;
System.err.println(threadName + " -> 环绕增强aroundAdvice --> before proceed()执行了");
try {
// 执行被增强方法,并获取到返回值
// 类似于 过滤器的chain.doFilter(req,resp)方法
obj = thisJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.err.println(threadName + " -> 环绕增强aroundAdvice --> after proceed()执行了");
return obj;
}
}
测试一下:
以debug模式启动项目,使用postman访问测试;依次放行断点,控制台输出:
注:之所以要以debug方式运行访问,来查看控制台输出;是因为如果直接运行访问的话,可能控制台打印出的就不是真实的
运行顺序,这是因为CPU会对无关的语句进行重排序。
通过注解来指定切点:
第一步:自定义一个注解
注:这是一个标志性的注解,因此不需要任何属性。
第二步:在要被增强的方法上使用该注解
第三步:定义切点时,使用注解来定义
注:此时即为:被@annotation(com.aspire.annotation.AdviceOne注解标注的方法,作为切点excudeAdvice()。
注:相比起使用execution来匹配方法,将匹配上的方法作为节点的方式;使用注解@annotation来标记方法的方式更为灵活。
第四步:编写增强器
注:同上,略。
第五步:使用测试(测试方式同上),控制台输出:
由此可见,这几个增强器的执行顺序依次为: