Spring Boot 使用 @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>
package com.example.demo.Aspect; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.SourceLocation; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.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(* com.example.demo.Aspect.TestController.doNormal(..))") public void pointCut(){} @Before(value = "pointCut()") public void before(JoinPoint joinPoint){ log.info("@Before通知执行"); //获取目标方法参数信息 Object[] args = joinPoint.getArgs(); Arrays.stream(args).forEach(arg->{ // 大大 try { log.info(OBJECT_MAPPER.writeValueAsString(arg)); } catch (JsonProcessingException e) { log.info(arg.toString()); } }); //aop代理对象 Object aThis = joinPoint.getThis(); log.info(aThis.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd //被代理对象 Object target = joinPoint.getTarget(); log.info(target.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd //获取连接点的方法签名对象 Signature signature = joinPoint.getSignature(); log.info(signature.toLongString()); //public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String) log.info(signature.toShortString()); //HelloController.getName(..) log.info(signature.toString()); //String com.xhx.springboot.controller.HelloController.getName(String) //获取方法名 log.info(signature.getName()); //getName //获取声明类型名 log.info(signature.getDeclaringTypeName()); //com.xhx.springboot.controller.HelloController //获取声明类型 方法所在类的class对象 log.info(signature.getDeclaringType().toString()); //class com.xhx.springboot.controller.HelloController //和getDeclaringTypeName()一样 log.info(signature.getDeclaringType().getName());//com.xhx.springboot.controller.HelloController //连接点类型 String kind = joinPoint.getKind(); log.info(kind);//method-execution //返回连接点方法所在类文件中的位置 打印报异常 SourceLocation sourceLocation = joinPoint.getSourceLocation(); log.info(sourceLocation.toString()); //log.info(sourceLocation.getFileName()); //log.info(sourceLocation.getLine()+""); //log.info(sourceLocation.getWithinType().toString()); //class com.xhx.springboot.controller.HelloController ///返回连接点静态部分 JoinPoint.StaticPart staticPart = joinPoint.getStaticPart(); log.info(staticPart.toLongString()); //execution(public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String)) //attributes可以获取request信息 session信息等 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); log.info(request.getRequestURL().toString()); //http://127.0.0.1:8080/hello/getName log.info(request.getRemoteAddr()); //127.0.0.1 log.info(request.getMethod()); //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.info(joinPoint.getSignature().getName()); if(exception instanceof NullPointerException){ log.info("发生了空指针异常!!!!!"); } } @After(value = "pointCut()") public void doAfterAdvice(JoinPoint joinPoint){ log.info("后置通知执行了!"); } /** * 环绕通知: * 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用 * * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 */ @Around(value = "pointCut()") public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ log.info("@Around环绕通知:"+proceedingJoinPoint.getSignature().toString()); Object obj = null; try { obj = proceedingJoinPoint.proceed(); //可以加参数 log.info(obj.toString()); } catch (Throwable throwable) { throwable.printStackTrace(); } log.info("@Around环绕通知执行结束"); return obj; } }
package com.example.demo.Aspect; import lombok.extern.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] o.a.c.c.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] com.example.demo.Aspect.LogAspect : @Around环绕通知:String com.example.demo.Aspect.TestController.doNormal(String,String) 2020-06-05 09:59:54.289 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : @Before通知执行 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : null 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : null 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController@5c7c88bb 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController@5c7c88bb 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : public java.lang.String com.example.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : TestController.doNormal(..) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : String com.example.demo.Aspect.TestController.doNormal(String,String) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : doNormal 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : class com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : method-execution 2020-06-05 09:59:54.297 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@77ad4f8f 2020-06-05 09:59:54.298 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : execution(public java.lang.String com.example.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String)) 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : http://localhost:8080/doNormal 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 0:0:0:0:0:0:0:1 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : GET 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : before通知执行结束 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.TestController : 【执行方法】:doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : @Around环绕通知执行结束 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 后置通知执行了! 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 第一个后置返回通知的返回值:doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.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.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'