让我们来面向切面编程吧,我们可以用AOP将某个共用的方法抽离出来,并在IOC容器中统一管理,而在Spring中是无法省略配置的,在Spring Boot中则完全不用,非常方便。下面以"记录接口的访问时长"功能场景来看看如何在 springboot 中使用AOP。
添加 spring-boot 的 aop 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
编写切面类和切面逻辑
@Aspect @Component public class HttpRequestAspect { private static final Logger log = LoggerFactory.getLogger(HttpRequestAspect.class); public static long startTime; public static long endTime; /*@PointCut注解表示表示横切点,哪些方法需要被横切*/ /*切点表达式*/ @Pointcut("execution(public * com.simons.cn.springbootdemo.controller.*.*(..))") /*切点签名*/ public void print() { } /*@Before注解表示在具体的方法之前执行*/ @Before("print()") public void before(JoinPoint joinPoint) { log.info("前置切面before……"); startTime = System.currentTimeMillis(); ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); String requestURI = request.getRequestURI(); String remoteAddr = request.getRemoteAddr(); //这个方法取客户端ip"不够好" String requestMethod = request.getMethod(); String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName(); String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); log.info("请求url=" + requestURI + ",客户端ip=" + remoteAddr + ",请求方式=" + requestMethod + ",请求的类名=" + declaringTypeName + ",方法名=" + methodName + ",入参=" + args); } /*@After注解表示在方法执行之后执行*/ @After("print()") public void after() { endTime = System.currentTimeMillis() - startTime; log.info("后置切面after……"); } /*@AfterReturning注解用于获取方法的返回值*/ @AfterReturning(pointcut = "print()", returning = "object") public void getAfterReturn(Object object) { log.info("本次接口耗时={}ms", endTime); log.info("afterReturning={}", object.toString()); } }
1.可以看到我把@PointCut(×××)给单独拎出来了,省的在@Before、@After、@AfterReturn 注解中重复写,而是直接用类似于@Before(value="print()")代替,减少重复性代码;
2.execution(public * com.simons.cn.springbootdemo.controller.*.*(..)) 表示 com.simons.cn.springbootdemo.controller 包下的所有类中的所有方法,".."表示所有方法中的参数不限个数;
编写测试类测试下
@GetMapping(value = "/index") @ResponseBody public String index() { ActivitySystemVariable systemVariable = activitySystemVariableMapper.selectByPrimaryKey(258); return systemVariable.toString(); }
日志输出结果,ok
2018-06-28 11:21:10.939 INFO 10696 --- [nio-8888-exec-8] c.s.cn.springbootdemo.HttpRequestAspect : 前置切面before…… 2018-06-28 11:21:10.939 INFO 10696 --- [nio-8888-exec-8] c.s.cn.springbootdemo.HttpRequestAspect : 请求url=/index,客户端ip=0:0:0:0:0:0:0:1,请求方式=GET,请求的类名=com.simons.cn.springbootdemo.controller.IndexController,方法名=index,入参=[L<a href="https://www.miaoroom.com/tag/java" data-toggle="tooltip" title="查看更多关于 java 的文章" target="_blank">java</a>.lang.Object;@1efabee 2018-06-28 11:21:10.951 INFO 10696 --- [nio-8888-exec-8] c.s.cn.springbootdemo.HttpRequestAspect : 后置切面after…… 2018-06-28 11:21:10.951 INFO 10696 --- [nio-8888-exec-8] c.s.cn.springbootdemo.HttpRequestAspect : 本次接口耗时=12ms 2018-06-28 11:21:10.951 INFO 10696 --- [nio-8888-exec-8] c.s.cn.springbootdemo.HttpRequestAspect : afterReturning=ActivitySystemVariable{id=258, name='simonsfan测试', svKey='simons-key', value='simons-value', memo='simonsfan测试', typeCode='', cataLog=0, createTime=2018-06-28 11:18:19.0}