当前位置:   article > 正文

spring过滤器Filter 、 拦截器Interceptor 、 切片Aspect 详解_spring filter

spring filter

springboot 过滤器Filter vs 拦截器Interceptor vs 切片Aspect 详解 

1 前言

      最近接触到了过滤器和拦截器,网上查了查资料,这里记录一下,这篇文章就来仔细剖析下过滤器和拦截器的区别与联系。

2 拦截器与过滤器之间的区别

从上面对拦截器与过滤器的描述来看,它俩是非常相似的,都能对客户端发来的请求进行处理,它们的区别如下:

拦截机制有三种:

1. 过滤器(Filter)能拿到http请求,但是拿不到处理请求方法的信息。

2. 拦截器(Interceptor)既能拿到http请求信息,也能拿到处理请求方法的信息,但是拿不到方法的参数信息。

3. 切片(Aspect)能拿到方法的参数信息,但是拿不到http请求信息。

他们三个各有优缺点,需要根据自己的业务需求来选择最适合的拦截机制。

  • 作用域不同
    • 过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用
    • 拦截器依赖于spring容器,可以在spring容器中调用,不管此时Spring处于什么环境
  • 细粒度的不同
    • 过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装
    • 拦截器提供更精细的控制,可以在controller对请求处理之前或之后被调用,也可以在渲染视图呈现给用户之后调用
  • 中断链执行的难易程度不同
    • 拦截器可以 preHandle方法内返回 false 进行中断
    • 过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面

Spring的Interceptor(拦截器)与Servlet的Filter有相似之处,都能实现权限检查、日志记录等。不同的是:

FilterInterceptor摘要
Filter 接口定义在 javax.servlet 包中接口 HandlerInterceptor 定义在org.springframework.web.servlet 包中
Filter 定义在 web.xml 中
Filter在只在 Servlet 前后起作用。Filters 通常将 请求和响应(request/response) 当做黑盒子,Filter 通常不考虑servlet 的实现。拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户介入(hook into)请求的生命周期,在请求过程中获取信息,Interceptor 通常和请求更加耦合。在Spring构架的程序中,要优先使用拦截器。几乎所有 Filter 能够做的事情, interceptor 都能够轻松的实现1
Filter 是 Servlet 规范规定的。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。使用范围不同
Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。而拦截器是在 Spring容器内的,是Spring框架支持的。规范不同
Filter 不能够使用 Spring 容器资源拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过IoC注入到拦截器即可Spring 中使用 interceptor 更容易
Filter 是被 Server(like Tomcat) 调用Interceptor 是被 Spring 调用因此 Filter 总是优先于 Interceptor 执行

拦截器与过滤器的区别

1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:

这里写图片描述

2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。

过滤器拦截器运行先后步骤:

这里写图片描述

其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的.

3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现

 

3 何时使用拦截器?何时使用过滤器?

  1. 如果是非spring项目,那么拦截器不能用,只能使用过滤器。
  2. 如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
  3. 如果是处理dispaterServlet前后,只能使用过滤器。

4 过滤器

      过滤器Filter,是在Servlet规范中定义的,是Servlet容器支持的,该接口定义在 javax.servlet包下,主要是在客户端请求(HttpServletRequest)进行预处理,以及对服务器响应(HttpServletResponse)进行后处理。接口代码如下:

  1. package javax.servlet;
  2. import java.io.IOException;
  3. public interface Filter {
  4. void init(FilterConfig var1) throws ServletException;
  5. void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
  6. void destroy();
  7. }

对上面三个接口方法进行分析:

  • init(FilterConfig): 初始化接口,在用户自定义的Filter初始化时被调用,它与Servlet的 init方法的作用是一样的。
  • doFilter(ServletRequest,ServletResponse,FilterChain): 在每个用户的请求进来时这个方法都会被调用,并在Servlet的service方法之前调用(如果我们是开发Servlet项目),而FilterChain就代表当前的整个请求链,通过调用 FilterChain.doFilter可以将请求继续传递下去,如果想拦截这个请求,可以不调用FilterChain.doFilter,那么这个请求就直接返回了,所以Filter是一种责任链设计模式,在spring security就大量使用了过滤器,有一条过滤器链。
  • destroy: 当Filter对象被销毁时,这个方法被调用,注意,当Web容器调用这个方法之后,容器会再调用一次doFilter方法

4.1 自定义Filter过滤器

在springboot自定义Filter类如下:

