ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-08-01 13:02:11  阅读:141  来源: 互联网

标签:filter -- 代码 request Filter 源码 import 解析 filterMap


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排序相关的好像没有找到,感兴趣的可以自行查找下

参考链接

标签:filter,--,代码,request,Filter,源码,import,解析,filterMap
来源: https://www.cnblogs.com/freedom-only/p/15086478.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有