2024-06-15    2024-06-26    3058 字  7 分钟

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package jakarta.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

=== 官方为了便于开发者使用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:

    1. web.xml方式下Filter对象的创建必须由规范实现者反射完成 如: Tomcat
    2. 基于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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@WebFilter(
    description = "Filter some description",
    displayName = "filter",
    initParams = {@WebInitParam(name="kye1", value="value1")},
    urlPatterns = "/*",
    asyncSupported = true,
    servletNames = {"servlet1", "servlet2"},
    dispatcherTypes = {DispatcherType.REQUEST})
)
public class CustomWebFilter extends HttpFilter{
    
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        System.out.println("do something......");
        chain.doFilter(request, response);
    }
    
}

: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:

    1. 走到Servlet | Filter 生命周期方法中,意味着项目早已完成了初始化,无法更新ServletContext
    2. 动态注册Filter显然可以不再提供无参构造方法,因为你可以自己去new一个Filter
    3. 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:

  1. initializerClasses 对应 @HandlesTypes 注解接口全部子类型
  2. @HandlesTypes 机制显然有助于分散 多个初始化逻辑动作 良好组织代码封装

4. 优先级

=== Filter 仅在多个 Filter 同时匹配一个 URL 请求时才具备优先级概念

  1. aFilter /a - 仅匹配 /a
  2. bFilter /b - 仅匹配 /b
  3. cFilter /a * - /a 任意 此时访问 /a 则顺序为 aFilter > cFilter
  4. dFilter *
  5. eFilter *
  6. 当Filter URL Mapping 且都能匹配当前请求 执行顺序如下
  7. web.xml 先后声明顺序决定
  8. @WebFilter Filter 类名称字母先后顺序 dFiler eFilter 则 dFiler 先
  9. 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:

    1. CorsFilter 会根据 request 对象进行跨域判断和处理
    2. 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:

    1. spring-webmvc 提供的 Filter 也是人提供的 同样需要注册 [注册给 Tomcat]
    2. Filter 作为 Spring 容器对象 通常没有意义[方便你取得该对象] Spring 不会帮你注册
    3. Filter 作为 Spring 容器对象 在 SpringBoot 环境会利用 SCI 帮你注册
    4. 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)
            };
        }
    }