例子1:

  1. @Component
  2. public class MyFilter implements Filter {
  3. private Logger logger = LoggerFactory.getLogger(MyFilter.class);
  4. @Override
  5. public void init(FilterConfig filterConfig) throws ServletException {
  6. logger.info("filter init");
  7. }
  8. @Override
  9. public void doFilter(ServletRequest servletRequest, ServletResponse
  10. servletResponse, FilterChain filterChain) throws IOException,
  11. ServletException {
  12. logger.info("doFilter");
  13. //对request,response进行预处理
  14. //TODO 进行业务逻辑
  15. filterChain.doFilter(servletRequest, servletResponse);
  16. }
  17. @Override
  18. public void destroy() {
  19. logger.info("filter destroy");
  20. }
  21. }

例子2:

  1. package com.zhongcy.filter
  2. @WebFilter(urlPatterns = "/*")
  3. @Order(value = 1)
  4. public class TestFilter implements Filter {
  5. private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
  6. Arrays.asList("/main/excludefilter", "/login", "/logout", "/register")));
  7. @Override
  8. public void init(FilterConfig filterConfig) throws ServletException {
  9. System.out.println("init-----------filter");
  10. }
  11. @Override
  12. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  13. HttpServletRequest request = (HttpServletRequest) req;
  14. HttpServletResponse response = (HttpServletResponse) res;
  15. String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
  16. boolean allowedPath = ALLOWED_PATHS.contains(path);
  17. if (allowedPath) {
  18. System.out.println("这里是不需要处理的url进入的方法");
  19. chain.doFilter(req, res);
  20. }
  21. else {
  22. System.out.println("这里是需要处理的url进入的方法");
  23. }
  24. }
  25. @Override
  26. public void destroy() {
  27. System.out.println("destroy----------filter");
  28. }
  29. }

 关于为什么SpringBoot中使用了@WebFilter但是过滤器却没有生效:一定要加上@Configuration注解,@Service其实也可以,其他类似。

  1. @SpringBootApplication
  2. @EnableTransactionManagement
  3. @MapperScan("com.zhongcy.repository")
  4. @ServletComponentScan("com.zhongcy.filter")
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class, args);
  8. }
  9. }

4.2 FilterRegistrationBean方式

在springboot中提供了FilterRegistrationBean方式,此类提供setOrder方法,可以为多个filter设置排序值。代码如下:

  1. @Configuration
  2. public class FilterConfig {
  3.     /**
  4.      * 配置一个Filter注册器
  5.      * @return
  6.      */
  7.     @Bean
  8.     public FilterRegistrationBean filterRegistrationBean1() {
  9.         FilterRegistrationBean registrationBean = new FilterRegistrationBean();
  10.         registrationBean.setFilter(filter1());
  11.         registrationBean.setName("filter1");
  12.         //设置顺序
  13.         registrationBean.setOrder(10);
  14.         return registrationBean;
  15.     }
  16.     @Bean
  17.     public FilterRegistrationBean filterRegistrationBean2() {
  18.         FilterRegistrationBean registrationBean = new FilterRegistrationBean();
  19.         registrationBean.setFilter(filter2());
  20.         registrationBean.setName("filter2");
  21.         //设置顺序
  22.         registrationBean.setOrder(3);
  23.         return registrationBean;
  24.     }
  25.     @Bean
  26.     public Filter filter1() {
  27.         return new MyFilter();
  28.     }
  29.     @Bean
  30.     public Filter filter2() {
  31.         return new MyFilter2();
  32.     }
  33. }

5 拦截器

    拦截器是Spring提出的概念,它的作用于过滤器类似,可以拦截用户请求并进行相应的处理,它可以进行更加精细的控制。

    在SpringMVC中,DispatcherServlet捕获每个请求,在到达对应的Controller之前,请求可以被拦截器处理,在拦截器中进行前置处理后,请求最终才到达Controller。

    拦截器的接口是 org.springframework.web.servlet.HandlerInterceptor接口,接口代码如下:

  1. public interface HandlerInterceptor {
  2. default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. return true;
  4. }
  5. default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
  6. }
  7. default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
  8. }
  9. }

