Java教程

Spring 源码解析 -- SpringWeb过滤器Filter解析

本文主要是介绍Spring 源码解析 -- SpringWeb过滤器Filter解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Spring 源码解析 -- SpringWeb过滤器Filter解析


简介

在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如Filter。本篇探索Filter初始化、请求处理等相关代码。

前言

说先简单的定义相关的测试代码:

启动类:

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

@ServletComponentScan
@SpringBootApplication
public class SpringExampleApplication {

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

Controller相关代码:

import com.example.springexample.vo.User;
import org.springframework.web.bind.annotation.*;

@RestController
public class HelloWorld {

    @GetMapping("/")
    public String helloWorld(@RequestParam(value = "id") Integer id,
                             @RequestParam(value = "name") String name) {
        return "Hello world:" + id;
    }

    @GetMapping("/test1")
    public String helloWorld1(@RequestParam(value = "id") Integer id) {
        return "Hello world:" + id;
    }

    @PostMapping("/test2")
    public String helloWorld2(@RequestBody User user) {
        return "Hello world:" + user.toString();
    }
}

Filter相关代码:

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@Slf4j
@Order(1)
@WebFilter(filterName = "MyFilter1", urlPatterns = "/test1")
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 1");
        chain.doFilter(request, response);
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@Slf4j
@Order(2)
@WebFilter(filterName = "MyFilter2", urlPatterns = "/test2")
public class MyFilter2 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 2");
        chain.doFilter(request, response);
    }
}

核心的代码如上,相关的请求Filter处理如下:

  • / : 两个Filter都不触发
  • /test1 : 触发MyFilter1
  • /test2 : 触发MyFilter2

符合我们的使用预期,接下来我们到源码中探索:

  • 1.Filter是如何初始化的
  • 2.Filter是如何对应相关的URL请求的

源码解析

探索时是直接在MyFilter类打上断点,一步步探索堆栈,得到的相关源码如下

Filter初始化

首先是找到Filter相关类获取并遍历的相关代码

下面的函数中,遍历获得了系统内置的和我们自己定义的Filter(如何获取的细节先不深究,在这里能得到所有的Filter)

    # ServletWebServerApplicationContext.class
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        this.prepareWebApplicationContext(servletContext);
        this.registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
	// 得到了所有的Filter,遍历处理
        Iterator var2 = this.getServletContextInitializerBeans().iterator();
        while(var2.hasNext()) {
            ServletContextInitializer beans = (ServletContextInitializer)var2.next();
            beans.onStartup(servletContext);
        }

    }

接下来来到添加注册Filter相关的代码部分

    # AbstractFilterRegistrationBean.class
    protected void configure(Dynamic registration) {
        super.configure(registration);
        EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
        if (dispatcherTypes == null) {
            T filter = this.getFilter();
            if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter", filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
                dispatcherTypes = EnumSet.allOf(DispatcherType.class);
            } else {
                dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
            }
        }

        Set<String> servletNames = new LinkedHashSet();
        Iterator var4 = this.servletRegistrationBeans.iterator();
	// 这部分代码作用尚不明确,留待以后探索
        while(var4.hasNext()) {
            ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next();
            servletNames.add(servletRegistrationBean.getServletName());
        }

        servletNames.addAll(this.servletNames);
        if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
	    // 系统默认的都走的这部分处理,拦截路径默认都是:/**
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
        } else {
            if (!servletNames.isEmpty()) {
                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames));
            }
	    // 我们自定义的都都了这里,拦截路径就是我们配置的:/test1,/test2
            if (!this.urlPatterns.isEmpty()) {
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
            }
        }

    }

在上面的代码中,我们看到了拦截路径的配置有两个方式:

  • servletNames
  • urlPatterns

