java教程丨javaweb程序必会技术之filter的执⾏顺序解密

1.引言

我们在编写javaweb程序的时候,时常会⽤filter这个组件,它能将我们⼀些通⽤逻辑抽取出来,在servlet执⾏业务逻辑之前运⾏,

达到简化代码和复⽤的⽬的.⽐如最常⽤的场景全站编码和登录验证功能.

servlet3.0以前我们只能通过web.xml的⽅式配置filter,并且多个filter的执⾏顺序是根据你web.xml中书写顺序来决定的.

servlet3.0以后,提供了注解的⽅式注⼊filter,只需要在filter类上加上@WebFilter()注解即可,⼤⼤的简化了开发复杂度.

2.抛出问题

注解的⽅式书写的filter的执⾏顺序⼜是如何的呢?

⽹上的很多资料都说是根据filter的类名来决定,也有说是根据filter的注解的name属性值的字⺟顺序来决定的.

对不对呢?

3.验证问题

我们创建了三个filter 来验证此问题

filter1号

package com.jk1123.web.filter.demo01;

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter("/*")

public class OrderFilter1 implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)

throws ServletException, IOException {

System.out.println("orderFilter1执⾏了..");

chain.doFilter(req, resp);

}

public void init(FilterConfig config) throws ServletException {

}

}

filter2号

package com.jk1123.web.filter.demo02;

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter("/*")

public class OrderFilter2 implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)

throws ServletException, IOException {

System.out.println("orderFilter2执⾏了..");

//这是放⾏

chain.doFilter(req, resp);

}

public void init(FilterConfig config) throws ServletException {

}

}

filter3号

package com.jk1123.web.filter.demo03;

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter("/*")

public class OrderFilter3 implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)

throws ServletException, IOException {

System.out.println("orderFilter3执⾏了..");

//这是放⾏

chain.doFilter(req, resp);

}

public void init(FilterConfig config) throws ServletException {

}

}

配上⼀个servlet 来访问试试

package com.jk1123.web.servlet;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/foo")

public class FooServlet extends HttpServlet {

protected void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

this.doGet(request, response);

}

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.getWriter().print("foo servlet");

}

}

验证结果:


4.查看源码

可以从上⾯验证看出 好像并不是根据类名或者filter的name属性的字⺟排序执⾏,那到底是根据什么执⾏的呢?

点开源码,我们⼀点点探寻它的秘密.

需要搞清楚如下问题

1.filterChain是什么时候执⾏的呢?

2.filterChain中的filter来源何处?

3.standardContext什么时候开始收集的过滤器集合

我们先查询第⼀段源码 解密filterChain是什么时候执⾏的

//在org.apache.catalina.core.StandardWrapperValve 类中有如下⼀个⽅法

public final void invoke(Request request, Response response)

throws IOException, ServletException {

//省略掉⼤段⽆关代码

//0.⽣命servlet对象

Servlet servlet = null;

/**

* 省略⼀⼤段⽆关代码

*/

try {

if (!unavailable) {

//1.这⾥创建正在访问的servlet对象

servlet = wrapper.allocate();

}

} catch (UnavailableException e) {

//省略掉⼤段⽆关代码

}

//省略⼤段⽆关代码

//2.创建过滤器链对象

ApplicationFilterChain filterChain =

ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

try {

if ((servlet != null) && (filterChain != null)) {

// Swallow output if needed

if (context.getSwallowOutput()) {

//省略掉⼤段⽆关代码

} else {

if (request.isAsyncDispatching()) {

request.getAsyncContextInternal().doInternalDispatch();

} else {

//3.执⾏过滤链对象doFilter⽅法

filterChain.doFilter

(request.getRequest(), response.getResponse());

}

}

}

} catch (ClientAbortException | CloseNowException e) {

//省略掉⼤段⽆关代码

}

}

我们⼀会⼉回过头来看它是如何创建过滤器链对象的代码,我们先来看他是如何执⾏过滤器链的,过滤器链对象的实现为:

package org.apache.catalina.core;

//我们只保留 跟执⾏序列有关的代码