接口方法解读:

  • preHandle方法:对客户端发过来的请求进行前置处理,如果方法返回true,继续执行后续操作,如果返回false,执行中断请求处理,请求不会发送到Controller
  • postHandler方法:在请求进行处理后执行,也就是在Controller方法调用之后处理,当然前提是之前的 preHandle方法返回 true。具体来说,postHandler方法会在DispatcherServlet进行视图返回渲染前被调用,也就是说我们可以在这个方法中对 Controller 处理之后的ModelAndView对象进行操作
  • afterCompletion方法: 该方法在整个请求结束之后执行,当然前提依然是 preHandle方法的返回值为 true才行。该方法一般用于资源清理工作

5.1 自定义拦截器

  1. public class MyInterceptor implements HandlerInterceptor {
  2. private Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. logger.info("preHandle....");
  6. return true;
  7. }
  8. @Override
  9. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  10. logger.info("postHandle...");
  11. }
  12. @Override
  13. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  14. logger.info("afterCompletion...");
  15. }
  16. }

5.2 注册拦截器同时配置拦截器规则

  1. @Configuration
  2. public class WebMvcConfig implements WebMvcConfigurer {
  3.     @Override
  4.     public void addInterceptors(InterceptorRegistry registry) {
  5.         registry.addInterceptor(handlerInterceptor())
  6.                 //配置拦截规则
  7.                 .addPathPatterns("/**");
  8.     }
  9.     @Bean
  10.     public HandlerInterceptor handlerInterceptor() {
  11.         return new MyInterceptor();
  12.     }
  13. }


 

5.3 多个拦截器协同工作

在springMVC中我们可以实现多个拦截器,并依次将他们注册进去,如下:

  1. public void addInterceptors(InterceptorRegistry registry) {
  2. registry.addInterceptor(handlerInterceptor())
  3. .addPathPatterns("/**");
  4. registry.addInterceptor(handlerInterceptor2())
  5. .addPathPatterns("/**");
  6. }

6 执行顺序

拦截器是在DispatcherServlet这个servlet中执行的,因此所有的请求最先进入Filter,最后离开Filter。其顺序如下。

Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter。

拦截器的顺序也跟他们注册时的顺序有关,至少 preHandle方法是这样,下图表示了两个拦截器协同工作时的执行顺序:

img

上图出自慕课网

后台打印日志也输出了相同的执行顺序:

  1. io-9999-exec-2] c.p.filter.interceptor.MyInterceptor : preHandle....
  2. 2018-09-13 12:13:31.292 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : preHandle2....
  3. 2018-09-13 12:13:31.388 INFO 9736 --- [nio-9999-exec-2] c.p.filter.controller.HelloController : username:pjmike,password:123456
  4. 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : postHandle2...
  5. 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : postHandle...
  6. 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : afterCompletion2...
  7. 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : afterCompletion...

7 spring boot 使用过滤器

两种方式:

  • 1、使用spring boot提供的FilterRegistrationBean注册Filter
  • 2、使用原生servlet注解定义Filter

两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter

7.1 方式一:

①、先定义Filter:

  1. package com.hwm.filter;
  2. import javax.servlet.*;
  3. import java.io.IOException;
  4. public class MyFilter implements Filter {
  5.     @Override
  6.     public void init(FilterConfig filterConfig) throws ServletException {
  7.     }
  8.     @Override
  9.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  10.         // do something 处理request 或response
  11.         System.out.println("filter1");
  12.         // 调用filter链中的下一个filter
  13.         filterChain.doFilter(servletRequest,servletResponse);
  14.     }
  15.     @Override
  16.     public void destroy() {
  17.     }
  18. }

②、注册自定义Filter

  1. @Configuration
  2. public class FilterConfig {
  3.     @Bean
  4.     public FilterRegistrationBean registrationBean() {
  5.         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
  6.         filterRegistrationBean.addUrlPatterns("/*");
  7.         return filterRegistrationBean;
  8.     }
  9. }

7.2 方式二:

  1. // 注入spring容器
  2. @Component
  3. // 定义filterName 和过滤的url
  4. @WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
  5. public class My2Filter implements Filter {
  6.     @Override
  7.     public void init(FilterConfig filterConfig) throws ServletException {
  8.     }
  9.     @Override
  10.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  11.         System.out.println("filter2");
  12.     }
  13.     @Override
  14.     public void destroy() {
  15.     }
  16. }

8 Spring boot拦截器的使用:

①、定义拦截器:

  1. public class MyInterceptor implements HandlerInterceptor {
  2.     @Override
  3.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4.         System.out.println("preHandle");
  5.         return true;
  6.     }
  7.     @Override
  8.     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
  9.         System.out.println("postHandle");
  10.     }
  11.     @Override
  12.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
  13.         System.out.println("afterCompletion");
  14.     }
  15. }