接着跟下去,下降就是将Filter添加

    # ApplicationFilterRegistration.java
    public void addMappingForUrlPatterns(
            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
            String... urlPatterns) {

        FilterMap filterMap = new FilterMap();

        filterMap.setFilterName(filterDef.getFilterName());

        if (dispatcherTypes != null) {
            for (DispatcherType dispatcherType : dispatcherTypes) {
                filterMap.setDispatcher(dispatcherType.name());
            }
        }

	// Filter添加的相关代码
        if (urlPatterns != null) {
            // % decoded (if necessary) using UTF-8
            for (String urlPattern : urlPatterns) {
                filterMap.addURLPattern(urlPattern);
            }

            if (isMatchAfter) {
                context.addFilterMap(filterMap);
            } else {
                context.addFilterMapBefore(filterMap);
            }
        }
        // else error?
    }

上面的代码中,Filter添加有两处代码:一个是添加到Map中,一个是context中,后者好像还是有其他道道,后面继续研究看看

到这里,Filter就初始化完成了,下面看看使用方面的代码

Filter匹配添加

在日常开发中,Filter我们都会配置相关的匹配路径,不是所有的请求都进行过滤,那这块的匹配是怎么的?接下来就发起请求,探索Filter的匹配添加

下面的代码是核心的Filter匹配处理,但前面的触发调用目前暂时还没有梳理清楚,Wrapper好像挺关键的,暂时忽略它,先看Filter匹配处理相关的

    # ApplicationFilterFactory.java
    public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
	......
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
	// 获取Filter,得到上面初始化的Filter
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0)) {
            return filterChain;
        }

        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

	// 请求的路径
        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

	// 在这里就进行匹配了
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

    // 上面的触发调用后,就来到了下面的将Filter添加到列表中的相关diam
    # ApplicationFilterChain.java
    void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters) {
            if(filter==filterConfig) {
                return;
            }
        }

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }

上面的就是核心的Filter匹配添加的核心代码,值得注意的点有下面几个:

  • 会被匹配添加两次
  • 匹配有下面三种方式:
    • matchDispatcher(filterMap, dispatcher)
    • matchFiltersURL(filterMap, requestPath)
    • matchFiltersServlet(filterMap, servletName)

这里就有下面两点疑问了:

  • 为啥需要将两次匹配分开:是为了前后Filter区分?
  • 两次Filter循环匹配,好像就是匹配路径requestPath和匹配ServletName的区别,两者有何不同,为啥需要分开?

关于上面的疑问目前我也没找到确定的线索,后面的探索中,应该能把它补上

Filter触发

经过上面的Filter匹配,请求的Filter就初始化好了,下面就进入到处理调用环节

    # ApplicationFilterChain.java
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                        (java.security.PrivilegedExceptionAction<Void>) () -> {
                            internalDoFilter(req,res);
                            return null;
                        }
                );
            } catch( PrivilegedActionException pe) {
		    ......
            }
        } else {
	    // 调用触发
            internalDoFilter(request,response);
        }
    }

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
		// 获取当前Filter
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
		    // 调用触发
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
		    ......
            }
            return;
        }

        // 下面的代码有疑似结束Filter,触发请求函数处理相关的代码。先打个标记,后面探索
        // We fell off the end of the chain -- call the servlet instance
	......
    }

上面就是Filter调用触发的核心代码了,链式触发调用,在SpringCloudGateway和Netty中都有类型的相关代码,看着这种代码模式很经典啊,但细节就后面研究了

总结

经过上面的代码分析,Filter的基本核心代码已经被我们找到了:

  • 1.Filter的初始化:在应用程序启动时,进行Filter的初始化
  • 2.Filter的匹配添加:对于不用的请求路径,会匹配生成不同的Filter链路
    • 其中有用缓存吗:经过实验,每次请求都会进行匹配
  • 3.链式调用处理

其中还有很多疑问点,还有Order排序相关的好像没有找到,感兴趣的可以自行查找下

参考链接

  • Spring 源码解析系列
这篇关于Spring 源码解析 -- SpringWeb过滤器Filter解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!