SpringBoot之Filter

上篇分析了Interceptor(拦截器),今天继续对Filter(过滤器)做一个分析。

何为过滤器

Filter是J2EE中来的,可以看做是Servlet的一种“加强版”,它主要用于对用户请求进行预处理和后处理,拥有一个典型的处理链。Filter也可以对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行预处理并生成响应,最后Filter再对服务器响应进行后处理。
通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截过滤。


SpringBoot之Filter_第1张图片

过滤器作用

在JavaDoc中给出了几种过滤器的作用:

 * Examples that have been identified for this design are
 * 1) Authentication Filters, 即用户访问权限过滤
 * 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等
 * 3) Image conversion Filters,图像转换过滤器
 * 4) Data compression Filters ,数据转换
 * 5) Encryption Filters ,安全加密
 * 6) Tokenizing Filters ,词法分析
 * 7) Filters that trigger resource access events ,资源访问事件触发过滤器
 * 8) XSL/T filters 
 * 9) Mime-type chain Filter ,文件类型链过滤器

过滤器的生命周期

Filter的生命周期:
Filter的创建:

Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

Filter的销毁:

Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

FilterConfig接口:

用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
  String getFilterName():得到filter的名称。
  String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
  Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
  public ServletContext getServletContext():返回Servlet上下文对象的引用

过滤器、拦截器和切面

Filter过滤器:拦截web访问url地址。依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据。比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问。依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于Web框架的调用。因此可以使用spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
Spring AOP拦截器:只能拦截Spring管理Bean的访问(业务层Service)


SpringBoot之Filter_第2张图片
获取信息的区别

Filter与Interceptor联系与区别

  1. 拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。
  2. 拦截器不依赖servlet容器,过滤器依赖于servlet容器。
  3. 拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
  4. 拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。

调用顺序:


SpringBoot之Filter_第3张图片

如上图,展示了三者的调用顺序Filter->Interceptor->Aspect->Controller。相反的是,当Controller抛出的异常的处理顺序则是从内到外的。因此我们总是定义一个注解@ControllerAdvice去统一处理控制器抛出的异常。如果一旦异常被@ControllerAdvice处理了,则调用拦截器的afterCompletion方法的参数Exception ex就为空了。


SpringBoot之Filter_第4张图片
访问流程

使用过滤器

根据 Filter 注册方式的不同,有注解、配置两种使用方式。若使用的是 Servlet3.0+版本,则两种方式均可使用;若使用的是 Servlet2.5版本,则只能使用配置类方式。
自定义的过滤器都必须实现javax.Servlet.Filter接口,并重写接口中定义的三个方法:

void init(FilterConfig config):用于完成Filter的初始化。

void destory():用于Filter销毁前,完成某些资源的回收。

void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):实现过滤功能,即对每个请求及响应增加的额外的预处理和后处理。执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。值得注意的是,chain.doFilter()方法执行之前为预处理阶段,该方法执行结束即代表用户的请求已经得到控制器处理。因此,如果在doFilter中忘记调用chain.doFilter()方法,则用户的请求将得不到处理。

1、配置方式实现过滤器:

先自定义filter实现Filter接口

import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class AccesLogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AccesLogFilter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requesturi = request.getRequestURI();

        log.info("Request URI:{}",requesturi);
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("AccesLogFilter destroy");
    }
}

再修改配置类


import com.example.demo.filter.AccesLogFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

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

    @Bean
    public FilterRegistrationBean registration() {
        //创建filter
        AccesLogFilter accesLogFilter = new AccesLogFilter();
        //注册过滤器
        FilterRegistrationBean registration = new FilterRegistrationBean<>(accesLogFilter);
        //添加条件
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }

}
2、注解方式实现过滤器

自定义filter实现Filter接口。
@Order(1):表示过滤器的顺序,假设我们有多个过滤器,你如何确定过滤器的执行顺序?这个注解就是规定过滤器的顺序。
@WebFilter:表示这个class是过滤器。里面的参数,filterName 为过滤器名字,urlPatterns 为过滤器的范围,initParams 为过滤器初始化参数。更多具体参数见下表


SpringBoot之Filter_第5张图片
@WebFilter参数表
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Order(1)
@WebFilter(filterName = "AccessLogFilter", urlPatterns = "/*" , initParams = {
        @WebInitParam(name = "URL", value = "http://localhost:8080")})