②、配置拦截器:

  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3.     @Override
  4.     public void addInterceptors(InterceptorRegistry registry) {
  5.         registry.addInterceptor(new MyInterceptor());
  6.     }
  7. }


③Controller演示:

  1. @RestController
  2. public class UController {
  3.     @GetMapping("/home")
  4.     public String home(){
  5.         System.out.println("home");
  6.         return "myhome";
  7.     }
  8. }

9 例子

9.1 拦截器interceptor例子1

interceptor 的执行顺序大致为:

  1. 请求到达 DispatcherServlet
  2. DispatcherServlet 发送至 Interceptor ,执行 preHandle
  3. 请求达到 Controller
  4. 请求结束后,postHandle 执行

Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:

  • preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
  • postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
  • afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等

统计请求耗时

  1. import javax.servlet.http.HttpServletRequest;
  2. import javax.servlet.http.HttpServletResponse;
  3. import org.apache.log4j.Logger;
  4. import org.springframework.web.servlet.ModelAndView;
  5. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  6. public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{
  7. private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);
  8. //before the actual handler will be executed
  9. public boolean preHandle(HttpServletRequest request,
  10. HttpServletResponse response, Object handler)
  11. throws Exception {
  12. long startTime = System.currentTimeMillis();
  13. request.setAttribute("startTime", startTime);
  14. return true;
  15. }
  16. //after the handler is executed
  17. public void postHandle(
  18. HttpServletRequest request, HttpServletResponse response,
  19. Object handler, ModelAndView modelAndView)
  20. throws Exception {
  21. long startTime = (Long)request.getAttribute("startTime");
  22. long endTime = System.currentTimeMillis();
  23. long executeTime = endTime - startTime;
  24. //modified the exisitng modelAndView
  25. modelAndView.addObject("executeTime",executeTime);
  26. //log it
  27. if(logger.isDebugEnabled()){
  28. logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
  29. }
  30. }
  31. }

例子来源 mkyong

使用mvc:interceptors标签来声明需要加入到SpringMVC拦截器链中的拦截器

  1. <mvc:interceptors>
  2. <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->
  3. <bean class="com.company.app.web.interceptor.AllInterceptor"/>
  4. <mvc:interceptor>
  5. <mvc:mapping path="/**"/>
  6. <mvc:exclude-mapping path="/parent/**"/>
  7. <bean class="com.company.authorization.interceptor.SecurityInterceptor" />
  8. </mvc:interceptor>
  9. <mvc:interceptor>
  10. <mvc:mapping path="/parent/**"/>
  11. <bean class="com.company.authorization.interceptor.SecuritySystemInterceptor" />
  12. </mvc:interceptor>
  13. </mvc:interceptors>

可以利用mvc:interceptors标签声明一系列的拦截器,然后它们就可以形成一个拦截器链,拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。

在mvc:interceptors标签下声明interceptor主要有两种方式:

  • 直接定义一个Interceptor实现类的bean对象。使用这种方式声明的Interceptor拦截器将会对所有的请求进行拦截。
  • 使用mvc:interceptor标签进行声明。使用这种方式进行声明的Interceptor可以通过mvc:mapping子标签来定义需要进行拦截的请求路径。

经过上述两步之后,定义的拦截器就会发生作用对特定的请求进行拦截了。

9.2 拦截器interceptor例子1

9.2.1 实现拦截器

