http://liuluo129.iteye.com/blog/1965268
圖來自上面連接
http://blog.csdn.net/geloin/article/details/7441330
整理於上面
Spring的web包中中有很多過濾器,這些過濾器位於org.springframework.web.filter並且理所當然地實現了javax.servlet.Filter,不過實現的方式有以下幾類:
(1) 直接實現Filter,這一類過濾器只有CompositeFilter;
(2) 繼承抽象類GenericFilterBean,該類實現了javax.servlet.Filter,這一類的過濾器只有一個,即DelegatingFilterProxy;
(3) 繼承抽象類OncePerRequestFilter,該類爲GenericFilterBean的直接子類,這一類過濾器包括CharacterEncodingFilter、HiddenHttpMethodFilter、HttpPutFormContentFilter、RequestContextFilter和ShallowEtagHeaderFilter;
(4) 繼承抽象類AbstractRequestLoggingFilter,該類爲OncePerRequestFilter的直接子類,這一類過濾器包括CommonsRequestLoggingFilter、Log4jNestedDiagnosticContextFilter和ServletContextRequestLoggingFilter。
本文要講述的,即是GenericFilterBean、OncePerRequestFilter和AbstractRequestLoggingFilter。
GenericFilterBean
抽象類GenericFilterBean實現了javax.servlet.Filter、org.springframework.beans.factory.BeanNameAware、org.springframework.context.EnvironmentAware、org.springframework.web.context.ServletContextAware、org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean五個接口,作用如下:
(1) Filter,實現過濾器;
(2) BeanNameAware,實現該接口的setBeanName方法,便於Bean管理器生成Bean;
(3) EnvironmentAware,實現該接口的setEnvironment方法,指明該Bean運行的環境;
(4) ServletContextAware,實現該接口的setServletContextAware方法,指明上下文;
(5) InitializingBean,實現該接口的afterPropertiesSet方法,指明設置屬性生的操作;
(6) DisposableBean,實現該接口的destroy方法,用於回收資源。
GenericFilterBean的工作流程是:init-doFilter-destory,其中的init和destory在該類中實現,doFilter在具體實現類中實現。init的代碼如下:
- /**
- * Standard way of initializing this filter.
- * Map config parameters onto bean properties of this filter, and
- * invoke subclass initialization.
- * @param filterConfig the configuration for this filter
- * @throws ServletException if bean properties are invalid (or required
- * properties are missing), or if subclass initialization fails.
- * @see #initFilterBean
- */
- public final void init(FilterConfig filterConfig) throws ServletException {
- Assert.notNull(filterConfig, "FilterConfig must not be null");
- if (logger.isDebugEnabled()) {
- logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
- }
-
- this.filterConfig = filterConfig;
-
- // Set bean properties from init parameters.
- try {
- PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
- BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
- ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
- bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
- initBeanWrapper(bw);
- bw.setPropertyValues(pvs, true);
- }
- catch (BeansException ex) {
- String msg = "Failed to set bean properties on filter '" +
- filterConfig.getFilterName() + "': " + ex.getMessage();
- logger.error(msg, ex);
- throw new NestedServletException(msg, ex);
- }
-
- // Let subclasses do whatever initialization they like.
- initFilterBean();
-
- if (logger.isDebugEnabled()) {
- logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
- }
- }
該方法來自於javax.servlet.Filter,即過濾器的初始化,它的主要工作集中於以下幾行代碼:
- // 從properties文件中獲取值,這裏是web.xml
- PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
- // 設置bean適配器
- BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
- // 設置上下文,這裏的servletContext的設定繼承自ServletContextAware的setter
- ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
- // 將上下文信息和環境信息設置到bean適配器中,這裏的environment來自於EnvironmentAware的setter
- bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
- // 初始化bean適配器
- initBeanWrapper(bw);
- // 將從properties中獲取的資源放置到bean適配器
- bw.setPropertyValues(pvs, true);
- // 初始化bean
- initFilterBean();
其中initFilterBean方法在兩個位置起作用,一處是上文所述的init方法,另一處是afterPropertiesSet方法,在調用該方法前,需要保證用於Filter的所有的bean都已被設置,該方法由子類實現。
GenericFilterBean中包含一個內部私有類FilterConfigPropertyValues,主要用於將web.xml中定義的init-param的值取出。
OncePerRequestFilter
抽象類oncePerRequestFilter繼承自GenericFilterBean,它保留了GenericFilterBean中的所有方法並對之進行了擴展,在oncePerRequestFilter中的主要方法是doFilter,代碼如下:
- /**
- * This <code>doFilter</code> implementation stores a request attribute for
- * "already filtered", proceeding without filtering again if the
- * attribute is already there.
- * @see #getAlreadyFilteredAttributeName
- * @see #shouldNotFilter
- * @see #doFilterInternal
- */
- public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
-
- if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
- throw new ServletException("OncePerRequestFilter just supports HTTP requests");
- }
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- HttpServletResponse httpResponse = (HttpServletResponse) response;
- // 調用GenericFilterBean的getFilterName方法返回已過濾的屬性名
- String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
- if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(httpRequest)) {
- // 未調用該過濾器或已過濾
- filterChain.doFilter(request, response);
- }
- else {
- // 進行過濾
- request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
- try {
- doFilterInternal(httpRequest, httpResponse, filterChain);
- }
- finally {
- // Remove the "already filtered" request attribute for this request.
- request.removeAttribute(alreadyFilteredAttributeName);
- }
- }
- }
在doFilter方法中,doFilterInternal方法由子類實現,主要作用是規定過濾的具體方法。
AbstractRequestLoggingFilter
AbstractRequestLoggingFilter繼承了OncePerRequestFilter並實現了其doFilterInternal方法,該方法代碼如下:
- /**
- * Forwards the request to the next filter in the chain and delegates down to the subclasses to perform the actual
- * request logging both before and after the request is processed.
- *
- * @see #beforeRequest
- * @see #afterRequest
- */
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- if (isIncludePayload()) {
- // 若日誌中包含負載,則重置request
- request = new RequestCachingRequestWrapper(request);
- }
- // 過濾前執行的方法
- beforeRequest(request, getBeforeMessage(request));
- try {
- // 執行過濾
- filterChain.doFilter(request, response);
- }
- finally {
- // 過濾後執行的方法
- afterRequest(request, getAfterMessage(request));
- }
- }
doFilter方法中的beforeRequest和afterRequest方法由子類實現,RequestCachingRequestWrapper爲AbstractRequestLoggingFilter的內部內,主要作用是重置request。
區別
我們在使用過濾器時,通常沒必要知道GenericFilterBean、OncePerRequestFilter和AbstractRequestLoggingFilter,但不防礙我們瞭解這幾個類,就上文所述,
AbstractRequestLoggingFilter繼承自OncePerRequestFilter,
OncePerRequestFilter繼承自GenericFilterBean,
所以我們知道,genericFilterBean是任何類型的過濾器的一個比較方便的超類,
這個類主要實現的就是從web.xml文件中取得init-param中設定的值,然後對Filter進行初始化(當然,其子類可以覆蓋init方法)。
OncePerRequestFilter繼承自GenericFilterBean,那麼它自然知道怎麼去獲取配置文件中的屬性及其值,所以其重點不在於取值,而在於確保在接收到一個request後,每個filter只執行一次,它的子類只需要關注Filter的具體實現即doFilterInternal。
AbstractRequestLoggingFilter是對OncePerRequestFilter的擴展,它除了遺傳了其父類及祖先類的所有功能外,還在doFilterInternal中決定了在過濾之前和之後執行的事件,它的子類關注的是beforeRequest和afterRequest。
總體來說,這三個類分別執行了Filter的某部分功能,當然,具體如何執行由它們的子類規定,若你需要實現自己的過濾器,也可以根據上文所述繼承你所需要的類
==============================================================================================================
Spring MVC過濾器-上下文信息過濾器(AbstractRequestLoggingFilter及其子類)
http://blog.csdn.net/geloin/article/details/7443329
AbstractRequestLoggingFilter類定義了兩個方法beforeRequest和afterRequest分別用於設定過濾前後執行的操作,
它有三個子類,
分別是CommonsRequestLoggingFilter、
ServletContextRequestLoggingFilter
和Log4jNestedDiagnosticContextFilter,
這三個子類分別實現了各自的beforeRequest和afterRequest。其中,
CommonsRequestLoggingFilter在過濾前後分別打印出一段debug的信息;
ServletContextRequestLoggingFilter在過濾前後分別向日志文件中寫入一段日誌信息,日誌文件可由log4j.properties等指定;
Log4jNestedDiagnosticContextFilter則將日誌信息存儲到NDC中,NDC採用了一個類似棧的機制來push和pot上下文信息,每一個線程都獨立地儲存上下文信息,比如說一個servlet就可以針對 每一個request創建對應的NDC,儲存客戶端地址等信息。
當然,在AbstractRequestLoggingFilter中,還包含很多其他方法:
setIncludeQueryString:查詢語句是否包含在日誌文件中,true或false;
setIncludeClientInfo:客戶地址和session id是否包含在日誌中,true或false;
setIncludePayload:負載信息是否包含在日誌信息中,true或false;
setMaxPayloadLength:最大負載量,int值;
setBeforeMessagePrefix:日誌信息前的信息的前綴;
setBeforeMessageSuffix:日誌信息前的信息的後綴;
setAfterMessagePrefix:日誌信息後的信息的前綴;
setAfterMessageSuffix:日誌信息後的信息的後綴。
以上這些信息均可以在init-param中進行設置。
日誌信息在getBeforeMessage和getAfterMessage方法中通過createMessage創建,然後被doFilterInternal調用,
日誌信息中至少包含uri信息,其他信息則根據init-param中所設定的值獲取,
例如,當includeQueryString的值爲true時,日誌信息中除uri信息外,還包含query string信息。
由此可以,在spring中已定義的AbstractRequestLoggingFiter的子類過濾器均是用於輸入上下文信息,只不過承載的方式不同,
其中CommonsRequestLoggingFilter將上下文信息直接打印;
ServletContextRequestLoggingFilter將上下文信息寫入日誌文件;
Log4jNestedDiagnosticContextFilter將上下文信息寫入NDC中
==============================================================================================================
Spring MVC過濾器-HiddenHttpMethodFilter
http://blog.csdn.net/geloin/article/details/7444321
因爲很少有人用到GET和POST以外的method, 而且只有部分瀏覽器不支持部分method put,deletede等
但是爲了做到程序兼容性更好,spring3.0添加了一個過濾器HiddenHttpMethodFilter,可以將這些請求轉換爲標準的http方法,使得支持PUT與DELETE,就像可以使用GET、POST、一樣,
HiddenHttpMethodFilter的父類是OncePerRequestFilter,它繼承了父類的doFilterInternal方法,工作原理是
將jsp頁面的form表單的method屬性值在doFilterInternal方法中轉化爲標準的Http方法,即GET,、POST、 HEAD、OPTIONS、PUT、DELETE、
TRACE,然後到Controller中找到對應的方法。
例如,在使用註解時我們可能會在Controller中用於@RequestMapping(value = "list", method = RequestMethod.PUT),
所以如果你的表單中使用的是<form ...,那麼這個表單會被提交到標了Controller的Method="PUT"的方法中
需要注意的是,由於doFilterInternal方法只對method爲post的表單進行過濾,所以在頁面中必須如下設置:
- <form action="..." method="<strong>post</strong>">
- <input type="hidden" name="_method" value="<strong>put</strong>" />
- ......
- </form>
而不是使用:
- <form action="..." method="put">
- ......
- </form>
同時,HiddenHttpMethodFilter必須作用於dispatcher前,所以在web.xml中配置HiddenHttpMethodFilter時,需參照如下代碼:
- <filter>
- <filter-name>HiddenHttpMethodFilter</filter-name>
- <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>HiddenHttpMethodFilter</filter-name>
- <servlet-name>spring</servlet-name>
- </filter-mapping>
- <servlet>
- <servlet-name>spring</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:dispatcher.xml</param-value>
- </init-param>
- lt;/servlet>
- <servlet-mapping>
- <servlet-name>spring</servlet-name>
- <url-pattern>*.html</url-pattern>
- lt;/servlet-mapping>
同樣的,作爲Filter,可以在web.xml中配置HiddenHttpMethodFilter的參數,可配置的參數爲methodParam,
如果不在web.xml裏重新配置的話,default的值爲 「_method」,而他在hidden裏對應的value爲http method的任意一個即可
- <input type="hidden" name="_method" value="<strong>put</strong>" />
如果不想用default的「_method」,可以在web.xml裏重新配置,比如abcd_abc,提供這個修改可能是爲了防止你的form表單裏如果已經有「_method」的定義了,不能用於HiddenHttpMethodFilter實現的目的,通過它可以重寫
- <input type="hidden" name="abcd_abc" value="<strong>put</strong>" />
==============================================================================================================
Spring MVC過濾器-HttpPutFormContentFilter
http://blog.csdn.net/geloin/article/details/7444590
在Spring MVC過濾器-HiddenHttpMethodFilter中我們提到,jsp或者說html中的form的method值只能爲post或get,我們可以通過HiddenHttpMethodFilter獲取put表單中的參數-值
而在Spring3.0中獲取put表單的參數-值還有另一種方法,即使用HttpPutFormContentFilter過濾器
HttpPutFormContentFilter過濾器的作爲就是獲取put表單的值,並將之傳遞到Controller中標註了method爲RequestMethod.put的方法中。
在web.xml中配置HttpPutFormContentFilter的代碼類似如下:
- <filter>
- <filter-name>httpPutFormcontentFilter</filter-name>
- <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>httpPutFormContentFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
需要注意的是,該過濾器只能接受enctype值爲application/x-www-form-urlencoded的表單,也就是說,在使用該過濾器時,form表單的代碼必須如下
- <form action="" method="put" enctype="<strong>application/x-www-form-urlencoded</strong>">
- ......
- </form>
==============================================================================================================
ShallowEtagHeaderFilter
ShallowEtagHeaderFilter是spring提供的支持ETag的一個過濾器,所謂ETag是指被請求變量的實體值,是一個可以與Web資源關聯的記號,而Web資源可以是一個Web頁,也可以是JSON或XML文檔,服務器單獨負責判斷記號是什麼及其含義,並在HTTP響應頭中將其傳送到客戶端,以下是服務器端返回的格式:
ETag:"50b1c1d4f775c61:df3"
客戶端的查詢更新格式是這樣的:
If-None-Match : W / "50b1c1d4f775c61:df3"
如果ETag沒改變,則返回狀態304然後不返回,這也和Last-Modified一樣。
ShallowEtagHeaderFilter會將JSP等的內容緩存,生成MD5的key,然後在response中作爲Etage的header返回給客戶端。下次客戶端對相同的資源(或者說相同的url)發出請求時,客戶端會將之前生成的key作爲If-None-Match的值發送到server端。 Filter會客戶端傳來的值和服務器上的做比較,如果相同,則返回304;否則,將發送新的內容到客戶端。
查看ShallowEtagHeaderFilter的源碼如下:
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException{
- ShallowEtagResponseWrapper responseWrapper = new ShallowEtagResponseWrapper(response, null);
- filterChain.doFilter(request, responseWrapper);
- // 由此可知,服務器仍會處理請求
- byte[] body = responseWrapper.toByteArray();
- int statusCode = responseWrapper.getStatusCode();
-
- if (isEligibleForEtag(request, responseWrapper, statusCode, body)) {
- String responseETag = generateETagHeaderValue(body);
- response.setHeader(HEADER_ETAG, responseETag);
-
- String requestETag = request.getHeader(HEADER_IF_NONE_MATCH);
- if (responseETag.equals(requestETag)) {
- if (this.logger.isTraceEnabled()) {
- this.logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");
- }
- response.setStatus(304);
- }
- else {
- if (this.logger.isTraceEnabled())&nbt-width:3px;border-style:none none none solid;border-left-color:rgb(108,226,108);list-style-position:outside;color:inherit;line-height:18px;padding:0px 3px 0px 10px;margin-right:0px;margin-bottom:0px;"> if (responseETag.equals(requestETag)) {
- if (this.logger.isTraceEnabled()) {
- this.logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");
- }
- response.setStatus(304);
- }
- else {
- if (this.logger.isTraceEnabled()) {
- this.logger.trace("ETag [" + responseETag + "] not equal to If-None-Match [" + requestETag + "], sending normal response");
- }
- copyBodyToResponse(body, response);
- }
- }
- else {