springboot的切面应用方式(注解Aspect)
作者:你是猪,
spring boot 拦截的方式
1.过滤器filter
可以获取http、http请求和响应,但无法获取与spring框架相关的信息,如哪个control处理,哪个方法处理,有哪些参数,这些都是无法获取的。
主要用于内容上的过滤,敏感字替换成*等,也可用于非登入状态的非法请求过滤。
2.拦截器interceptor
除了获取http、http请求和响应对象,还可以获取请求的类名、方法名,但拦截器无法获取请求参数的值,从DispatcherServlet类源码分析。
主要用于对公共的一些拦截获取,例如请求的IP 地址,IP黑白名单里的过过滤,非登入状态的接口请求拦截。
3.切面拦截Aspect
能获取到方法请求的参数,方法名,以及方法返回的json数据,更多的是用于数据的处理,比如对操作进行记录,修改,新建,查询,审批等操作记录进行处理统计。
对返回的json中的一些特殊数据,比如字典值替换成对应的数据,避免前端转化,等等。
执行顺序
- 正常情况:过滤器、拦截器、切片,
- 异常报错:切片、ControllerAdvice注解类、拦截器、过滤器
切片的使用
相关注解
(1)@Pointcut 注解:
指定一个切点,定义需要拦截的东西,这里介绍两个常 用的表达式:一个是使用 execution(),另一个是使用 annotation()。
- execution表达式:
以 execution(* com.mutest.controller..*.*(..))) 表达式为例:
- 第一个 * 号的位置:表示返回值类型,* 表示所有类型。包名:表示需要拦截的包名,后面的两个句*斜体样式*点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
- 第二个 * *号的位置:表示类名,** 表示所有类。
- (..):*这个星号表示方法名*,* 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
annotation() 表达式:
annotation() 方式是针对某个注解来定义切点,其中注解包括GetMapping等
(2)@Around注解:
用于修饰Around增强处理,Around增强处理非常强大,表现在:
@Around可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法。这个特性的实现在于,调用 ProceedingJoinPoint参数的procedd()方法才会执行目标方法。
@Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
(3)@Before 注解:
指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,可以通过参数JointPoint 来获取一些有用的信息,可以用它来获取一个签名,利用签名可以获取请求的包名、方法名,包括参数(通过joinPoint. getArgs() 获取)等。
(4)@After注解:
和before注解相对应的注解,同样可以进行一些日志处理等
(5)@AfterReturning 注解:
和@After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理。
对返回的json字符串进行处理。
(6)@@AfterThrowing注解:
当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。
示例
import com.alibaba.fastjson.JSONObject; import com.cmhit.crm.constants.FunctionEnum; import com.cmhit.crm.service.OperateLogService; import com.cmhit.crm.utils.JsonUtil; import com.cmhit.crm.vo.operlog.OperateLogCreateReqVO; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Objects; @Slf4j @Aspect @Component public class OperateLogAspect { //操作日志service @Autowired private OperateLogService operateLogService; //操作日志实体 private OperateLogCreateReqVO operateLog = new OperateLogCreateReqVO(); // 定义一个切入点 @Pointcut("execution(* com.cmhit.crm.controller.*.*(..))") public void operlog(){ //这里面不要写代码,不会执行的 } // 前置通知 @Before(value = "operlog()") public void before(JoinPoint jp) { //方法名获取 String name = jp.getSignature().getName(); //方法参数获取 Object[] args = jp.getArgs(); //设置操作日志 setOperateLogType(name,args); log.debug("{}方法开始执行...开始设置设置操作日志的操作类型",name); } // 后置通知 @After(value = "operlog()") public void after(JoinPoint jp) { String name = jp.getSignature().getName(); log.debug("{}方法执行结束...",name); } // 返回通知 @AfterReturning(value = "operlog()") public void afterReturning(JoinPoint jp) { String name = jp.getSignature().getName(); if (Objects.nonNull(operateLog.getSourceId())) { operateLog.setOperationResult("操作成功!"); operateLogService.insertOperateLog(operateLog); } log.debug("{}方法执行成功",name); } // 异常通知 @AfterThrowing(value = "operlog()", throwing = "e") public void afterThrowing(JoinPoint jp, Exception e) { String name = jp.getSignature().getName(); if (Objects.nonNull(operateLog.getSourceId())){ operateLog.setOperationResult(e.getMessage()); operateLogService.insertOperateLog(operateLog); } log.debug("{}方法抛异常,异常是{}",name , e.getMessage()); } // 环绕通知 @Around("operlog()") public Object around(ProceedingJoinPoint pjp) throws Throwable { String name = pjp.getSignature().getName(); // 统计方法执行时间 long start = System.currentTimeMillis(); Object result = pjp.proceed(); long end = System.currentTimeMillis(); System.out.println(name + "方法执行时间为:" + (end - start) + " ms"); return result; } private void setOperateLogType(String name,Object[] args){ //公司的业务逻辑,这里建立使用自己的 } } //参数处理方法 private void dealGetinfoArgs(Object[] args){ //自己写逻辑吧 } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。