1. 程式人生 > >Java過濾器鏈原理解析

Java過濾器鏈原理解析

在很多Java Web專案中我們會在web.xml中配置一些過濾器來攔截請求,比如下面解決亂碼的編碼過濾器:

<filter>
	<filter-name>encodingFilter</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>encodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
但是讓我一直困惑的是,這些過濾器是由誰來呼叫的?又是誰將它們組織起來,並確保他們的執行順序的?
今天使用Spring cloud用程式碼的方法配置過濾器的時候(不再是配到web.xml中,Spring clound自帶web容器,提供開箱即用的服務,所以以前很多需要配到web容器的配置都交給Spring來管理了,在學習Spring cloud的時候發現這貨真的是想統一全世界呀-_-),發現自己定義的一個過濾器死活不執行,就打算將這個問題深挖到底。

過濾器demo如下(為保護公司祕密,重要程式碼已刪除,各位見諒哈):

@WebFilter(filterName="FilterDemo", urlPatterns={"/**"})
@Order(1)	//當有多個filter時,指定filter的順序
public class SecurityFilter implements Filter{
	private static final Logger LOGGER = LoggerFactory.getLogger( FilterDemo.class );
	
	@Override
	public void destroy() {
		
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		//TODO do some filter action;
		chain.doFilter(request, response);
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
	}

}
本以為這樣寫了之後過濾器就能工作了,可是請求進來以後發現過濾器未執行。後來想到Spring的工作都是交給註冊到beanFacotry中的一個個bean來完成的,所以可能需要把這個過濾器註冊到beanFactory中,就像這樣:
@Configuration   //表明這是一個Spring配置檔案
@EnableSwagger2  //swagger是一個restful介面的文件線上自動生成+功能測試功能框架
@RefreshScope    //允許當Spring cloud config配置檔案改動之後,依賴配置的bean自動更新而不用重啟服務
public class GlobalBeanConfig {

    //將filter加入到Spring配置中,否則只把filter類寫出來,不加入配置還是無法起作用
    @Bean  //將xml形式的<bean></bean>採用程式碼來配置
    public Filter filterDemo(){
        return new FilterDemo();
    }

    @Bean(name = "loggerInteceptor")
    public GlobalAspectInteceptor getLoggerInteceptor() {
        return new GlobalAspectInteceptor();
    }

    @Bean
    public ThreadPoolTaskExecutor globalTaskExecutor(
            @Value("${global.thread.pool.corePoolSize}") Integer corePoolSize,
            @Value("${global.thread.pool.maxPoolSize}") Integer maxPoolSize,
            @Value("${global.thread.pool.queueCapacity}") Integer queueCapacity
    ) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize( corePoolSize );
        executor.setMaxPoolSize( maxPoolSize );
        executor.setQueueCapacity( queueCapacity );
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadGroupName("globalTaskExecutor");
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.DiscardPolicy() );
        executor.initialize();
        return executor;
    }
}
加了註解之後果然立馬見效,看來我的直覺還是挺準的,嘻嘻~_~

好了,既然我們知道怎麼用工具了,下面就來拆工具,看看how it works。通過斷點除錯,發現過濾器中兩個關鍵的類在起作用:ApplicationFilterConfig,ApplicationFilterChain。

這兩個類是org.apache.catalina.core包下面的,可以肯定是tomcat容器來管理過濾器鏈了。

接下來看ApplicationFilterConfig,先上部分原始碼:

public final class ApplicationFilterConfig implements FilterConfig, Serializable {
    private static final long serialVersionUID = 1L;
static final StringManager sm = StringManager.getManager("org.apache.catalina.core");
private static final Log log = LogFactory.getLog(ApplicationFilterConfig.class);
private static final List<String> emptyString = Collections.emptyList();
private final transient Context context;
private transient Filter filter = null;
private final FilterDef filterDef;
private transient InstanceManager instanceManager;
private ObjectName oname;
ApplicationFilterConfig(Context context, FilterDef filterDef) throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException, InvocationTargetException, NamingException, IllegalArgumentException, NoSuchMethodException, SecurityException {
        this.context = context;
this.filterDef = filterDef;
if(filterDef.getFilter() == null) {
            this.getFilter();
} else {
            this.filter = filterDef.getFilter();
this.getInstanceManager().newInstance(this.filter);
this.initFilter();
}
    }
}
通過分析可以發現,這個類是就是用來持有一個過濾器的,構造方法傳入的context想必就是tomcat配置檔案下的context.xml配置的應用環境,fliterDef就是過濾器的描述資訊,應該是通過配置在web.xml中(或者其他什麼地方)的filter引數來構造的。