实现拦截器可以自定义实现HandlerInterceptor接口,也可以通过继承HandlerInterceptorAdapter类,后者是前者的实现类。下面是拦截器的一个实现的例子,目的是判断用户是否登录。如果preHandle方法return true,则继续后续处理。

  1. public class LoginInterceptor extends HandlerInterceptorAdapter {
  2. /**
  3. *预处理回调方法,实现处理器的预处理(如登录检查)。
  4. *第三个参数为响应的处理器,即controller。
  5. *返回true,表示继续流程,调用下一个拦截器或者处理器。
  6. *返回false,表示流程中断,通过response产生响应。
  7. */
  8. @Override
  9. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  10. Object handler) throws Exception {
  11. System.out.println("-------------------preHandle");
  12. // 验证用户是否登陆
  13. Object obj = request.getSession().getAttribute("username");
  14. if (obj == null || !(obj instanceof String)) {
  15. response.sendRedirect(request.getContextPath() + "/index.html");
  16. return false;
  17. }
  18. return true;
  19. }
  20. /**
  21. *当前请求进行处理之后,也就是Controller 方法调用之后执行,
  22. *但是它会在DispatcherServlet 进行视图返回渲染之前被调用。
  23. *此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理。
  24. */
  25. @Override
  26. public void postHandle(HttpServletRequest request, HttpServletResponse response,
  27. Object handler, ModelAndView modelAndView) throws Exception {
  28. System.out.println("-------------------postHandle");
  29. }
  30. /**
  31. *方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。
  32. *这个方法的主要作用是用于进行资源清理工作的。
  33. */
  34. @Override
  35. public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
  36. Object handler, Exception ex) throws Exception {
  37. System.out.println("-------------------afterCompletion");
  38. }
  39. }

9.2.2 注册拦截器

为了使自定义的拦截器生效,需要注册拦截器到spring容器中,具体的做法是继承WebMvcConfigurerAdapter类,覆盖其addInterceptors(InterceptorRegistry registry)方法。最后别忘了把Bean注册到Spring容器中,可以选择@Component 或者 @Configuration。

  1. @Component
  2. public class InterceptorConfiguration extends WebMvcConfigurerAdapter{
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. // 注册拦截器
  6. InterceptorRegistration ir = registry.addInterceptor(new LoginInterceptor());
  7. // 配置拦截的路径
  8. ir.addPathPatterns("/**");
  9. // 配置不拦截的路径
  10. ir.excludePathPatterns("/**.html");
  11. // 还可以在这里注册其它的拦截器
  12. //registry.addInterceptor(new OtherInterceptor()).addPathPatterns("/**");
  13. }
  14. }

9.2.3 拦截器的应用场景

拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:

  1. 登录验证,判断用户是否登录。
  2. 权限验证,判断用户是否有权限访问资源。
  3. 日志记录,记录请求日志,以便统计请求访问量。
  4. 处理cookie、本地化、国际化、主题等。
  5. 性能监控,监控请求处理时长等。

10 配置例子

10.1 过滤器(Filter)

首先说一下Filter的使用地方,我们在配置web.xml时,总会配置下面一段设置字符编码,不然会导致乱码问题:

  1. <filter>
  2.     <filter-name>encoding</filter-name>
  3.     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  4.     <init-param>
  5.         <param-name>encoding</param-name>
  6.         <param-value>UTF-8</param-value>
  7.     </init-param>
  8.     <init-param>
  9.         <param-name>forceEncoding</param-name>
  10.         <param-value>true</param-value>
  11.     </init-param>
  12. </filter>
  13. <filter-mapping>
  14.     <filter-name>encoding</filter-name>
  15.     <servlet-name>/*</servlet-name>
  16. </filter-mapping>

10.2 拦截器(Interceptor)

拦截器的配置一般在SpringMVC的配置文件中,使用Interceptors标签,具体配置如下:

  1. <mvc:interceptors>
  2.     <mvc:interceptor>
  3.         <mvc:mapping path="/**" />
  4.         <bean class="com.scorpios.atcrowdfunding.web.LoginInterceptor"></bean>
  5.     </mvc:interceptor>
  6.     <mvc:interceptor>
  7.         <mvc:mapping path="/**" />
  8.         <bean class="com.scorpios.atcrowdfunding.web.AuthInterceptor"></bean>
  9.     </mvc:interceptor>
  10. </mvc:interceptors>


 

参考:拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别_止步前行的博客-CSDN博客_过滤器拦截器监听器执行顺序

11 切片Aspect编程

11.1 编程范式主要以下几大类

  • AOP(Aspect Oriented Programming)面向切面编程
  • OOP(Object Oriented Programming)面向对象编程
  • POP(procedure oriented programming)面向过程编程
  • FP(Functional Programming)面向函数编程

11.2 引入aop依赖

以下示例是基于Spring Boot实战系列(2)数据存储之Jpa操作MySQL chapter2-1可在Github获取源码

项目根目录 pom.xml 添加依赖 spring-boot-starter-aop

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

11.3 是否需要在程序主类中增加@EnableAspectJAutoProxy 注解

答案是否。只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。

11.4 aop注解

  • @Aspect: 切面,由通知和切入点共同组成,这个注解标注在类上表示为一个切面。
  • @Joinpoint: 连接点,被AOP拦截的类或者方法,在前置通知中有介绍使用@Joinpoint获取类名、方法、请求参数。
  • Advice: 通知的几种类型
    • @Before: 前置通知,在某切入点@Pointcut之前的通知
    • @After: 后置通知,在某切入点@Pointcut之后的通知无论成功或者异常。
    • @AfterReturning: 返回后通知,方法执行return之后,可以对返回的数据做加工处理。
    • @Around: 环绕通知,在方法的调用前、后执行。
    • @AfterThrowing: 抛出异常通知,程序出错跑出异常会执行该通知方法。
  • @Pointcut: 切入点,从哪里开始。例如从某个包开始或者某个包下的某个类等。

11.4.1 单切面不同advice执行顺序:

11.4.2 多切面不同advice执行顺序

详情可见,《Spring官方文档》
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-ordering

Spring AOP通过指定aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:

Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。

Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。

其中,数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值:

  1. /**
  2.      * Useful constant for the lowest precedence value.
  3.      * @see java.lang.Integer#MAX_VALUE
  4.      */
  5.     int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