@Slf4j
public class AccessLogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AccesLogFilter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requesturi = request.getRequestURI();

        log.info("Request URI:{}",requesturi);
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("AccesLogFilter destroy");
    }
}

修改springboot启动入口:
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册。


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan(basePackages="com.example.demo.filter")
public class DemoApplication {

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

}

还可以通过@Component注解标注为组件自动注入bean。只要当前类在@ComponentScan的扫描范围内,就会自动注入此Filter,拦截路径为/*,拦截所有。

@Component
public class AccessLogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AccesLogFilter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requesturi = request.getRequestURI();

        log.info("Request URI:{}",requesturi);
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("AccesLogFilter destroy");
    }
}

Filter源码

直接上翻译后的注释

package javax.servlet;

import java.io.IOException;

/**
 * 过滤器是指拦截请求,并对传给被请求资源的ServletRequest 或 ServletResponse 进行处理的一个对象。
 * Examples that have been identified for this design are
 * 1) Authentication Filters, 即用户访问权限过滤
 * 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等
 * 3) Image conversion Filters,图像转换过滤器
 * 4) Data compression Filters ,数据转换
 * 5) Encryption Filters ,安全加密
 * 6) Tokenizing Filters ,词法分析
 * 7) Filters that trigger resource access events ,资源访问事件触发过滤器
 * 8) XSL/T filters 
 * 9) Mime-type chain Filter ,文件类型链过滤器
 */

public interface Filter {

    /** 
     *当过滤器启动服务的时候,比如应用程序启动时,servlet容器就会调用init方法。换句话说,  
     *不用等到调用与被过滤器相关的资源之后,才调用init方法。这个方法只调用一次,并且应该 
     *包含该过滤器的初始化代码。
     *init方法的签名如下:
     *void init(FilterConfig filterConfig)
     *注意:servlet容器给init方法传递了一个FilterConfig。
     */
    public void init(FilterConfig filterConfig) throws ServletException;
    
    
    /**
     * 每次调用与过滤器相关的资源时,servlet容器都会调用Filter实例的doFilter方法。该方法会 
     *收到一个ServletRequest、ServletResponse和FilterChain。
     *doFilter方法的签名如下:
     *public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
     *doFilter的实现可以访问ServletRequest 和 ServletResponse。因此,可以在 
     *ServletRequest中添加属性,或者在ServletResponse中添加一个标头,甚至可以对 
     *ServletRequest 或 ServletResponse进行修复,改变它们的行为。
     *     
     *doFilter方法实现中最后一行代码应该是调用FilterChain中的doFilter方法,
     *FilterChain.doFilter方法签名为
     *public void doFilter(ServletRequest request, ServletResponse response)
     *一个资源可以与多个过滤器关联,FilterChain.doFilter( ) 通常会引发调用链中的下一个过滤 
     *器被调用。在链中的最后一个过滤器中调用FilterChain.doFilter( )会引发资源本身被调用。 
     *如果你没有在 Filter.doFilter( )方法实现代码的最后调用FilterChain.doFilter( )方法,那么程 
     *序的处理将会在这里停止,并且不会调用请求。
     *注意,doFilter方法是FilterChain接口中唯一的方法,它与Filter中的doFilter方法不同。在 
     *FilterChain中,doFilter只有两个参数,而不是三个。
     */
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;


    /**
     * Filter中最后一个生命周期方法是destory,其方法签名为
     * destroy()
     *这个方法在过滤器即将终止服务之前,由Servlet调用,销毁过滤器对象,一般发生在应用程序停止的时候。
     */
    public void destroy();
}

由翻译来的源码注释可见,在应用程序启动时,servlet容器就会调用init方法。然后每次调用与过滤器相关的资源时,servlet容器都会调用doFilter方法,若是doFilter方法调用了FilterChain.doFilter方法,那么会引发调用链中的下一个过滤器被调用,一直到最后。当应用程序停止的时候,servlet会调用destroy(),销毁过滤器对象。

Filter的注册原理

servlet可以通过ServeltContext来注册Filter(当然还包括Servlet、Listener)到Servlet容器;至于注册到Servlet容器后,容器内部如何处理Filter以后再写。现在先这样认为:通过ServletContext注册Filter到容器,那么Filter就能起到过滤作用了。那么问题来了,springboot是如何将Filter注册到容器的?

