spring boot 拦截的方式
- 过滤器filter:
可以获取http、http请求和响应,但无法获取与spring框架相关的信息,如哪个control处理,哪个方法处理,有哪些参数,这些都是无法获取的。主要用于内容上的过滤,敏感字替换成*等,也可用于非登入状态的非法请求过滤。 - 拦截器interceptor:
除了获取http、http请求和响应对象,还可以获取请求的类名、方法名,但拦截器无法获取请求参数的值,从DispatcherServlet类源码分析。主要用于对公共的一些拦截获取,例如请求的IP 地址,IP黑白名单里的过过滤,非登入状态的接口请求拦截。 - 切面拦截Aspect:
能获取到方法请求的参数,方法名,以及方法返回的json数据,更多的是用于数据的处理,比如对操作进行记录,修改,新建,查询,审批等操作记录进行处理统计。对返回的json中的一些特殊数据,比如字典值替换成对应的数据,避免前端转化,等等。
执行顺序
正常情况:过滤器、拦截器、切片,
异常报错:切片、ControllerAdvice注解类、拦截器、过滤器
切片的使用:
- 相关注解
1.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){
//自己写逻辑吧
}
}