Filter
1. 生命周期
=== Filter作为特殊的API,其实由开发者完成,但Filter对象必须由规范实现者持有
xjakarta.servlet.Filter接口设计了三个声明周期的方法 init destory doFilter
init — 规范实现者创建Filter对象时会调用一次init()方法并传入FilterConfig
destory — 规范实现者销毁Filter对象之前会调用destory()
doFilter – 规范实现者会在Filter URL 匹配当次请求时调用doFilter()
FilterChain — 过滤器链对象用于控制当前请求继续向下执行
init | destory 被手动调用时 不视为声明周期
- jakarta.servlet.Filter
|
|
=== 官方为了便于开发者使用Filter组件,提供了HttpFilter的过渡实现
jakarta.servlet.http.HttpFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public abstract class HttpFilter extends GenericFilter { private static final long serialVersionUID = 7478463438252262094L; public HttpFilter() { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) { this.doFilter((HttpServletRequest)req, (HttpServletResponse)res, chain); } else { throw new ServletException("non-HTTP request or response"); } } protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { chain.doFilter(req, res); }
2. 注册方式
=== Filter相比于Servlet组件【3种】【Filter注册方式有以下四种】
- web.xml
- @WebFilter
- ServletContext URL
- ServletContext Servlet
2.1 web.xml
=== 通过将Filter类配置到web.xml完成Filter注册
CustomWebFilter.java
1 2 3 4 5 6 7
public class CustomWebFilter extend HttpFilter { protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { System.out.println("customFilter....."); chain.doFilter(response, response); } }
Web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<filter> <description>CustomWebFilter for something … </description> <display-name>CustomWebFilter√ display-name></display-name> <filter-name>CustomWebFilter√filter-name></filter-name> <filter-class>com.example.filter.CustomWebFilter></filter-class> <async-supported>false</async-supported> <init-param> <param-name>init-key-name</param-name> <param-value>init-key-value</param-value> </init-param> <filter-mapping> <filter-name>CustomWebFilter</filter-name> <url-pattern>/*</url-pattern> <!-- <servlet-name>servletName</servlet-name> --> <!-- <dispatcher>FORWARD</dispatcher> --> </filter-mapping> </filter>
:warning:
- web.xml方式下Filter对象的创建必须由规范实现者反射完成 如: Tomcat
- 基于web.xml必须为Filter提供无参构造方法
jakarta.servlet.DispatcherType Filter拦截器的类型
1 2 3 4 5 6 7 8 9 10 11 12
package jakarta.servlet; public enum DispatcherType { FORWARD, INCLUDE, REQUEST, ASYNC, ERROR; private DispatcherType() { } }
jakarta.servlet.FilterConfig 初始化Filter获取拦截器的相关信息
1 2 3 4 5 6 7 8 9 10
public interface FilterConfig { String getFilterName(); ServletContext getServletContext(); String getInitParameter(String var1); Enumeration<String> getInitParameterNames(); }
2.2 @WebFilter
=== @WebFilter注解由Servlet3.0版本提供,为了基于JAVA配置提代web.xml
|
|
:warning:Warning:
注解的方式也必须提供Filter的无参构造方法。
2.3 ServletContext URL
=== Servlet 3.0 开始 允许以编程 API 方式 完成 Filter 组件注册
- ServletContext 注册 Filter 组件必须在项目初始化之前 通常是 SCI 引擎方式注册
CustomWebFilter[以下代码逻辑正确但是时机不对]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
package com.example.filter; import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterRegistration; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.EnumSet; public class CustomWebFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,ServletException { System.out.println("do something . "); ServletContext servletContext = request.getServletContext(); // FilterRegistration.Dynamic registration = // servletContext.addFilter("CustomWebFilter", CustomWebFilter.class); FilterRegistration.Dynamic registration = servletContext.addFilter("CustomWebFilter", new CustomWebFilter()); registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, " /* "); chain.doFilter(request, response); } }
:warning:
- 走到Servlet | Filter 生命周期方法中,意味着项目早已完成了初始化,无法更新ServletContext
- 动态注册Filter显然可以不再提供无参构造方法,因为你可以自己去new一个Filter
- isMatchAfter 参数表示决定多个相同 URL Filter 匹配规则
2.4 ServletContext Servlet
=== Filter 还允许不指定 URL mapping 但必须指定 Servlet 的拦截方式
- Filter 不指定 URL 方式时 | 必须指定拦截那些 Servlet
OtherWebFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class OtherWebFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,ServletException { System.out.println("do something . "); ServletContext servletContext = request.getServletContext(); FilterRegistration.Dynamic registration = servletContext.addFilter("CustomWebFilter", CustomWebFilter.class); registration.addMappingForServletNames( EnumSet.allOf(DispatcherType.class), true, "servlet1", "servlet2" ); chain.doFilter(request, response); } }
:notebook:
- 对于没有无参构造的 Filter 而言 你只能通过 API 方式动态完成注册
- spring-webmvc : org.springframework.web.filter.CorsFilter [没有无参构造]
3. ServletContainerInitializer
=== ServletContainerInitializer 简称为SCI引擎 Servlet规范预定机制
3.1 SCI机制
- SCI引擎允许项目启动之前,定制初始化行为,且可获取ServletContext
- SCI需声明 META-INF/services/jakarta.servlet.ServletContainerInitializer
- 内容为a.b.c.ServletContainerInitializerImpl [实现类全路径名称]
jakarta.servlet.ServletContainerInitializer
1 2 3 4 5 6
package jakarta.servlet; import java.util.Set; public interface ServletContainerInitializer { public void onStartup(Set<Class<? > classes,ServletContext ctx) throws ServletException; }
com.sci.WebComponentInitializer
1 2 3 4 5 6 7 8 9
public class WebComponentInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<? > initializerClasses, ServletContext servletContext) throws ServletException{ assert initializerClasses = null; FilterRegistration.Dynamic registration = servletContext.addFilter("s", CustomWebFilter.class); registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class),true, "/* "); // do other things ... } }
:warning:
- 直接实现 ServletContainerInitializer | initializerClasses 参数总是为 null
3.2 @HandlerType
=== @HandlesTypes 仅能用在 SCI 引擎处获取指定接口类型及其子类型给到initializerClasses
com.sci.WebInitializer
1 2 3 4 5 6 7
package com.sci; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; public interface WebInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
com.sci.WebComponentInitializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
package com.sci; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.Set; @HandlesTypes(WebInitializer.class) public class WebComponentInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<? > initializerClasses, ServletContext servletContext) throws ServletException { assert initializerClasses = null; for (Class<?> initializerClass : initializerClasses) { if (!initializerClass.isInterface() & !Modifier.isAbstract(initializerClass.getModifiers()) & WebInitializer.class.isAssignableFrom(initializerClass)) { Constructor<?>[] constructors = initializerClass.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { if (constructor.getParameterCount() = 0) { try { WebInitializer o = (WebInitializer) constructor.newInstance(); o.onStartup(servletContext); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } } } } }
:notebook:
- initializerClasses 对应 @HandlesTypes 注解接口全部子类型
- @HandlesTypes 机制显然有助于分散 多个初始化逻辑动作 良好组织代码封装
4. 优先级
=== Filter 仅在多个 Filter 同时匹配一个 URL 请求时才具备优先级概念
- aFilter /a - 仅匹配 /a
- bFilter /b - 仅匹配 /b
- cFilter /a * - /a 任意 此时访问 /a 则顺序为 aFilter > cFilter
- dFilter *
- eFilter *
- 当Filter URL Mapping 且都能匹配当前请求 执行顺序如下
- web.xml
先后声明顺序决定 - @WebFilter Filter 类名称字母先后顺序 dFiler eFilter 则 dFiler 先
- ServletContext 受到注册顺序 & isMatchAfter 参数影响
5. Spring-webMvc
=== spring-webMvc | spring-web 是spring框架的web功能模块而非框架
- spring-web抽象 | 封装web http相关的技术基础的API
- spring-webmvc 围绕servlet.api 规范进行实现的
Dependendy
1 2 3 4 5
<dependency> <groupId>org.springframework </groupId> <artifactId>spring-webmvc </artifactId> <version>6.1.8 </version> </dependency>
5.1 OncePerRequestFilter
=== OncePerRequestFilter Filter 的实现之一 保证规范实现外的单次请求仅执行一次
org.springframework.web.filter.GenericFilterBean
1 2 3 4 5 6 7 8 9 10 11 12
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean { public final void init(FilterConfig filterConfig) throws ServletException { // 已实现 } }
org.springframework.web.filter.OncePerRequestFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 根据封装逻辑 你需要实现 doFilterInternal 生命周期 public abstract class OncePerRequestFilter extends GenericFilterBean { public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // ... doFilterInternal(request, response, filterChain); } // 子类需要实现这个 doFilterInternal protected abstract void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException; }
5.2 CharacterEncodingFilter
=== spring-web 抽象并实现编码控制过滤器
org.springframework.web.filter.CharacterEncodingFilter
1 2 3 4 5
public class CharacterEncodingFilter extends OncePerRequestFilter { private String encoding; private boolean forceRequestEncoding = false; private boolean forceResponseEncoding = false; }
:notes:
request.setCharacterEncoding(encoding) - 仅 POST 有效 [GET 需 URL 参数转换]
response.setCharacterEncoding(encoding) - 不区分请求方式
5.3 CorsFilter
=== spring-web 提供的跨域 filter 处理 [CorsFilter 只能通过 API 方式注册]
org.springframework.web.filter.CorsFilter
1 2 3 4 5 6 7 8
public class CorsFilter extends OncePerRequestFilter { private final CorsConfigurationSource configSource; private CorsProcessor processor = new DefaultCorsProcessor(); public CorsFilter(CorsConfigurationSource configSource) { Assert.notNull(configSource, "CorsConfigurationSource must not benull"); this.configSource = configSource; } }
:warning:
- CorsFilter 会根据 request 对象进行跨域判断和处理
- CorsFilter 会使用 response 对象进行跨域响应头相关的添加
CorsFilter:doFilterInternal
1 2 3 4 5 6 7 8 9 10 11 12 13
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request); boolean isValid = this.processor.processRequest(corsConfiguration, request, response); if (!isValid | CorsUtils.isPreFlightRequest(request)) { return; } filterChain.doFilter(request, response); }
org.springframework.web.cors.DefaultCorsProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; public class DefaultCorsProcessor implements CorsProcessor { private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class); static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK ="Access-Control-Request-Private-Network"; static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-ControlAllow-Private-Network"; @Override @SuppressWarnings("resource") public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException { Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY); if (!varyHeaders.contains(HttpHeaders.ORIGIN)) { response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN); } if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) { response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); } if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) { response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS); } if (!CorsUtils.isCorsRequest(request)) { return true; } if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) = null) { logger.trace("Skip: response already contains \"Access-ControlAllow-Origin\""); return true; } boolean preFlightRequest = CorsUtils.isPreFlightRequest(request); if (config = null) { if (preFlightRequest) { rejectRequest(new ServletServerHttpResponse(response)); return false; } else { return true; } } return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest); } }
:warning:
- spring-webmvc 提供的 Filter 也是人提供的 同样需要注册 [注册给 Tomcat]
- Filter 作为 Spring 容器对象 通常没有意义[方便你取得该对象] Spring 不会帮你注册
- Filter 作为 Spring 容器对象 在 SpringBoot 环境会利用 SCI 帮你注册
- SpringBoot 环境你应该使用 SpringBoot 提供的 SCI 机制注册 Web 组件
5.4 SpringServletContainerInitializer
=== spring-web 提供的 SCI 引擎 可完成 spring-webmvc 环境初始化 | Web组件注册
org.springframework.web.SpringServletContainerInitializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(@Nullable Set<Class<? > webAppInitializerClasses, ServletContext servletContext) throws ServletException { } }
org.springframework.web.WebApplicationInitializer
1 2 3
public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
spring-web-6.1.8.jar/META-INF/services/jakarta.servlet.ServletContainerInitializer
1
org.springframework.web.SpringServletContainerInitializer
5.5 初始化
=== SCI 搭建 spring-webmvc
Dependency
1 2 3 4 5 6 7 8 9 10
<dependency> <groupId>org.springframework </groupId> <artifactId>spring-webmvc </artifactId> <version>6.1.8 </version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core </groupId> <artifactId>jackson-databind </artifactId> <version>2.17.1 </version> </dependency>
SpringContextConfiguration
1 2 3 4 5 6 7 8
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @ComponentScan @EnableWebMvc public class SpringContextConfiguration { }
SpringWebmvcInitializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
import jakarta.servlet.Filter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.support .AbstractAnnotationConfigDispatcherServletInitializer; import java.nio.charset.StandardCharsets; public class SpringWebmvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringContextConfiguration.class}; } public static final String DISPATCHER_SERVLET_URL_MAPPING = "/*"; @Override protected String[] getServletMappings() { return new String[]{DISPATCHER_SERVLET_URL_MAPPING}; } @Override protected boolean isAsyncSupported() { return super.isAsyncSupported(); } @Override protected Filter[] getServletFilters() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(" * ", config); return new Filter[] { new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true, false), new CorsFilter(source) }; } }