SpringBoot过滤器、拦截器、切面之间的区别和执行顺序
SpringBoot过滤器、拦截器、切面之间的区别和执行顺序
相关原理
首先了解一下SpringMVC的执行流程

具体流程如下:
用户发起请求到前端控制器(Controller)
前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。
HandlerMapping返回执行链,包含了2部分内容:① Handler对象、② 拦截器数组
前端处理器通过处理器适配器包装后执行Handler对象。
处理业务逻辑。
Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
将ModelAndView返回给前端控制器。
视图解析器(ViewResolver)返回真正的视图对象(View)。
(此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
返回渲染后的视图(html/json/xml)返回。
给用户产生响应。
核心就是DispatcherServlet核心控制器,我们看源码可知道DispatcherServlet是Servlet的子类
而过滤器、Servlet容器、拦截器、AOP、Controller之间的关系

然后具体执行流程如下:

拦截器和过滤器的区别
拦截器不依赖与servlet容器是SpringMVC自带的,过滤器依赖于Servlet容器。
拦截器是基于java的反射机制的,而过滤器是基于函数回调。
拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
拦截器可以访问controller上下文、值栈里的对象,而过滤器不能访问。
拦截器的preHandle方法在进入controller前执行,而拦截器的postHandle方法在执行完controller业务流程后,在视图解析器解析ModelAndView之前执行,可以操控Controller的ModelAndView内容。而afterCompletion是在视图解析器解析渲染ModelAndView完成之后执行的
过滤器是在服务器启动时就会创建的,只会创建一个实例,常驻内存,也就是说服务器一启动就会执行Filter的init(FilterConfig config)方法.当Filter被移除或服务器正常关闭时,会执行destroy方法
拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
关于这句话的解读是:我们知道拦截器是SprinMVC自带的,而SpringMVC存在Controller层的,而controller层可以访问到service层,service层是不能访问service层的,而过滤器是客户端和服务端之间请求与响应的过滤
过滤器和拦截器触发时机、时间、地方不一样
过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是在servlet处理完后,返回给前端
过滤器包裹住servlet,servlet包裹住拦截器。
代码实现
目录结构
|-aspect |---------TestAspect // 切面 |-config |---------InterceptorConfig // 拦截器配置 |-filter |---------TestFilter1 // 过滤器 |---------TestFilter2 |-interceptor |---------TestInterceptor1 // 拦截器 |---------TestInterceptor2 |-rest |---------TestController |-MySpringApplication
POM依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.6.0</version> </dependency> <!-- AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.6.0</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.80</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> </dependencies>
MySpringApplication
@SpringBootApplication @ServletComponentScan public class MySpringApplication { public static void main(String[] args) { SpringApplication.run(MySpringApplication.class, args); } }
TestController
@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
@GetMapping(value = "/find")
public String findInterfaceList(@RequestParam String userName) {
log.info("查询数据成功!!!!");
return userName;
}
}
过滤器
/** * @author MMDZ * @description TODO 过滤器执行顺序会按照filterName的字母顺序进行 * @WebFilter 指定的过滤器优先级都高于FilterRegistrationBean配置的过滤器 * <p> * 注:这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。 * 这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外, * 我们需在启动类中加注解:@ServletComponetScan,指定扫描的包。 */ @Slf4j @WebFilter(urlPatterns = "/*", filterName = "aaaa") public class TestFilter1 implements Filter { @Override public void init(FilterConfig filterConfig) { log.info("过滤器1初始化!!!!"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info("过滤器1开始!!!!"); long start = System.currentTimeMillis(); filterChain.doFilter(servletRequest, servletResponse); log.info("过滤器1结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } @Override public void destroy() { //当Filter被移除或服务器正常关闭时 log.info("过滤器1销毁!!!!"); } } // ------------------------------------------------------------------------- @Slf4j @WebFilter(urlPatterns = "/*", filterName = "bbbb") public class TestFilter2 implements Filter { @Override public void init(FilterConfig filterConfig) { log.info("过滤器2初始化!!!!"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info("过滤器2开始!!!!"); long start = System.currentTimeMillis(); filterChain.doFilter(servletRequest, servletResponse); log.info("过滤器2结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } @Override public void destroy() { //当Filter被移除或服务器正常关闭时 log.info("过滤器2销毁!!!!"); } }
拦截器&配置
/** * @author MMDZ * @description TODO 拦截器配置: 实现WebMvcConfigurer接口并且重写addInterceptors方法 */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration interceptorRegistration1 = registry.addInterceptor(new TestInterceptor1()); interceptorRegistration1.addPathPatterns("/**"); interceptorRegistration1.order(1); InterceptorRegistration interceptorRegistration2 = registry.addInterceptor(new TestInterceptor2()); interceptorRegistration2.addPathPatterns("/**"); interceptorRegistration2.order(2); } } // ------------------------------------------------------------------------- @Slf4j public class TestInterceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("请求执行前拦截器11111开始!!!!"); long start = System.currentTimeMillis(); log.info("请求执行前拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); return true;//当返回true的时候,才可以执行postHandle/afterCompletion } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("请求执行完成后,拦截器11111开始!!!!"); long start = System.currentTimeMillis(); log.info("请求执行完成后,拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("视图渲染完成后,拦截器11111开始!!!!"); long start = System.currentTimeMillis(); log.info("视图渲染完成后,拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } } // ------------------------------------------------------------------------- @Slf4j public class TestInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("请求执行前拦截器22222开始!!!!"); long start = System.currentTimeMillis(); log.info("请求执行前拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); return true;//当返回true的时候,才可以执行postHandle/afterCompletion } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("请求执行完成后,拦截器22222开始!!!!"); long start = System.currentTimeMillis(); log.info("请求执行完成后,拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("视图渲染完成后,拦截器22222开始!!!!"); long start = System.currentTimeMillis(); log.info("视图渲染完成后,拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms"); } }
aop切面
@Aspect @Slf4j @Component public class TestAspect { /* Pointcut是织入Advice的触发条件 */ @Pointcut("execution(* com.mmdz.spring.mvc.rest..*.*(..))") // rest下的任何方法 public void pointcut() { } @Before("pointcut()") public void beforeControllerMethod(JoinPoint joinPoint) { log.info("前置通知!!!!方法执行前执行"); } @Around("pointcut()") public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String uuIdTrace = UUID.randomUUID().toString().replace("-", ""); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //IP地址 String ipAddr = getRemoteHost(request); // String url = request.getRequestURL().toString(); String requestMethod = request.getMethod(); String requestURI = request.getRequestURI(); String reqParam = preHandle(proceedingJoinPoint, request);//获取请求参数 log.info(uuIdTrace + " 环绕通知,执行方法前,请求源IP:【{}】,请求方法:【{}】,请求URL:【{}】,请求参数:【{}】", ipAddr, requestMethod, requestURI, reqParam); long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed();// 执行方法,获取响应信息 String respParam = postHandle(result); log.info(uuIdTrace + " 环绕通知,执行方法后,请求源IP:【{}】,请求URL:【{}】,返回参数:【{}】,执行耗时 : {}", ipAddr, requestURI, respParam, (System.currentTimeMillis() - start) + "ms"); return result; } @After("pointcut()") public void afterControllerMethod(JoinPoint joinPoint) { log.info("后置通知:方法执行完后执行"); } @AfterReturning(returning = "result", pointcut = "pointcut()") public void doAfterReturnint(Object result) { log.info("后置通知,方法执行完后执行,响应信息为:{}", JSONObject.toJSONString(result)); } /** * 入参数据 * * @param joinPoint * @param request * @return */ private String preHandle(ProceedingJoinPoint joinPoint, HttpServletRequest request) { try { Map<String, Object> fieldsName = getFieldsName(joinPoint); return JSON.toJSONString(fieldsName); } catch (Exception e) { log.warn("切面获取请求参数出现异常", e); return null; } } private Map<String, Object> getFieldsName(JoinPoint joinPoint) throws Exception { String classType = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); // 参数值 Object[] args = joinPoint.getArgs(); Class<?>[] classes = new Class[args.length]; for (int k = 0; k < args.length; k++) { // 对于接受参数中含有MultipartFile,ServletRequest,ServletResponse类型的特殊处理,我这里是直接返回了null。(如果不对这三种类型判断,会报异常) if (args[k] instanceof MultipartFile || args[k] instanceof ServletRequest || args[k] instanceof ServletResponse) { return null; } if (!args[k].getClass().isPrimitive()) { // 当方法参数是基础类型,但是获取到的是封装类型的就需要转化成基础类型 // String result = args[k].getClass().getName(); // Class s = map.get(result); // 当方法参数是封装类型 Class s = args[k].getClass(); classes[k] = s == null ? args[k].getClass() : s; } } ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); // 获取指定的方法,第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型 Method method = Class.forName(classType).getMethod(methodName, classes); // 参数名 String[] parameterNames = pnd.getParameterNames(method); // 通过map封装参数和参数值 HashMap<String, Object> paramMap = new HashMap(); for (int i = 0; i < parameterNames.length; i++) { paramMap.put(parameterNames[i], args[i]); } return paramMap; } /** * 返回数据 * * @param retVal * @return */ private String postHandle(Object retVal) { if (null == retVal) { return ""; } return JSON.toJSONString(retVal); } /** * 获取目标主机的ip * * @param request * @return */ private String getRemoteHost(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } }
测试
浏览器访问

控制台输出

总结
执行顺序
当过滤器、拦截器、Aop都用于一个请求路径的接口方法时,执行顺序为
过滤器--->拦截器--->AOP
作用
过滤器的主要作用
用户访问权限处理
设置字符集乱码处理
过滤敏感词汇、压缩响应信息
拦截器的主要作用
只能拦截请求,可以访问上下文等对象,功能强大,一个请求可多次拦截。
用户访问权限处理
登记日志
AOP的主要作用
日志记录
性能统计
安全控制
事务处理
异常处理
只能应用于由 Spring 容器管理的 bean
一般情况下,数据被过滤拦截的时机越早对服务的性能影响越小,在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是AOP

浙公网安备 33010602011771号