1:引入maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2:Service层(被切面的业务逻辑层)
这里简单写一个业务逻辑,内容不是重点,重点是怎么切面。
BusinessService:
@Service public class BusinessService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public User getIpInfo(HttpServletRequest request, int type) { logger.info("进入getIpInfo方法。"); User user = new User(); user.setName(request.getContextPath()); user.setId(request.getContentLengthLong()); return user; } public Integer getTestInfo(int type) { logger.info("进入getTestInfo方法。"); return type; } }
Aspect切面
有了上面的Service,可以直接使用@Component@Aspect来写切面逻辑。
OperateLogAspect:
package com.springboot.study.aspjectj; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * @Author: guodong * @Date: 2021/6/29 16:13 * @Version: 1.0 * @Description: */ @Component @Aspect public class OperateLogAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(* com.springboot.study.service.BusinessService.*(..)) && !execution(* com.springboot.study.service.BusinessService.getTest*(..)) ") private void log() { } @Before("log()") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); joinPoint.getArgs(); logger.info("方法为:" + methodName + ", 这是一个前置测试 "); } @After("execution(* com.springboot.study.service.BusinessService.*(..))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); logger.info("方法为:" + methodName + ", 这是一个后置测试 "); } @Around("execution(* com.springboot.study.service.BusinessService.getTestInfo(..))") public void test(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); logger.info("方法为:" + methodName + ", 测水水水水水水水水水水 "); } @AfterReturning(value = "log()", returning = "result") public void afterReturning(JoinPoint point, Object result) { String methodName = point.getSignature().getName(); logger.info("方法为:" + methodName + ",目标方法执行结果为:" + result); } @Around("log()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { logger.info("请求参数:{}", joinPoint.getArgs()); Object[] arr = joinPoint.getArgs(); Integer type = (Integer) arr[1]; logger.info("请求类型:{}", type); long startTime = System.currentTimeMillis(); Object obj = joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; logger.info("执行时间:{}", timeTaken); return obj; } }
参数详解如下:
运行结果
测试中,我调用BusinessService的getIpInfo方法。
切面测试结果:
2021-06-29 16:17:07.450 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 请求参数:org.apache.catalina.connector.RequestFacade@71268e04 2021-06-29 16:17:07.452 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 请求类型:0 2021-06-29 16:17:07.454 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 方法为:getIpInfo, 这是一个前置测试 2021-06-29 16:17:07.462 INFO 13176 --- [nio-8080-exec-1] c.s.study.service.BusinessService : 进入getIpInfo方法。 2021-06-29 16:17:07.463 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 执行时间:11 2021-06-29 16:17:07.463 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 方法为:getIpInfo, 这是一个后置测试 2021-06-29 16:17:07.463 INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect : 方法为:getIpInfo,目标方法执行结果为:User(a=0, id=-1, name=, age=null, result=null)
切面表达式浅谈
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
?表示可省略,
修饰符模式如public、protected等;
返回类型模式表示方法返回类型;
方法名模式表示类+方法;
参数模式表示参数;
异常模式表示抛出的异常;
表达式中,可以使用*来代表任意字符,用..来表示任意个参数。
示例:
execution(* com.cff.springbootwork.aspectj.service.BusinessService.*(..))
最后补充一点小知识:
AspectJ 支持 5 种类型的通知注解
1)@Before: 前置通知:在方法执行之前执行的通知。
2)@After: 后置通知, 在方法执行之后执行 , 即方法返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止。
3)@AfterRunning: 返回通知, 在方法返回结果之后执行。ps:无论方法是正常返回还是抛出异常, 后置通知都会执行. 如果只想在方法返回的时候记录日志, 应使用返回通知代替后置通知。
4)@AfterThrowing: 异常通知, 在方法抛出异常之后。
5)@Around: 环绕通知, 围绕着方法执行(即方法前后都有执行)。环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点。