SpringBoot使⽤@Aspect注解实现AOP
AOP(Aspect Oriented Programming,⾯向切⾯编程)是通过预编译⽅式和运⾏期动态代理实现程序功能的统⼀维护的⼀种技术。AOP是OOP的延续,是软件开发中的⼀个热点,也是Spring框架中的⼀个重要内容,是函数式编程的⼀种衍⽣范型。利⽤AOP可以对业务逻辑的各个部分进⾏隔离,从⽽使得业务逻辑各部分之间的耦合度降低,提⾼程序的可重⽤性,同时提⾼了开发的效率。
在Spring AOP中业务逻辑仅仅只关注业务本⾝,将⽇志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,从⽽在改变这些⾏为的时候不影响业务逻辑的代码。
相关注解介绍:
注解作⽤
@Aspect把当前类标识为⼀个切⾯
@Pointcut Pointcut是织⼊Advice的触发条件。每个Pointcut的定义包括2部分,⼀是表达式,⼆是⽅法签名。⽅法签名必须是public及void 型。可以将Pointcut中的⽅法看作是⼀个被Advice引⽤的助记符,因为表达式不直观,因此我们可以通过⽅法签名的⽅式为此表达式命名。因此Pointcut中的⽅法只需要⽅法签名,⽽不需要在⽅法体内编写实际代码。
@Around环绕增强,⽬标⽅法执⾏前后分别执⾏⼀些代码
@AfterReturning返回增强,⽬标⽅法正常执⾏完毕时执⾏
@Before前置增强,⽬标⽅法执⾏之前执⾏
@AfterThrowing异常抛出增强,⽬标⽅法发⽣异常的时候执⾏
@After后置增强,不管是抛出异常或者正常退出都会执⾏
⼀、添加依赖
<!--Spring AOP 切⾯模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
⼆、编写增强类
ample.demo.Aspect;
import com.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.flect.SourceLocation;
import org.springframework.stereotype.Component;
import org.t.request.RequestContextHolder;
import org.t.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Component
@Aspect
@Slf4j
public class LogAspect {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Pointcut("execution(* ample.demo.Aspect.TestController.doNormal(..))")
public void pointCut(){}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
log.info("@Before通知执⾏");
//获取⽬标⽅法参数信息
Object[] args = Args();
Arrays.stream(args).forEach(arg->{  // ⼤⼤
try {
log.info(OBJECT_MAPPER.writeValueAsString(arg));
} catch (JsonProcessingException e) {
log.String());
}
});
//aop代理对象
Object aThis = This();
log.String()); //com.ller.HelloController@69fbbcdd
//被代理对象
Object target = Target();
log.String()); //com.ller.HelloController@69fbbcdd
//获取连接点的⽅法签名对象
Signature signature = Signature();
log.LongString()); //public java.lang.String com.Name(java.lang.String)            log.ShortString()); //Name(..)
log.String()); //String com.Name(String)
//获取⽅法名
log.Name()); //getName
//获取声明类型名
log.DeclaringTypeName()); //com.ller.HelloController
//获取声明类型⽅法所在类的class对象
log.DeclaringType().toString()); //class com.ller.HelloController
//和getDeclaringTypeName()⼀样
log.DeclaringType().getName());//com.ller.HelloController
//连接点类型
String kind = Kind();
log.info(kind);//method-execution
//返回连接点⽅法所在类⽂件中的位置打印报异常
SourceLocation sourceLocation = SourceLocation();
log.String());
//log.FileName());
//log.Line()+"");
//log.WithinType().toString()); //class com.ller.HelloController
///返回连接点静态部分
JoinPoint.StaticPart staticPart = StaticPart();
log.LongString());  //execution(public java.lang.String com.Name(java.lang.String))
//attributes可以获取request信息 session信息等
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestAtt
ributes();
HttpServletRequest request = Request();
log.RequestURL().toString()); //127.0.0.1:8080/hello/getName
log.RemoteAddr()); //127.0.0.1
log.Method()); //GET
log.info("before通知执⾏结束");
}
/**
* 后置返回
*      如果第⼀个参数为JoinPoint,则第⼆个参数为返回值的信息
*      如果第⼀个参数不为JoinPoint,则第⼀个参数为returning中对应的参数
* returning:限定了只有⽬标⽅法返回值与通知⽅法参数类型匹配时才能执⾏后置返回通知,否则不执⾏,
*            参数为Object类型将匹配任何⽬标返回值
*/
@AfterReturning(value = "pointCut()",returning = "result")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object result){
log.info("第⼀个后置返回通知的返回值:"+result);
}
@AfterReturning(value = "pointCut()",returning = "result",argNames = "result")
public void doAfterReturningAdvice2(String result){
log.info("第⼆个后置返回通知的返回值:"+result);
}
//第⼀个后置返回通知的返回值:姓名是⼤⼤
//第⼆个后置返回通知的返回值:姓名是⼤⼤
//第⼀个后置返回通知的返回值:{name=⼩⼩, id=1}
/**
* 后置异常通知
*  定义⼀个名字,该名字⽤于匹配通知实现⽅法的⼀个参数名,当⽬标⽅法抛出异常返回后,将把⽬标⽅法抛出的异常传给通知⽅法;
*  throwing:限定了只有⽬标⽅法抛出的异常与通知⽅法相应参数异常类型时才能执⾏后置异常通知,否则不执⾏,
*            对于throwing对应的通知⽅法参数为Throwable类型将匹配任何异常。
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
log.Signature().getName());
if(exception instanceof NullPointerException){
log.info("发⽣了空指针异常");
}
}
@After(value = "pointCut()")
public void doAfterAdvice(JoinPoint joinPoint){
springboot和过滤器log.info("后置通知执⾏了!");
}
/**
* 环绕通知:
*  注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运⾏,不要同时使⽤
*
*  环绕通知⾮常强⼤,可以决定⽬标⽅法是否执⾏,什么时候执⾏,执⾏时是否需要替换⽅法参数,执⾏完毕是否需要替换返回值。
*  环绕通知第⼀个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = "pointCut()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
log.info("@Around环绕通知:"+Signature().toString());
Object obj = null;
try {
obj = proceedingJoinPoint.proceed(); //可以加参数
log.String());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info("@Around环绕通知执⾏结束");
return obj;
}
}
三、Controller
ample.demo.Aspect;
slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
@RequestMapping("/doNormal")
public String doNormal(String name, String age) {
log.info("【执⾏⽅法】:doNormal");
return "doNormal";
}
@RequestMapping("/doWithException")
public String doWithException(String name, String age) {
log.info("【执⾏⽅法】:doWithException");
int a = 1 / 0;
return "doWithException";
}
}
启动程序,当访问doNormal⽅法时,⽇志输出如下:
2020-06-05 09:59:54.256  INFO 27344 --- [nio-8080-exec-1] C.[Tomcat].[localhost].[/]      : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-05 09:59:54.257  INFO 27344 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-06-05 09:59:54.262  INFO 27344 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
2020-06-05 09:59:54.289  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : @Around环绕通知:ample.demo.Aspect.TestController.doNormal(String,String) 2020-06-05 09:59:54.289  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : @Before通知执⾏
2020-06-05 09:59:54.295  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : null
2020-06-05 09:59:54.295  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : null
2020-06-05 09:59:54.295  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : ample.demo.Aspect.TestController@5c7c88bb
2020-06-05 09:59:54.295  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : ample.demo.Aspect.TestController@5c7c88bb
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : public java.lang.ample.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String)
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : TestController.doNormal(..)
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : ample.demo.Aspect.TestController.doNormal(String,String)
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : doNormal
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : ample.demo.Aspect.TestController
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect     
  : ample.demo.Aspect.TestController
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : ample.demo.Aspect.TestController
2020-06-05 09:59:54.296  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : method-execution
2020-06-05 09:59:54.297  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@77ad4f8f
2020-06-05 09:59:54.298  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : execution(public java.lang.ample.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String)) 2020-06-05 09:59:54.299  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : localhost:8080/doNormal
2020-06-05 09:59:54.299  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : 0:0:0:0:0:0:0:1
2020-06-05 09:59:54.299  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : GET
2020-06-05 09:59:54.299  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : before通知执⾏结束
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.TestController  : 【执⾏⽅法】:doNormal
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : doNormal
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : @Around环绕通知执⾏结束
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : 后置通知执⾏了!
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : 第⼀个后置返回通知的返回值:doNormal
2020-06-05 09:59:54.307  INFO 27344 --- [nio-8080-exec-1] ample.demo.Aspect.LogAspect        : 第⼆个后置返回通知的返回值:doNormal
Disconnected from the target VM, address: '127.0.0.1:56402', transport: 'socket'
2020-06-05 10:03:39.155  INFO 27344 --- [extShutdownHook] o.urrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'