SpringBoot拦截器和 Servlet3.0自定义FilterListener

Posted 天宇轩-王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot拦截器和 Servlet3.0自定义FilterListener相关的知识,希望对你有一定的参考价值。

官方文档译文

Spring Boot 包括对嵌入式TomcatJettyUndertow服务器的支持。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器在 port 8080上侦听 HTTP 请求。

如果选择在CentOS上使用 Tomcat,请注意,默认情况下,临时目录用于存储已编译的 JSP,文件上载等。当 application 正在运行时,tmpwatch可能会删除此目录,从而导致失败。要避免此行为,您可能希望自定义tmpwatch configuration,以便不删除tomcat.*目录或配置server.tomcat.basedir,以便嵌入式 Tomcat 使用不同的位置。

1 Servlets,Filters 和 listeners

使用嵌入式 servlet 容器时,可以使用 Spring beans 或扫描 Servlet 组件,从 Servlet 规范中注册 servlets,过滤器和所有 listeners(如HttpSessionListener)。

将 Servlets,Filters 和 Listeners 注册为 Spring Beans

作为 Spring bean 的任何ServletFilter或 servlet *Listener实例都在嵌入式容器中注册。如果要在 configuration 期间从application.properties引用 value,这可能特别方便。

默认情况下,如果 context 仅包含一个 Servlet,则它将映射到/。在多个 servlet beans 的情况下, bean name 用作路径前缀。将 map 过滤为/*

如果 convention-based mapping 不够灵活,您可以使用ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean classes 进行完全控制。

Spring Boot 附带了许多可能定义 Filter beans 的 auto-configurations。以下是过滤器及其各自 order 的一些示例(lower order value 表示更高的优先级):

Servlet 过滤器订购
OrderedCharacterEncodingFilter Ordered.HIGHEST_PRECEDENCE
WebMvcMetricsFilter Ordered.HIGHEST_PRECEDENCE + 1
ErrorPageFilter Ordered.HIGHEST_PRECEDENCE + 1
HttpTraceFilter Ordered.LOWEST_PRECEDENCE - 10

将 Filter beans 无序排列通常是安全的。

如果需要特定的 order,则应避免配置在Ordered.HIGHEST_PRECEDENCE处读取请求正文的 Filter,因为它可能违反 application 的字符编码 configuration。如果 Servlet 过滤器包装请求,则应使用小于或等于OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER的 order 进行配置。

2 Servlet Context 初始化

嵌入式 servlet 容器不直接执行 Servlet 3.0 javax.servlet.ServletContainerInitializer接口或 Spring 的org.springframework.web.WebApplicationInitializer接口。这是一个故意的设计决定,旨在降低第方库设计为 war 内部 war 的风险可能 break Spring Boot applications。

如果需要在 Spring Boot application 中执行 servlet context 初始化,则应注册实现org.springframework.boot.web.servlet.ServletContextInitializer接口的 bean。单个onStartup方法提供对ServletContext的访问,如果需要,可以很容易地用作现有WebApplicationInitializer的适配器。

扫描 Servlet,过滤器和 listeners

使用嵌入式容器时,可以使用@ServletComponentScan启用使用@WebServlet@WebFilter@WebListener注释的 classes 的自动注册。

@ServletComponentScan在独立容器中没有任何效果,而是使用容器的 built-in 发现机制。

3 ServletWebServerApplicationContext

在引擎盖下,Spring Boot 使用不同类型的ApplicationContext来嵌入 servlet 容器支持。 ServletWebServerApplicationContext是一种特殊类型的WebApplicationContext,它通过搜索单个ServletWebServerFactory bean 来引导自己。通常TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory已经是 auto-configured。

您通常不需要知道这些 implementation classes。大多数 applications 都是 auto-configured,并且代表您创建了适当的ApplicationContextServletWebServerFactory

4 自定义嵌入式 Servlet 容器

可以使用 Spring Environment properties 配置 Common servlet 容器设置。通常,您将在application.properties文件中定义 properties。

Common 服务器设置包括:

  • 网络设置:监听传入 HTTP 请求的 port(server.port),绑定到server.address的接口地址,依此类推。

  • Session 设置:session 是持久性的(server.servlet.session.persistence),session 超时(server.servlet.session.timeout),session 数据的位置(server.servlet.session.store-dir)和 session-cookie configuration(server.servlet.session.cookie.*)。

  • 错误 management:错误页面的位置(server.error.path)等。

  • SSL

  • HTTP 压缩

Spring Boot 尝试尽可能多地暴露 common 设置,但这并不总是可行。对于这些情况,专用命名空间提供 server-specific 自定义(请参阅server.tomcatserver.undertow)。例如,可以使用嵌入的 servlet 容器的特定 features 配置访问日志

有关完整列表,请参阅ServerProperties class。

程序化定制

如果需要以编程方式配置嵌入式 servlet 容器,可以注册实现WebServerFactoryCustomizer接口的 Spring bean。 WebServerFactoryCustomizer提供对ConfigurableServletWebServerFactory的访问,其中包括许多自定义 setter 方法。以下 example 以编程方式显示 port:

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
​
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
​
    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }
​
}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactoryConfigurableServletWebServerFactory的专用变体,它们分别为 Tomcat,Jetty 和 Undertow 提供了额外的自定义 setter 方法。

直接自定义 ConfigurableServletWebServerFactory

如果前面的自定义技术太有限,您可以自己注册TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactorybean。

@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.setPort(9000);
    factory.setSessionTimeout(10, TimeUnit.MINUTES);
    factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
    return factory;
}

 

为许多 configuration 选项提供了 setter。如果您需要做一些更具异国情调的事情,还会提供一些受保护的方法“挂钩”。有关详细信息,请参阅source code 文档

5 JSP 限制

当 running 使用嵌入式 servlet 容器的 Spring Boot application(并打包为可执行存档)时,JSP 支持存在一些限制。

  • 使用 Jetty 和 Tomcat,如果使用 war 包装,它应该可以工作。可执行的 war 在使用java -jar启动时将起作用,并且还可以部署到任何标准容器。使用可执行文件 jar 时不支持 JSP。

  • Undertow 不支持 JSP。

  • 创建自定义error.jsp页面不会覆盖错误处理的默认视图。应该使用自定义错误页面代替。

有一个JSP sample,所以你可以看到如何设置。

代码

1 Filter

  • 启动类

    @SpringBootApplication
    @ServletComponentScan
    public class FilterApplication {
    ​
        public static void main(String[] args) {
            SpringApplication.run(FilterApplication.class, args);
        }
    ​
    }
  • 过滤器

    **
     * @author WGR
     * @create 2019/11/14 -- 21:25
     */
    @WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
    public class LoginFilter implements Filter {
    ​
        /**
         * 容器加载的时候调用
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("init loginFilter");
        }
    ​
    ​
        /**
         * 请求被拦截的时候进行调用
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("doFilter loginFilter");
    ​
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            HttpServletResponse resp = (HttpServletResponse) servletResponse;
            String username = req.getParameter("username");
    ​
            if ("topcheer".equals(username)) {
                filterChain.doFilter(servletRequest,servletResponse);
            } else {
                resp.sendRedirect("/index.html");
                return;
            }
    ​
        }
    ​
        /**
         * 容器被销毁的时候被调用
         */
        @Override
        public void destroy() {
            System.out.println("destroy loginFilter");
        }
    }
  • web层

    /**
     * @author WGR
     * @create 2019/11/14 -- 21:32
     */
    ​
    @RestController
    public class LoginController {
    ​
        @GetMapping("/api/test_request")
        public Object testRequest(String username){
            return username;
        }
    }
  • html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    index static
    <h1>topcheer</h1>
    </body>
    </html>
  •  测试

     

 

 

 

 

 

 