11.4.3 同一aspect,相同advice的执行顺序


同一aspect,相同advice的执行顺序并不能直接确定,而且 @Order 在advice方法上也无效,但是有如下两种变通方式:

将两个 advice 合并为一个 advice,那么执行顺序就可以通过代码控制了
将两个 advice 分别抽离到各自的 aspect 内,然后为 aspect 指定执行顺序

 

11.4.4 Transactional Aspect的优先级

Spring事务管理(Transaction Management),也是基于Spring AOP。

在Spring AOP的使用中,有时我们必须明确自定义aspect的优先级低于或高于事务切面(Transaction Aspect),所以我们需要知道:

事务切面优先级:默认为最低优先级
LOWEST_PRECEDENCE = Integer.MAX_VALUE


如何修改事务切面的优先级:
在开启事务时,通过设置 @EnableTransactionManagement 和 <tx:annotation-driven/> 中的, order 属性来修改事务切面的优先级。
详情可见,《Spring官方文档》https://docs.spring.io/spring/docs/4.3.18.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#tx-annotation-driven-settings

11.4.5 spring 注解 事务,声明事务共存的情况下,先后顺序 order

配置:

   

  1. <!-- 註解式事務的支持 -->  
  2.  <tx:annotation-driven transaction-manager="txManager"  order="0"/>  
  3.  <!-- 服務事務註冊切面 -->  
  4.  <aop:config >  
  5.      <aop:pointcut expression="execution(* oddtech.service.impl.*.*(..))"  
  6.          id="txPoint"  />  
  7.      <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"  order="1"/>  
  8.  </aop:config> 

   参考:spring 注解 事务,声明事务共存的情况下,先后顺序 order_glory-of-me的博客-CSDN博客

或者代码:

  1. //@Transactional 的优先级默认为LOWEST_PRECEDENCE​​​​​​​
  2. @Aspect
  3. @Order(100) //优先级设置高点 这里要求优先级大于Transactional

11.5 实现日志分割功能

目录 aspect下 新建 httpAspect.java类,在收到请求之后先记录请求的相关参数日志信息,请求成功完成之后打印响应信息,请求处理报错打印报错日志信息。

