Loading

SpringBoot2.X 过滤器-拦截器实战

SpringBoot2.x 过滤器

什么是过滤器?

v2-f74107cb2a98ba4ae67ae0373d4532bc_r

过滤器可以比喻成打鱼的渔网,渔网有网孔大小,会过滤掉小于网孔大小的🐟;

引申在Web容器中,过滤器可以做:过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证等等等,过滤器的作用非常大,只要发挥想象就可以有意想不到的效果

Filter是如何实现拦截的?

Filter接口中有一个叫做 doFilter 的方法,这个方法实现了对用户请求的过滤。具体流程大体是这样的:

  1. 用户发送请求到 web 服务器,请求会先到过滤器;
  2. 过滤器会对请求进行一些处理比如过滤请求的参数、修改返回给客户端的 response 的内容、判断是否让用户访问该接口等等。
  3. 用户请求响应完毕。
  4. 进行一些自己想要的其他操作。

img

如何自定义过滤器?

下面提供两种方法。

方法一:自己手动注册配置实现

自定义的 Filter 需要实现javax.Servlet.Filter接口,并重写接口中定义的3个方法。

MyFilter.java

@Component
public class MyFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public void init(FilterConfig filterConfig) {
        logger.info("初始化过滤器:", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //对请求进行预处理
        logger.info("过滤器开始对请求进行预处理:");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestUri = request.getRequestURI();
        System.out.println("请求的接口为:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        filterChain.doFilter(servletRequest, servletResponse);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器");
    }
}

MyFilterConfig.java

在配置中注册自定义的过滤器。

@Configuration
public class MyFilterConfig {
    @Autowired
    MyFilter myFilter;
    @Bean
    public FilterRegistrationBean<MyFilter> thirdFilter() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setFilter(myFilter);

        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

        return filterRegistrationBean;
    }
}

方法二:通过提供好的一些注解实现

在自己的过滤器的类上加上@WebFilter 然后在这个注解中通过它提供好的一些参数进行配置。

@WebFilter(filterName = "MyFilterWithAnnotation", urlPatterns = "/api/*")
public class MyFilterWithAnnotation implements Filter {

   ......
}

另外,为了能让 Spring 找到它,你需要在启动类上加上 @ServletComponentScan 注解。

@SpringBootApplication
@ServletComponentScan
public class SpringbootFilterApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootFilterApplication.class, args);
    }

}

定义多个拦截器,并决定它们的执行顺序

MyFilter2.java

@Component
public class MyFilter2 implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(MyFilter2.class);

    @Override
    public void init(FilterConfig filterConfig) {
        logger.info("初始化过滤器2");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //对请求进行预处理
        logger.info("过滤器开始对请求进行预处理2:");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestUri = request.getRequestURI();
        System.out.println("请求的接口为2:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        filterChain.doFilter(servletRequest, servletResponse);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为2:" + (endTime - startTime));
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器2");
    }
}

在配置中注册自定义的过滤器,通过FilterRegistrationBeansetOrder 方法可以决定 Filter 的执行顺序。

需要注意的是 order 的数字越小优先级越高,越优先执行

@Configuration
public class MyFilterConfig {
    @Autowired
    MyFilter myFilter;

    @Autowired
    MyFilter2 myFilter2;

    @Bean
    public FilterRegistrationBean<MyFilter> setUpMyFilter() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setOrder(0);
        filterRegistrationBean.setFilter(myFilter);
        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<MyFilter2> setUpMyFilter2() {
        FilterRegistrationBean<MyFilter2> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setFilter(myFilter2);
        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
        return filterRegistrationBean;
    }
}

自定义 Controller 验证过滤器

@RestController
@RequestMapping("/api")
public class MainController {

    @GetMapping("/hello")
    public String getHello() throws InterruptedException {
        Thread.sleep(1000);
        return "Hello";
    }
}

实际测试效果如下:

image-20200608000310281

SpringBoot2.x 拦截器

什么是拦截器?

当你来到公司并想见见公司经理时,你需要通过拦截器,这些拦截器可以是门卫或者接待员

在Spring中,当请求发送到Controller时,在被Controller处理之前,它必须经过拦截器(0或多个)。
Spring Interceptor是一个非常类似于Servlet Filter的概念。

Spring Interceptor仅适用于发送到Controller的请求。
img

您可以使用Interceptor来执行某些任务,例如在Controller处理请求之前编写日志,添加或更新配置,...

如下图所示:LogInterceptor 拦截器、MainController 主控制器

访问一个路径为 "/test" 的请求,在到达MainController之前,先被 LogInterceptor 的 preHandle方法处理,我们可以在该方法中打印日志或者校验某些入参,可以记录下访问时间;MainController 的 test 方法处理完请求之后 Response 给用户之前,被 postHandle 方法处理可以记录下访问结束时间,并统计该请求的处理时间,方便开发人员进行性能分析;最后整个请求处理完成后,会执行 afterCompletion 方法。

![img](F:\project\小滴课堂移动端在线项目\第 9 天 新版Servlet3.0和SpringBoot2.X过滤器-拦截器实战\14286863.png)

如何自定义拦截器?

自定义 Interceptor 必须实现org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类。

需要实现三种抽象方法:

public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler)
 
 
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView)
 
 
public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex)

注意:preHandle方法返回true或false。 如果返回true,则意味着请求将继续执行。

每个请求可能会通过许多拦截器。下图说明了这一点。

img

SpringBoot 拦截器实战

案例背景:

本项目采用 SpringBoot + thymeleaf 模板引擎进行交互,提供三种请求和三个拦截器,帮助大家理解请求在拦截器中的流转过程。 LogInterceptor 拦截所有请求,并且第一顺位执行,OldLoginInterceptor 仅拦截 "/admin/oldLogin" 请求,第二顺位执行,AdminInterceptor 拦截 "/admin/*" 下除了 "/admin/oldLogin" 之外的三个请求,并且最后执行。

项目结构:

![image-20200607230857070](F:\project\小滴课堂移动端在线项目\第 9 天 新版Servlet3.0和SpringBoot2.X过滤器-拦截器实战\image-20200607230857070.png)

LogInterceptor** 拦截所有请求:

public class LogInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println("\n-------- LogInterception.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Start Time: " + System.currentTimeMillis());

        request.setAttribute("startTime", startTime);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
                           Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("\n-------- LogInterception.postHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());

        // You can add attributes in the modelAndView
        // and use that in the view page
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("\n-------- LogInterception.afterCompletion --- ");

        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("End Time: " + endTime);

        System.out.println("Time Taken: " + (endTime - startTime));
    }

}

OldLoginInterceptor 拦截器用于将请求 "/admin/oldLogin" 重定向到请求 "/admin/login" 路径上

img

img

public class OldLoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");

        response.sendRedirect(request.getContextPath() + "/admin/login");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
                           Object handler, ModelAndView modelAndView) throws Exception {

        // This code will never be run.
        System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
                                Object handler, Exception ex) throws Exception {

        // This code will never be run.
        System.out.println("\n-------- QueryStringInterceptor.afterCompletion --- ");
    }

}

AdminInterceptor 拦截除了 "/admin/oldLogin" 之外的匹配 "/admin/*" 路径的请求:

public class AdminInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        System.out.println("\n-------- AdminInterceptor.preHandle --- ");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
                           Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("\n-------- AdminInterceptor.postHandle --- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
                                Object handler, Exception ex) throws Exception {

        System.out.println("\n-------- AdminInterceptor.afterCompletion --- ");
    }

}

配置拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        /**
         * 处理所有请求,该拦截器最先被执行
         */
        registry.addInterceptor(new LogInterceptor());

        /**
         * 处理路径为 "/admin/oldLogin" 的请求
         * 该拦截器在 LogInterceptor 之后执行
         */
        registry.addInterceptor(new OldLoginInterceptor())//
                .addPathPatterns("/admin/oldLogin");

        /**
         * 该拦截器处理 路径为 "/admin/*" 的所有请求,除了 "/admin/oldLogin"
         * 该拦截器为最后执行拦截器, 如果请求在前面被拦截并不通过那么该拦截器将得不到执行
         */
        registry.addInterceptor(new AdminInterceptor())//
                .addPathPatterns("/admin/*")//
                .excludePathPatterns("/admin/oldLogin");
    }
}

自定义Controller校验拦截器

@RestController
public class MainController {

    @RequestMapping(value = { "/", "/test" })
    public String test(Model model) {

        System.out.println("\n-------- MainController.test --- ");

        System.out.println(" ** You are in Controller ** ");

        return "test";
    }

    // This path is no longer used.
    // It will be redirected by OldLoginInterceptor
    @Deprecated
    @RequestMapping(value = { "/admin/oldLogin" })
    public String oldLogin(Model model) {

        // Code here never run.
        return "oldLogin";
    }

    @RequestMapping(value = { "/admin/login" })
    public String login(Model model) {

        System.out.println("\n-------- MainController.login --- ");

        System.out.println(" ** You are in Controller ** ");

        return "login";
    }

}

thymeleaf 模板引擎

test.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
 
   <head>
      <meta charset="UTF-8" />
      <title>Spring Boot Mvc Interceptor example</title>
   </head>
 
   <body>
      <div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
         <a th:href="@{/}">Home</a>
         &nbsp;&nbsp; | &nbsp;&nbsp;
         <a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>  
      </div>
 
      <h3>Spring Boot Mvc Interceptor</h3>
       
      <span style="color:blue;">Testing LogInterceptor</span>
      <br/><br/>
 
      See Log in Console..
 
   </body>
</html>

login.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8" />
      <title>Spring Boot Mvc Interceptor example</title>
   </head>
   <body>
    
      <div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
         <a th:href="@{/}">Home</a>
         &nbsp;&nbsp; | &nbsp;&nbsp;
         <a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>  
      </div>
       
      <h3>This is Login Page</h3>
       
      <span style="color:blue">Testing OldLoginInterceptor &amp; AdminInterceptor</span>
      <br/><br/>
      See more info in the Console.
       
   </body>
    
</html>

启动项目,测试

测试用户访问 http://localhost:8080/ 的时候, LogInterceptor记录相关信息(页面地址,访问时间),并计算 Web服务器处理请求的时间。另外,页面会被渲染成 test.html

当用户访问 http://localhost:8080/admin/oldLogin 也就是旧的登录页面(不再使用)时, OldLoginInterceptor将请求重定向 http://localhost:8080/admin/login 页面会被渲染成正常的登录页面 login.html

注意看控制台打印出的信息。

拦截器与过滤器的区别?

过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。

拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。

参考文章:

https://o7planning.org/en/11689/spring-boot-interceptors-tutorial#a14288051

https://snailclimb.gitee.io/springboot-guide/#/./docs/basis/springboot-interceptor

posted @ 2020-06-08 00:15  PinGoo  阅读(499)  评论(0编辑  收藏  举报