public final class ApplicationFilterChain implements FilterChain {

//当前正在执⾏的filter索引

private int pos = 0;

//总共有多少个filter匹配上了

private int n = 0;

//关联的要执⾏的servlet对象

private Servlet servlet = null;

//匹配上的filter数组

private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

@Override

public void doFilter(ServletRequest request, ServletResponse response)

throws IOException, ServletException {

if( Globals.IS_SECURITY_ENABLED ) {

//删除⽆关代码

} else {

//0.执⾏内容的doFilter⽅法

internalDoFilter(request,response);

}

}

private void internalDoFilter(ServletRequest request,

ServletResponse response)

throws IOException, ServletException {

// Call the next filter if there is one

if (pos < n) {

//这个地⽅主义有个pos++ 进来⼀次 ++⼀次

ApplicationFilterConfig filterConfig = filters[pos++];

try {

Filter filter = filterConfig.getFilter();

//删除⼤段⽆关代码

if( Globals.IS_SECURITY_ENABLED ) {

//删除⼤段⽆关代码

} else {

//执⾏过滤器链中的过滤器的doFilter⽅法

//⽽我们的过滤器中满⾜条件后 放⾏ 放⾏就会跳转回来执⾏过滤器链的

//的doFilter 也就是⼜回来执⾏第⼆个

filter.doFilter(request, response, this);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.filter"), e);

}

return;

}

try {

//删除⼤段⽆关代码

// Use potentially wrapped request from this point

if ((request instanceof HttpServletRequest) &&

(response instanceof HttpServletResponse) &&

Globals.IS_SECURITY_ENABLED ) {

//删除⼤段⽆关代码

} else {

//如果没有需要执⾏的filter就会执⾏ servlet的service⽅法 也就是我们写的业务

逻辑

servlet.service(request, response);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.servlet"), e);

} finally {

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {

lastServicedRequest.set(null);

lastServicedResponse.set(null);

}

}

}

}

从filterChain类的源码可以看出底层是包含了 所匹配上的filter数组 也就是添加进去匹配上过滤器对象是有序的 添加的时候就决定了!!!

那么它是什么时候添加的呢?

2.filterChain中的filter来源何处?

其实我们在在org.apache.catalina.core.StandardWrapperValve 类的invoke⽅法中

ApplicationFilterChain filterChain =

ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

点开这段代码查询把!

public static ApplicationFilterChain createFilterChain(ServletRequest request,

Wrapper wrapper, Servlet servlet) {

// If there is no servlet to execute, return null

if (servlet == null)

return null;

// 在这⾥创建ApplicationFilterChain 对象

//但是对象⾥还没有filter对象

ApplicationFilterChain filterChain = null;

if (request instanceof Request) {

Request req = (Request) request;

if (Globals.IS_SECURITY_ENABLED) {

// Security: Do not recycle

filterChain = new ApplicationFilterChain();

} else {

filterChain = (ApplicationFilterChain) req.getFilterChain();

if (filterChain == null) {

filterChain = new ApplicationFilterChain();

req.setFilterChain(filterChain);

}

}

} else {

// Request dispatcher in use

filterChain = new ApplicationFilterChain();

}

filterChain.setServlet(servlet);

filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

// Acquire the filter mappings for this Context

StandardContext context = (StandardContext) wrapper.getParent();

//获取ServletContext对象 注册的所有的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();

//这⾥开始遍历 filterMaps数组根据请求路径匹配添加

for (int i = 0; i < filterMaps.length; i++) {

if (!matchDispatcher(filterMaps[i] ,dispatcher)) {

continue;

}

if (!matchFiltersURL(filterMaps[i], requestPath))

continue;

ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)

context.findFilterConfig(filterMaps[i].getFilterName());

if (filterConfig == null) {

// FIXME - log configuration problem

continue;

}

filterChain.addFilter(filterConfig);

}

// Add filters that match on servlet name second

for (int i = 0; i < filterMaps.length; i++) {

if (!matchDispatcher(filterMaps[i] ,dispatcher)) {

continue;

}

if (!matchFiltersServlet(filterMaps[i], servletName))

continue;

ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)

context.findFilterConfig(filterMaps[i].getFilterName());

if (filterConfig == null) {

// FIXME - log configuration problem

continue;

}

filterChain.addFilter(filterConfig);

}

// Return the completed filter chain

return filterChain;

}

可以看出在创建filterChain对象时候,从ServletContext获取所有注册的filter的数组 取出需要的添加到这次请求创建的filterChain对象中

