SpringBoot拦截器和 Servlet3.0自定义Filter、Listener
官方文档译文
Spring Boot 包括对嵌入式Tomcat,Jetty和Undertow服务器的支持。大多数开发人员使用适当的“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 的任何Servlet
,Filter
或 servlet *Listener
实例都在嵌入式容器中注册。如果要在 configuration 期间从application.properties
引用 value,这可能特别方便。
默认情况下,如果 context 仅包含一个 Servlet,则它将映射到/
。在多个 servlet beans 的情况下, bean name 用作路径前缀。将 map 过滤为/*
。
如果 convention-based mapping 不够灵活,您可以使用ServletRegistrationBean
,FilterRegistrationBean
和ServletListenerRegistrationBean
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 来引导自己。通常TomcatServletWebServerFactory
,JettyServletWebServerFactory
或UndertowServletWebServerFactory
已经是 auto-configured。
您通常不需要知道这些 implementation classes。大多数 applications 都是 auto-configured,并且代表您创建了适当的
ApplicationContext
和ServletWebServerFactory
。
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
)等。
Spring Boot 尝试尽可能多地暴露 common 设置,但这并不总是可行。对于这些情况,专用命名空间提供 server-specific 自定义(请参阅server.tomcat
和server.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); } }
TomcatServletWebServerFactory
,JettyServletWebServerFactory
和UndertowServletWebServerFactory
是ConfigurableServletWebServerFactory
的专用变体,它们分别为 Tomcat,Jetty 和 Undertow 提供了额外的自定义 setter 方法。
直接自定义 ConfigurableServletWebServerFactory
如果前面的自定义技术太有限,您可以自己注册TomcatServletWebServerFactory
,JettyServletWebServerFactory
或UndertowServletWebServerFactory
bean。
@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。
有一个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执行->拦截后->过滤后