Spring有AOP和IOC两大特性,本篇主要讲AOP的实战使用。主要从以下的例子来说明aop的使用。
AOP实现全局日志记录
/**
* 控制日志aop记录类
* author zy
* createdate 2022-02-16
*/
@Aspect
@Configuration
public class ControllerLogAspect {
private static final Log logger = LogFactory.getLog(ControllerLogAspect.class);
//抽取公共的切入点表达式,此表达式标识其作用域作用于controller层下的代码
@Pointcut("execution(public String com.zy.main.controller.*.*(..))")
public void pointCut() {
}
;
/**
* 在方法执行前
* @param joinPoint
*/
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
//获取参数
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature().getDeclaringTypeName()类名
//joinPoint.getSignature().getName() 获取到请求方法名
logger.info(joinPoint.getSignature().getDeclaringTypeName()+joinPoint.getSignature().getName() +"运行。。。@Before:参数列表是"+ Arrays.asList(args));
}
/**
* 在方法执行后执行
* @param joinPoint
*/
@After("com.zy.main.aspect.ControllerLogAspect.pointCut()")
public void logEnd(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature().getDeclaringTypeName()类名
//joinPoint.getSignature().getName() 获取到请求方法名
logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@After:参数列表是"+ Arrays.asList(args));
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature().getDeclaringTypeName()类名
//joinPoint.getSignature().getName() 获取到请求方法名
logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@AfterReturning:参数列表是"+ Arrays.asList(args)+"正常返回。。。@AfterReturning:运行结果:{" + result + "}");
}
/**
* 记录异常
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature().getDeclaringTypeName()类名
//joinPoint.getSignature().getName() 获取到请求方法名
logger.error(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@AfterThrowing:参数列表是"+ Arrays.asList(args),exception);
}
}
- @Before
此注解作用于方法执行之前,我们可以通过JoinPoint.getArgs()方法来获取到方法执行前接收到的参数。 - @After
此注解作用于方法执行之后 - @AfterReturning
此注解作用于方法返回之后,通过returning = "result"可以获取到方法的返回值 - @AfterThrowing
此注解作用于,异常抛出后 - @Around
环绕增强,这个会在后面仔细介绍。
下面用代码来展示各个注解的执行顺序,测试类
/**
* auhthor zy
* date 20220518
*/
@RestController
public class TestController {
private static final Log logger = LogFactory.getLog(TestController.class);
@RequestMapping("getSxbm")
public String getSxbm(HttpServletResponse response1, String jsonParam,String param) throws Exception {
return "ok";
}
}
20:22:17.330 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestControllergetSxbm运行。。。@Before:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]
20:22:17.335 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@After:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]
20:22:17.335 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@AfterReturning:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]正常返回。。。@AfterReturning:运行结果:{ok}
执行顺序依次为Before,After,AfterReturning,可以看见这三个注解中均获取到了方法的参数,同时AfterReturning也获取到了方法的反参。已经达到了日志记录的功能。
下面测试这种写法的异常捕捉
20:50:38.573 [http-nio-8070-exec-2] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
20:50:38.583 [http-nio-8070-exec-2] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 10 ms
20:50:38.617 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestControllergetSxbm运行。。。@Before:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
20:50:38.622 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@After:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
20:50:38.622 [http-nio-8070-exec-2] ERROR com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@AfterThrowing:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
java.lang.ArithmeticException: / by zero
可以看见@AfterThrowing 成功捕捉到了这次异常。下面来讲还没用到的around。
around 环绕增强,上面实现的所有日志记录功能都可以通过around来实现
around版日志记录实现
代码实现
/**
* 控制日志aop记录类
* author zy
* create date 2022-02-16
*/
@Aspect
@Configuration
public class ControllerLogAspect {
private static final Log logger = LogFactory.getLog(ControllerLogAspect.class);
/**抽取公共的切入点表达式
*
*/
@Pointcut("execution(public String com.zy.main.controller.*.*(..))")
public void pointCut() {
}
@Around("com.zy.main.aspect.ControllerLogAspect.pointCut()")
public Object logEnd(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
logger.info("入参为"+Arrays.asList(args));
Object result = new Object();
// logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"参数列表是"+ Arrays.asList(args));
try{
result = joinPoint.proceed();
logger.info("反参为"+result.toString());
}catch (Exception e){
logger.error("Exception",e);
} catch (Throwable throwable) {
logger.error("throwable",throwable);
}
return result.toString();
}
}
照例还是通过代码测试返回结果
21:53:14.864 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - 入参为[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@1e42dee5, {"itemCode":"0103022000"}, null]
21:53:14.868 [http-nio-8070-exec-2] INFO com.zy.main.aspect.ControllerLogAspect - 反参为ok
可以看见,around同样也可以不修改业务方法但完成业务日志的记录。细心的同学可以注意到,After等注解使用的对象为JoinPoint,而我在around注解中使用的则是ProceedingJoinPoint。
从两者的源码来说前者是后者的父类
ProceedingJoinPoint类中,包含两个新方法
proceed()与proceed(object)方法,proceed这个方法的作用是通知目标方法执行,同时返回结果值。前者是不更改原始传参,后者是重写请求传入的参数,再传入方法中进行执行。
本次只是用作日志功能,所以不对原始参数进行重写。
从执行流程下around的执行流程,可以涵盖before,after,afterthrowing等注解的功能
从开发角度来说,AOP可以做到不修改原有的业务代码就可以增加通用的功能。而从AOP里面的注解来说,个人推荐使用around。但具体情况具体分析,大家根据自己的情况进行使用。下篇带大家了解自定义注解结合AOP的使用。