在SpringBoot应用来说,是自身启动了一个Servlet引擎,并且需要创建一个与应用关联ServletContext对象绑定到Servlet引擎,从而使得Servlet引擎接收到请求可以分发到该应用来处理。
ServletContext内部通常会包含Servlet规范中的Servlet,Filter,Listener等组件,而将这些组件注册到ServletContext,在SpringBoot中主要通过三步来完成,分别是:
1、在应用代码定义和配置组件;
2、应用启动,获取这些组件,并生成对应的BeanDefinition注册到Spring容器;
3、从Spring容器取出这些组件Bean(取出过程中完成有BeanFactory调用getBean方法,基于BeanDefinition完成Bean对象的创建)并绑定到该ServletContext中。

RegistrationBean是SpringBoot提供的一个抽象类,是ServletContextInitializer接口的实现类,故在应用启动创建应用对应的内嵌的ServletContext时,会从Spring容器获取已经加载好的ServletContextInitializer接口实现类对象,然后对ServletContext进行初始化。

注意:

对于两种实现方式(配置类和注解类)来说,其实底层的实现都是一样的,都是基于RegistrationBean实现的,只是注解这种方式是SpringBoot在内部完成封装,而配置类方式是在应用代码显示使用FilterRegistrationBean(实现了RegistrationBean接口)的实现类进行操作。接下来分别看下两种方式:

先来看FilterRegistrationBean配置类方式:

FilterRegistrationBean:相当于Servlet 3.0+的ServletContext#addFilter(String, Filter)方法,主要用于自定义Filter过滤器来添加到当前的ServletContext,对请求进行过滤,源码如下:

/**
 * A {@link ServletContextInitializer} to register {@link Filter}s in a Servlet 3.0+
 * container. Similar to the {@link ServletContext#addFilter(String, Filter) registration}
 * features provided by {@link ServletContext} but with a Spring Bean friendly design.
 * 

* The {@link #setFilter(Filter) Filter} must be specified before calling * {@link #onStartup(ServletContext)}. Registrations can be associated with * {@link #setUrlPatterns URL patterns} and/or servlets (either by {@link #setServletNames * name} or via a {@link #setServletRegistrationBeans ServletRegistrationBean}s. When no * URL pattern or servlets are specified the filter will be associated to '/*'. The filter * name will be deduced if not specified. * * @param the type of {@link Filter} to register * @author Phillip Webb * @since 1.4.0 * @see ServletContextInitializer * @see ServletContext#addFilter(String, Filter) * @see DelegatingFilterProxyRegistrationBean */ public class FilterRegistrationBean extends AbstractFilterRegistrationBean { /** * Filters that wrap the servlet request should be ordered less than or equal to this. * @deprecated since 2.1.0 in favor of * {@code OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER} */ @Deprecated public static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = AbstractFilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER; private T filter; ... }

FilterRegistrationBean的类描述:一个用于向Servlet 3.0+容器注册Filter的ServletContextInitializer,类似于ServletContext提供的ServletContext#addFilter(String,Filter)注册功能,但具有Spring Bean特性的友好设计。相当于对ServletContext#addFilter进行了spring bean的友好性适配,本质还是ServletContext#addFilter。


SpringBoot之Filter_第6张图片
FilterRegistrationBean的类继承图

从类继承图也可以看出来,FilterRegistrationBean是实现类RegistrationBean的。


再来看看注解实现方式:

这种方式用@WebFilter和注解扫描@ServletComponentScan实现。
可以使用@WebServlet,@WebFilter,@WebListener注解运用在对应的组件类上面,注意组件类自身实现基于Servlet规范,如实现Filter接口,ServletListener接口等。然后需要在@Configuration注解的配置类中,加上@ServletComponentScan注解,用于扫描@WebServlet,@WebFilter,@WebListener这些注解的类来注册到Spring容器中。

其中创建BeanDefinition注册到Spring容器时,也是使用RegistrationBean的实现类,即ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean,对组件进行封装的,故也是基于RegistrationBean实现的,只是SpringBoot在内部完成封装,而不需要像上面一种方式一样在应用代码显示使用以上三个RegistrationBean的实现类进行操作,使用@WebServlet,@WebFilter,@WebListener注解定义,使用@ServletComponentScan来扫描即可。


