SpringMVC源碼閱讀:過濾器

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧javascript

本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC過濾器是如何執行的,並自定義過濾器,分清楚過濾器和攔截器的區別html

2.源碼分析

web.xml配置前端

    <filter>
        <!--過濾器名稱-->
        <filter-name>Set Character Encoding</filter-name>
        <!--過濾器處理類-->
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--初始化參數-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Set Character Encoding</filter-name>
        <!--攔截路徑-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

打開CharacterEncoding類,該類爲request設置字符編碼,打開類繼承圖java

CharacterEncodingFilter重寫父類OncePerRequestFilter的doFilterInterval方法jquery

forceRequestEncoding爲Truegit

188行獲取編碼github

191行爲request設置編碼web

194行爲response設置編碼ajax

197行調用FilterChainspring

打開CharcterEncodingFilter的父類OncePerRequestFilter,OncePerRequestFilter保證請求分發執行一次Filter

CharcterEncodingFilter實現Filter接口的doFilter方法,打開之

96行獲取屬性,該屬性代表這個方法是否被過濾

98行hasAlreadyFilteredAttribute代表這個方法是否被過濾;skipDispatch方法判斷是否跳過度發;shouldNotFilter方法默認返回False,被子類重寫,然而,CharacterEncodingFilter類並未重寫,ForwardedHeaderFilter重寫該方法,暫時不看

101行直接調用FilterChain的doFilter方法,FilterChain部分

98行的上述條件都不知足,則進入105行,爲request設置「已過濾」標識

107行調用子類CharacterEncodingFilter的doFilterInterval方法

繼續深刻,打開OncePerRequestFilter類的父類GenericFilterBean類

重點看init方法,該方法啓動服務纔會進入,主要負責獲取web.xml裏配置的參數

 

180行獲取FilterConfig

filterClass和filterName是咱們在web.xml配置的屬性

184行獲取<init-param>裏屬性

 

185行聲明BeanWrapper,一個通用的JavaBean接口,封裝了setget方法,在SpringMVC源碼閱讀:屬性編輯器、數據綁定我已經講過

186行聲明資源加載器,加載classpath和文件系統

189行給屬性設置True

199行initFilterBean方法被子類重寫,用於補充初始化方法,OncePerRequestFilter未重寫,咱們暫時不看

3.自定義過濾器

如今Web項目基本都是先後端分離的,不可避免地要解決跨域問題。看Filter接口繼承圖,發現CorsFilter能夠處理跨域,可是並不方便

什麼是跨域?

前端地址和後臺地址的

IP一致,端口不一致

IP不一致,端口不一致

IP不一致,端口一致

都屬於跨域範疇

前端代碼以下

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <!-- <link rel="stylesheet" href=""> -->
    <style>
    </style>
</head>

<body>
    <script></script>
    <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
    <script type="text/javascript">
        $.ajax({
            url: "http://localhost:8080/springmvcdemo/employee/detail/8",
            data: {},
            success: function (data) 
            {
                console.log(html)
            },
            dataType: "json"
        });
    </script>

</body>

</html>

將前端代碼地址設置爲http://localhost:1234,VSCode能夠作,不贅述。後臺代碼地址http://localhost:8080,由於端口不一致,造成了跨域,不作跨域處理,直接訪問有以下效果

如今咱們來自定義過濾器解決跨域問題

3.1 自定義過濾器繼承OncePerRequestFilter

web.xml

    <filter>
        <!--過濾器名稱-->
        <filter-name>CorsFilter</filter-name>
        <!--過濾器處理類-->
        <filter-class>org.format.demo.custom.ExtendedCorsFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <!--攔截路徑-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

ExtendedCorsFilter.java,重寫doFilterInterval方法

public class ExtendedCorsFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        response.addHeader("Access-Control-Allow-Headers", "Content-Type");
        filterChain.doFilter(request,response);
    }

}

此時再運行前端代碼,已經成功請求到結果

Headers設置成功

3.2 自定義過濾器實現Filter接口

web.xml配置以下

 <filter>
        <!--過濾器名稱-->
        <filter-name>CorsFilter</filter-name>
        <!--過濾器處理類-->
        <filter-class>org.format.demo.custom.ImplementedCorsFilter</filter-class>
        <!--初始化參數-->
        <init-param>
            <param-name>allowedOrigins</param-name>
            <param-value>*</param-value>
        </init-param>
        <init-param>
            <param-name>allowedMethods</param-name>
            <param-value>*</param-value>
        </init-param>
        <init-param>
            <param-name>allowedHeaders</param-name>
            <param-value>*</param-value>
        </init-param>
    </filter>

 

ImplementedCorsFilter.java

public class ImplementedCorsFilter implements Filter{
    //容許的請求域
    private String allowedOrigins;
    //容許的請求方法
    private String allowedMethods;
    //容許的請求頭
    private String allowedHeaders;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.allowedOrigins = filterConfig.getInitParameter("allowedOrigins");
        this.allowedMethods = filterConfig.getInitParameter("allowedMethods");
        this.allowedHeaders = filterConfig.getInitParameter("allowedHeaders");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse rsp = (HttpServletResponse) response;
        rsp.setHeader("Access-Control-Allow-Origin", allowedOrigins);
        rsp.setHeader("Access-Control-Allow-Methods", allowedMethods);
        rsp.setHeader("Access-Control-Allow-Headers", allowedHeaders);

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

相比繼承OncePerRequestFilter方法,實現Filter接口能夠經過重寫init方法獲取web.xml屬性值,效果一致

4.過濾器(Filter)和攔截器(Interceptor)的區別

我在SpringMVC源碼閱讀:攔截器詳細講解了SpringMVC攔截器的工做原理

過濾器先於攔截器執行,後於攔截器執行結束

4.1 過濾器:

  依賴於servlet容器。在實現上基於函數回調,能夠對幾乎全部請求進行過濾,可是缺點是一個過濾器實例只能在容器初始化時調用一次。使用過濾器的目的是用來作一些過濾操做,獲取咱們想要獲取的數據.

  好比:在過濾器中修改字符編碼;在過濾器中修改HttpServletRequest的一些參數,包括:過濾低俗文字、危險字符等

4.2 攔截器:

  依賴於web框架,在SpringMVC中就是依賴於SpringMVC框架。在實現上基於Java的反射機制,屬於面向切面編程(AOP)的一種運用。因爲攔截器是基於Web框架的調用。

  所以可使用spring的依賴注入(DI)進行一些業務操做,同時一個攔截器實例在一個controller生命週期以內能夠屢次調用。可是缺點是隻能對controller請求進行攔截,對其餘的一些好比直接訪問靜態資源的請求則沒辦法進行攔截處理。

總結:業務中儘可能使用基於方法的攔截器,在進行一些須要統一處理的業務可使用基於Servlet的過濾器

5.總結:

Filter接口用來執行過濾任務

CompositeFilter實現filter,用到了組合設計模式

抽象類GenericFilterBean實現Filter接口,負責解析web.xml的Filter的init-param中參數,是全部過濾器的父類。init方法解析web.xml的參數

抽象類OncePerRequestFilter繼承GenericFilterBean,doFilter方法根據hasAlreadyFilteredAttribute判斷是否執行過濾

CharacterEncodingFilter重寫父類OncePerRequestFilter的doFilterInterval方法,調用FilterChain的doDilter方法執行過濾邏輯

其餘過濾器園友能夠自行查看,再也不贅述,demo源碼

6.參考

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

https://docs.spring.io/spring/docs/current/javadoc-api/

https://github.com/spring-projects/spring-framework

文中不免有不足,歡迎指正

相關文章
相關標籤/搜索