httpAspect.java

  1. package com.angelo.aspect;
  2. import com.google.gson.Gson;
  3. import com.google.gson.GsonBuilder;
  4. import org.aspectj.lang.JoinPoint;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.*;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.web.context.request.RequestContextHolder;
  11. import org.springframework.web.context.request.ServletRequestAttributes;
  12. import javax.servlet.http.HttpServletRequest;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. @Aspect
  16. @Component
  17. public class HttpAspect {
  18. // 打印日志模块
  19. private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
  20. // 下面会一一介绍...

添加切入点

定义切入的入口在哪里,封装一个公共的方法实现复用

httpAspect.java​​​​​​​

  1. /**
  2. * 定义一个公共的方法,实现服用
  3. * 拦截UserController下面的所有方法
  4. * 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)写法:@Before("execution(public * com.angelo.controller.UserController.userList(..))")
  5. */
  6. @Pointcut("execution(public * com.angelo.controller.UserController.*(..))")
  7. public void log() {
  8. }

前置通知

拦截方法之前的一段业务逻辑,获取请求的一些信息,其中用到了Gson处理对象转json输出

httpAspect.java

  1. @Before("log()")
  2. public void doBefore(JoinPoint joinPoint) {
  3. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  4. HttpServletRequest request = attributes.getRequest();
  5. Map params = new HashMap();
  6. params.put("url", request.getRequestURL()); // 获取请求的url
  7. params.put("method", request.getMethod()); // 获取请求的方式
  8. params.put("ip", request.getRemoteAddr()); // 获取请求的ip地址
  9. params.put("className", joinPoint.getSignature().getDeclaringTypeName()); // 获取类名
  10. params.put("classMethod", joinPoint.getSignature().getName()); // 获取类方法
  11. params.put("args", joinPoint.getArgs()); // 请求参数
  12. // 输出格式化后的json字符串
  13. Gson gson = new GsonBuilder().setPrettyPrinting().create();
  14. logger.info("REQUEST: {}", gson.toJson(params));
  15. }

后置通知

拦截方法之后的一段业务逻辑

httpAspect.java​​​​​​​

  1. @After("log()")
  2. public void doAfter() {
  3. logger.info("doAfter");
  4. }

环绕通知

环绕通知是在方法的前后的一段逻辑操作,可以修改目标方法的返回值,第一个参数是org.aspectj.lang.ProceedingJoinPoint类型,注意这里要调用执行目标方法proceed()获取值返回,不然会造成空指针异常。在环绕通知里面也可以捕获错误返回。

httpAspect.java

  1. @Around("log()")
  2. public Object doAround(ProceedingJoinPoint point) {
  3. try {
  4. Object o = point.proceed();
  5. System.out.println("方法环绕proceed,结果是 :" + o);
  6. logger.info("doAround1");
  7. return o;
  8. } catch (Throwable e) {
  9. // e.printStackTrace();
  10. logger.info("doAround2");
  11. return null;
  12. }
  13. }

返回后通知

在切入点完成之后的返回通知,此时就不会抛出异常通知,除非返回后通知的业务逻辑报错。

httpAspect.java

  1. @Around("log()")
  2. public Object doAround(ProceedingJoinPoint point) {
  3. try {
  4. Object o = point.proceed();
  5. System.out.println("方法环绕proceed,结果是 :" + o);
  6. logger.info("doAround1");
  7. return o;
  8. } catch (Throwable e) {
  9. // e.printStackTrace();
  10. logger.info("doAround2");
  11. return null;
  12. }
  13. }

异常通知

抛出异常后的通知,此时返回后通知@AfterReturning就不会执行。

httpAspect.java

  1. @AfterThrowing(pointcut = "log()")
  2. public void doAfterThrowing() {
  3. logger.error("doAfterThrowing: {}", " 异常情况!");
  4. }

一段段伪代码读懂执行顺序

  1. try {
  2. // @Before 执行前通知
  3. // 执行目标方法
  4. // @Around 执行环绕通知 成功走finall,失败走catch
  5. } finally {
  6. // @After 执行后置通知
  7. // @AfterReturning 执行返回后通知
  8. } catch(e) {
  9. // @AfterThrowing 抛出异常通知
  10. }

controller/UserController.java文件的userList方法增加了exception参数

  1. /**
  2. * 查询用户列表
  3. * @return
  4. */
  5. @RequestMapping(value = "/user/list/{exception}")
  6. public List<User> userList(@PathVariable("exception") Boolean exception) {
  7. if (exception) {
  8. throw new Error("测试抛出异常!");
  9. }
  10. return userRepository.findAll();
  11. }

通过以上两种情况测试可以看到环绕通知在正常、异常两种情况都可以执行到。

Github查看本文完整示例 chapter3-1
Github: Spring Boot实战系列
 

12 小结

   简单总结一下,拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做。


参考:

spring boot 过滤器、拦截器的区别与使用__正在学技术的胖子的博客-CSDN博客_springboot过滤器的使用

在spring-boot工程中添加spring mvc拦截器 - 简书

springboot系列文章之过滤器 vs 拦截器 - 掘金

SpringBoot 手写切片/面向切面编程 - 简书

Spring Boot实战系列(3)AOP面向切面编程 - 掘金

Spring AOP之坑:完全搞清楚advice的执行顺序_公众号:流花鬼的博客-CSDN博客

Springboot源码分析之@Transactional - 掘金

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/编程变革者/article/detail/61337
推荐阅读
相关标签
  

闽ICP备14008679号