⽽且servletContext对象的注册的所有的过滤器本身就是⼀个数组 本身就是有序的,所以遍历匹配的时候,也就是有序的!

3.ServletContext什么时候开始收集的数组,从哪来的呢?

这个要从tomcat启动的时候来看了!

⽽StandardContext创建完成以后就要开始初始化操作了!

StandardContext.startInternal()⽅法---->fireLifecycleEvent()⽅法-->ContextConfig.lifecycleEvent()--

>ContextConfig.lifecycleEventcon-->ContextConfig.con.figureStart()-->ContextConfig.webConfig()

好了我们现在查看该⽅法:

protected void webConfig() {

WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),

context.getXmlValidation(), context.getXmlBlockExternal());

Set<WebXml> defaults = new HashSet<>();

defaults.add(getDefaultWebXmlFragment(webXmlParser));

//创建了web.xml 配置⽂件对象

//也就是它就代表我们项⽬的配置相关的信息

WebXml webXml = createWebXml();

// Parse context level web.xml

InputSource contextWebXml = getContextWebXmlSource();

//解析web.xml 配置⽂件

//发现配置⽂件中的 filter servlet listener等配置

//⽽xml的解析是从上到下 所以你在web.xml 配置filter

//得到filter集合就是有序的

if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {

ok = false;

}

ServletContext sContext = context.getServletContext();

//省略⼤段⽆关代码

if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {

// Steps 4 & 5.

//扫描编译的类⽂件 寻找注解⽅式书写的servlet filter listener

processClasses(webXml, orderedFragments);

}

//省略⼤段⽆关代码

}

从上⾯代码 可以看出web,xml配置的filter肯定是有序的了 解析的时候 就会收集到webXml对象的

//采⽤的是linkedHashset来存储的 是有序的

private final Set<FilterMap> filterMaps = new LinkedHashSet<>();

解析xml过程我们就不看了 ⼈家才是的digester的xml解析框架来做.

我们来查看processClasses(webXml, orderedFragments); 这个⽅法是解析注解⽤的

protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {

// Step 4. Process /WEB-INF/classes for annotations and

// @HandlesTypes matches

Map<String, JavaClassCacheEntry> javaClassCache = new HashMap<>();

if (ok) {

//获取项⽬下的类路径

WebResource[] webResources =

context.getResources().listResources("/WEB-INF/classes");

for (WebResource webResource : webResources) {

// Skip the META-INF directory from any JARs that have been

// expanded in to WEB-INF/classes (sometimes IDEs do this).

if ("META-INF".equals(webResource.getName())) {

continue;

}

//开始根据注解解析了

processAnnotationsWebResource(webResource, webXml,

webXml.isMetadataComplete(), javaClassCache);

}

}

// Step 5. Process JARs for annotations and

// @HandlesTypes matches - only need to process those fragments we

// are going to use (remember orderedFragments includes any

// container fragments)

if (ok) {

processAnnotations(

orderedFragments, webXml.isMetadataComplete(), javaClassCache);

}

// Cache, if used, is no longer required so clear it

javaClassCache.clear();

}

查看processAnnotationsWebResource⽅法

protected void processAnnotationsWebResource(WebResource webResource,

WebXml fragment, boolean handlesTypesOnly,

Map<String,JavaClassCacheEntry> javaClassCache) {

//看看是否是个⽬录

if (webResource.isDirectory()) {

WebResource[] webResources =

webResource.getWebResourceRoot().listResources(

webResource.getWebappPath());

if (webResources.length > 0) {

if (log.isDebugEnabled()) {

log.debug(sm.getString(

"contextConfig.processAnnotationsWebDir.debug",

webResource.getURL()));

}

//遍历⽬录

for (WebResource r : webResources) {

//递归处理

processAnnotationsWebResource(r, fragment, handlesTypesOnly,

javaClassCache);

}

}

} else if (webResource.isFile() &&

webResource.getName().endsWith(".class")) {

try (InputStream is = webResource.getInputStream()) {

//如果是类⽂件的话 开始处理

processAnnotationsStream(is, fragment, handlesTypesOnly,

javaClassCache);

} catch (IOException e) {

log.error(sm.getString("contextConfig.inputStreamWebResource",

webResource.getWebappPath()),e);

} catch (ClassFormatException e) {

log.error(sm.getString("contextConfig.inputStreamWebResource",

webResource.getWebappPath()),e);

}

}

}

