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; } }
参数详解如下:
- @Pointcut可以定义切点,定义之后,其他地方使用同样的切点就不需要再写execution,直接写切点的方法名即可。
- @Before是在被切方法调用前执行,可以获取到被切方法的参数。
- @After是在被切方法执行后执行,无法获取到被切方法的返回值。
- @AfterReturning是被被切方法执行后执行,可以获取到被切方法的返回值。
- @Around是环绕被切方法,记住需要调用joinPoint.proceed()执行被切方法,如果有返回值,注意return 返回值。
- @AfterThrowing: 异常通知, 在方法抛出异常之后执行。
- 如果切面方法外有事务,切面执行的方法也是会回滚的。
运行结果
测试中,我调用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.*(..))
- 切面表达式是从execution开始的,()内是表达式主体。
- 第一个号:表示返回类型,号表示所有的类型。
- com.cff.springbootwork.aspectj.service.BusinessService.*表示匹配该类的所有方法。
- (..)表示参数个数任意。
- 多个execution可以合并,可以使用||、&&等进行连接。
最后补充一点小知识:
AspectJ 支持 5 种类型的通知注解
1)@Before: 前置通知:在方法执行之前执行的通知。
2)@After: 后置通知, 在方法执行之后执行 , 即方法返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止。
3)@AfterRunning: 返回通知, 在方法返回结果之后执行。ps:无论方法是正常返回还是抛出异常, 后置通知都会执行. 如果只想在方法返回的时候记录日志, 应使用返回通知代替后置通知。
4)@AfterThrowing: 异常通知, 在方法抛出异常之后。
5)@Around: 环绕通知, 围绕着方法执行(即方法前后都有执行)。环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点。