接下来正式走注册流程:

Spring容器是通过ApplicationContext的refresh方法来定义启动步骤的。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        ...
        
    }
}

依次按顺序执行:
1:obtainFreshBeanFactory:创建BeanFactory和加载BeanDefintion;

2:invokeBeanFactoryPostProcessors:调用BeanFactoryPostProcessor,即BeanFactory后置处理器。ComponentScan进行相关类扫描是在这里完成的。以上两种方法创建Servlet,Filter和Listener,对应的BeanDefinition的创建并注册到BeanFactory是在这步完成的;

3:onRefresh:完成有特殊功能的bean实例的创建。从BeanFactory获取Servlet,Filter和Listener对应的BeanDefinition并创建Bean对象实例,然后绑定到ServletContext是在这步完成的。

Filter(包括Servlet和Listener)对应的BeanDefinition的创建并注册到BeanFactory这一步详细源码要分两种实现方式(配置法和注解法)来讨论,回头我会另起一篇来说。(链接:待定)

那么在注册后,接下来ApplicationContext的refresh方法会调用onRefresh方法,在这个方法中注册具有特殊含义的bean对象。

从onRefresh到调用每个ServletContextInitializer的onStartup方法

(先去spring的beanFactory中获取ServletContextInitializer的全部实例,并将其放入到ServletContextInitializerBeans的initializers中,然后遍历initializers,调用每个ServletContextInitializer的onStartup方法)

创建和启动应用内嵌的Servlet引擎WebServer,创建内嵌的ServletContext对象绑定到WebServer,创建Servlet,Filter和Listener对应的bean对象绑定到ServletContext就是在ServletWebServerApplicationContext类的onRefresh方法实现的。onRefresh方法的实现如下:

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

@Override
protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer();
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        
        // 创建webServer,ServletContext,
        // 以及获取ServletContext的ServletContextInitializer
        // 并执行其onStartup方法
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}

// getSelfInitializer调用该方法
// 从BeanFactory获取ServletContextInitializer接口的实现类
private void selfInitialize(ServletContext servletContext) throws ServletException {    
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
            servletContext);
    // 在getServletContextInitializerBeans方法内部实现:
    // 从BeanDefiniton创建bean对象实例,具体为调用了BeanFactory的getBean
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

从以上源码可知,在selfInitialize方法中,调用getServletContextInitializerBeans方法来从BeanFactory获取ServletContextInitializer接口的实现类,创建bean对象实例并执行onStartup方法。

从onStartup到调用ServletContext的addFilter方法将Filter注册到Servlet容器

其中RegistrationBean就实现了 ServletContextInitializer接口。RegistrationBean的onStartup方法实现如下:具体由子类实现register方法完成业务逻辑。对Servlet规范相关的Servlet,Filter,Listener,则是绑定到ServletContext。

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {

    private boolean enabled = true;

    @Override
    public final void onStartup(ServletContext servletContext) throws ServletException {
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description)
                    + " was not registered (disabled)");
            return;
        }
        // 将当前bean对象注册到servletContext
        register(description, servletContext);
    }

    // 抽象方法,由子类实现
    
    /**
     * Register this bean with the servlet context.
     * @param description a description of the item being registered
     * @param servletContext the servlet context
     */
    protected abstract void register(String description, ServletContext servletContext);

    ...
    
}

最终调用ServletContext的addFilter方法将Filter注册到Servlet容器,以下以ServletListenerRegistrationBean的register方法实现为例看看子类来调用servletContext.addListener完成绑定:

@Override
protected void register(String description, ServletContext servletContext) {
    try {
        servletContext.addListener(this.listener);
    }
    catch (RuntimeException ex) {
        throw new IllegalStateException(
                "Failed to add listener '" + this.listener + "' to servlet context",
                ex);
    }
}

综上,spring将(@Bean修饰的RegistrationBean)/(@WebFilter修饰的Fliter类)对应的BeanDefinition注册到beanFactory后,然后从beanFactory中获取全部的ServletContextInitializer,遍历它们并调用他们的onStartup方法将RegistrationBean中的bean注册到servlet容器。
完事!

参考博客:
https://www.cnblogs.com/youzhibing/p/9866690.html#_label2_0
https://blog.csdn.net/u010013573/article/details/86707091?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

你可能感兴趣的:(SpringBoot之Filter)