查看processAnnotationsStream⽅法

protected void processAnnotationsStream(InputStream is, WebXml fragment,

boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)

throws ClassFormatException, IOException {

ClassParser parser = new ClassParser(is);

JavaClass clazz = parser.parse();

checkHandlesTypes(clazz, javaClassCache);

if (handlesTypesOnly) {

return;

}

//处理开始

processClass(fragment, clazz);

}

protected void processClass(WebXml fragment, JavaClass clazz) {

AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();

if (annotationsEntries != null) {

String className = clazz.getClassName();

for (AnnotationEntry ae : annotationsEntries) {

String type = ae.getAnnotationType();

if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {

processAnnotationWebServlet(className, ae, fragment);

//判断是否webFilter注解 如果是就添加到 webxml配置对象中

}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {

processAnnotationWebFilter(className, ae, fragment);

}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {

fragment.addListener(className);

} else {

// Unknown annotation - ignore

}

}

}

}

从上⾯可以看出原来扫描类路径的时候,就是先遍历⽂件夹 遍历⽂件夹下类⽂件 反射查看是否是⼀个带有WebFilter注解的类

如果是就添加到web.xml中set集合中,⽽那个set集合是有序的linkedset

所有顺序就是递归遍历⽂件夹的顺序 ⼀切就看 递归的时候如何获取下级⽂件夹的代码了 看它是否进⾏排序了?

也就是说由如下代码决定的

WebResource[] webResources =

webResource.getWebResourceRoot().listResources(

webResource.getWebappPath());

点开这段代码

protected WebResource[] listResources(String path, boolean validate) {

if (validate) {

path = validate(path);

}

String[] resources = list(path, false);

WebResource[] result = new WebResource[resources.length];

for (int i = 0; i < resources.length; i++) {

if (path.charAt(path.length() - 1) == '/') {

result[i] = getResource(path + resources[i], false, false);

} else {

result[i] = getResource(path + '/' + resources[i], false, false);

}

}

return result;

}

//继续

private String[] list(String path, boolean validate) {

if (validate) {

path = validate(path);

}

// Set because we don't want duplicates

// LinkedHashSet to retain the order. It is the order of the

// WebResourceSet that matters but it is simpler to retain the order

// over all of the JARs.

HashSet<String> result = new LinkedHashSet<>();

for (List<WebResourceSet> list : allResources) {

for (WebResourceSet webResourceSet : list) {

if (!webResourceSet.getClassLoaderOnly()) {

String[] entries = webResourceSet.list(path);

for (String entry : entries) {

result.add(entry);

}

}

}

}

return result.toArray(new String[result.size()]);

}

//继续

public String[] list(String path) {

checkPath(path);

String webAppMount = getWebAppMount();

if (path.startsWith(webAppMount)) {

File f = file(path.substring(webAppMount.length()), true);

if (f == null) {

return EMPTY_STRING_ARRAY;

}

//就到这⾥了 我们可以看到 它没有排序就是调⽤了

//file类的list⽅法

String[] result = f.list();

if (result == null) {

return EMPTY_STRING_ARRAY;

} else {

return result;

}

} else {

if (!path.endsWith("/")) {

path = path + "/";

}

if (webAppMount.startsWith(path)) {

int i = webAppMount.indexOf('/', path.length());

if (i == -1) {

return new String[] {webAppMount.substring(path.length())};

} else {

return new String[] {

webAppMount.substring(path.length(), i)};

}

}

return EMPTY_STRING_ARRAY;

}

}

来来打开file类的list⽅法看看

所以最终 web.xml收集到所有filter的set集合 如果采⽤的是注解⽅式 没有任何顺序可⾔的.

然后接下来的代码我们就不看了⽆⾮就是将收集到的filter集合转换成数组 设置给StandardContext对象

4.得出结论

如果采⽤web.xml写的filter执⾏顺序跟书写顺序有关

⽽采⽤注解⽅式的是没有顺序可⾔的!!!!

⽽采⽤注解⽅式的是没有顺序可⾔的!!!!

⽽采⽤注解⽅式的是没有顺序可⾔的!!!!

实践出真知!切记⼈云亦云!有问题找源码!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343