2 Servlet

  • servlet
@WebServlet(name = "userServlet",urlPatterns = "/v1/api/test/customs")
public class UserServlet extends HttpServlet{

     @Override
     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         
         resp.getWriter().print("custom sevlet");
         resp.getWriter().flush();
         resp.getWriter().close();
     }

     
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doGet(req, resp);
    }

     
    
}
  • 测试方法
 @GetMapping("/v1/api/test/customs")
    public Object testServlet(){
        return "success";
    }
  • 测试结果

 

3 Listener

  • listener
@WebListener
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        // TODO Auto-generated method stub
        System.out.println("======requestDestroyed========");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("======requestInitialized========");

    }


}
  • 测试

 

 

 

 4 拦截器

@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer  {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api/*/**");
        registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/*/**");
        
        //.excludePathPatterns("/api2/xxx/**"); //拦截全部 /*/*/**
        
        WebMvcConfigurer.super.addInterceptors(registry);
    }

    
    
    


}
public class LoginIntercepter implements HandlerInterceptor{

    /**
     * 进入controller方法之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginIntercepter------->preHandle");

//        String token = request.getParameter("access_token");
//        
//        response.getWriter().print("fail");
        
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * 调用完controller之后,视图渲染之前
     */
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
        System.out.println("LoginIntercepter------->postHandle");
        
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 整个完成之后,通常用于资源清理
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("LoginIntercepter------->afterCompletion");
        
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    
    
}
public class TwoIntercepter implements HandlerInterceptor{

    /**
     * 进入对应的controller方法之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        System.out.println("TwoIntercepter------>preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * controller处理之后,返回对应的视图之前
     */
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("TwoIntercepter------>postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 整个请求结束后调用,视图渲染后,主要用于资源的清理
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("TwoIntercepter------>afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    
}

 

测试:

 

 总结:

1、@Configuration
继承WebMvcConfigurationAdapter(SpringBoot2.X之前旧版本)

SpringBoot2.X 新版本配置拦截器 implements WebMvcConfigurer

2、自定义拦截器 HandlerInterceptor
preHandle:调用Controller某个方法之前
postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理

3、按照注册顺序进行拦截,先注册,先被拦截

拦截器不生效常见问题:
1)是否有加@Configuration
2)拦截路径是否有问题 ** 和 *
3)拦截器最后路径一定要 “/**”, 如果是目录的话则是 /*/

Filter
是基于函数回调 doFilter(),而Interceptor则是基于AOP思想
Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等

依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。

在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。

Filter和Interceptor的执行顺序

过滤前->拦截前->action执行->拦截后->过滤后

 

以上是关于SpringBoot拦截器和 Servlet3.0自定义FilterListener的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot 配置 ServletFilterListener

SpringBoot配置介绍

Servlet3.0学习总结——使用注解标注Servlet

Servlet3.0新特性

servlet3.0初体验

Servlet3.0学习总结——基于Servlet3.0的文件上传