好,重點來啦-ApplicationFilterChain,名字就暴露了它的作用,就是它將一個個分散的過濾器組織起來的。結合上面我猜測,它裡面應該有一個數組或列表來儲存ApplicationFilterConfig,還有一個過濾器遊標,來記錄當前過濾器走到哪兒了。原始碼(部分)如下:

public final class ApplicationFilterChain implements FilterChain {
    private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;
public static final int INCREMENT = 10;
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private int pos = 0;
private int n = 0;
private Servlet servlet = null;
private boolean servletSupportsAsync = false;
private static final StringManager sm;
private static final Class<?>[] classType;
private static final Class<?>[] classTypeUsedInService;
public ApplicationFilterChain() {
    }

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if(Globals.IS_SECURITY_ENABLED) {
            final ServletRequest req = request;
final ServletResponse res = response;
try {
                AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Void run() throws ServletException, IOException {
                        ApplicationFilterChain.this.internalDoFilter(req, res);
return null;
}
                });
} catch (PrivilegedActionException var7) {
                Exception e = var7.getException();
if(e instanceof ServletException) {
                    throw (ServletException)e;
}

                if(e instanceof IOException) {
                    throw (IOException)e;
}

                if(e instanceof RuntimeException) {
                    throw (RuntimeException)e;
}

                throw new ServletException(e.getMessage(), e);
}
        } else {
            this.internalDoFilter(request, response);
}

    }

    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if(this.pos < this.n) {
            ApplicationFilterConfig e1 = this.filters[this.pos++];
try {
                Filter res1 = e1.getFilter();
if(request.isAsyncSupported() && "false".equalsIgnoreCase(e1.getFilterDef().getAsyncSupported())) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}

                if(Globals.IS_SECURITY_ENABLED) {
                    Principal principal1 = ((HttpServletRequest)request).getUserPrincipal();
Object[] args1 = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", res1, classType, args1, principal1);
} else {
                    res1.doFilter(request, response, this);
}

            } catch (ServletException | RuntimeException | IOException var15) {
                throw var15;
} catch (Throwable var16) {
                Throwable res = ExceptionUtils.unwrapInvocationTargetException(var16);
ExceptionUtils.handleThrowable(res);
throw new ServletException(sm.getString("filterChain.filter"), res);
}
        } else {
            try {
                if(ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set(request);
lastServicedResponse.set(response);
}

                if(request.isAsyncSupported() && !this.servletSupportsAsync) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}

                if(request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
                    Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
                    this.servlet.service(request, response);
}
            } catch (ServletException | RuntimeException | IOException var17) {
                throw var17;
} catch (Throwable var18) {
                Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
                if(ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}

            }

        }
    }

    ......}
果然,欄位裡面有一個ApplicationFilterConfig[]用來儲存一系列過濾器,pos用來儲存當前過濾器位置,還有其他欄位就不深入下去了,有興趣的小夥伴可以自己探索。

再來看兩個重點方法:doFilter,internalDoFilter

doFilter的最終目的只有一個,呼叫internalDoFilter,中間可能會增加一些安全策略,估計Globals.IS_SECURITY_ENABLE與是否開啟https服務有關,具體沒仔細研究過。

internalDoFilter的最終目的也只有一個,就是調當前pos指向的過濾器鏈中的某一個filter的doFilter(request, response, this)方法,中間可能會增加一些安全策略,以及當所有過濾器呼叫完了,進行的一些收尾清理工作,包括呼叫servlet.service(request, response)方法,來處理真正的請求,以及清除threadLocal中儲存的當前的request和response,為下一次請求做準備。

再把流程梳理一遍:

一個request請求進來了,先把自己交給filterChain;

filterChain啟動過濾器鏈,從頭開始,把request交給第一個filter,並把自己傳給filter;

filter在doFilter裡做完自己的過濾邏輯,再呼叫filterChain的doFilter,以啟動下一個過濾器;

filterChain遊標移動,啟動下一個過濾器,如此迴圈下去......

過濾器遊標走到鏈的末尾,filterChain執行收尾工作;

最後給一個簡單的流程圖: