写在前面:struts2在web应用层面属于表示层框架,在MVC开发模式中属于C(Controller控制器),负责对M(Model模型)和V(View视图)进行解耦。struts2是在struts1和webwork的技术基础上进行了合并的全新的框架。struts2虽然和struts1在名字上很相似,但是却不是后者的升级版。struts2其实是以另一个表示层框架webwork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以struts2也可以理解为webwork的更新产品。从它们的处理请求的执行流程就可以看出相似点。
直观感受一下:
webwork:
struts2:
struts2和webwork都是通过一个FilterDispatcher过滤器来匹配客户端发送的所有请求(当然,现在struts2的过滤器名是StrutsPreparedAndExecuteFilter),不同于struts1是通过一个servlet来匹配所有请求,好了,这里先简单的了解一下struts2和struts1的区别, 在文章末尾会详细的介绍struts2和struts1的区别,开始进入主题。
struts2的执行流程(结合流程图分析):
-
客户端发送一个HTTP请求
-
该请求被struts2的核心过滤器StrutsPreparedAndExecuteFilter匹配(只要是在过滤器的url-pattern中配置了/*,那么任何请求都会进入该过滤器,无论该请求是否需要struts2来处理),当然,在进入这个过滤器之前会依次进入在web.xml中配置的位置在struts2过滤器之前的其他Filter或Servlet
-
struts2的过滤器会询问(形象一点的说法,其实就是调用方法)ActionMapper该请求是否有与之对应的业务控制类,如果没有,则放行,如果有,进入下一步执行流程
-
struts2通过ActionProxy实例化ActionInvocation,当然在这之前ActionProxy还会通过ConfigurationManager按序加载struts2的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml…(先加载struts默认的,然后才是自己定义的),正是因为加载了这些配置文件所以struts才能找到相应的拦截器以及业务控制类。
-
ActionProxy初始化一个ActionInvocation并通过它的invoke来正式执行一系列的拦截器以及Action,在执行完Action之后会根据使用的模板(jsp, velocity, freemarker…)组装结果集Result,渲染页面
-
返回给客户端响应
接下来详细的分析一下:
1. 客户端发送一个HTTP请求
可能是一个登陆请求,也可能是查询某个功能列表的请求...2. StrutsPreparedAndExecuteFilter过滤器拦截该请求
过滤器拦截到该请求的时候会调用doFilter方法,如下:public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 1.将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// 2.对不由struts2处理的请求放行,这个excludedPatterns是一个List<Pattern>集合,里面存储了不被struts2的过滤器匹配的url
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
// 3.设置请求和相应的编码以及国际化的相关信息
prepare.setEncodingAndLocale(request, response);
// 4.创建一个Action的上下文,并初始化一个本地线程
// 源码注释:Creates the action context and initializes the thread local
prepare.createActionContext(request, response);
// 5.把dispatcher指派给本地线程
// 源码注释:Assigns the dispatcher to the dispatcher thread local
prepare.assignDispatcherToThread();
// 6.包装一下request防止它是一个multipart/form-data类型的请求
// 源码注释:Wrap request first, just in case it is multipart/form-data
request = prepare.wrapRequest(request);
// 7.查找ActionMapping信息(包括name,namespace,method,extention,params,result)
ActionMapping mapping = prepare.findActionMapping(request, response, true);
// 8.没有找到请求对应的业务控制类
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
// 9.找到了对应的业务控制类那就去执行该Action
execute.executeAction(request, response, mapping);
}
}
} finally {
// 10.释放掉这个Request所占用的一些内存空间
prepare.cleanupRequest(request);
}
}
2.1. 首先将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象
2.2. 判断是否设置了不被struts2过滤器拦截的请求
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
}
这个excludedPatterns是一个List< Pattern >集合,里面包含了不被struts2过滤器拦截的url。看这一句:prepare.isUrlExcluded(request, excludedPatterns),判断这个请求里面是否包含这样的url,跟进源码查看一具体的实现:
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {
if (excludedPatterns != null) {
// 1.获取当前请求中的uri
String uri = RequestUtils.getUri(request);
// 2.查看集合中是否有与之匹配的,有就返回true
for ( Pattern pattern : excludedPatterns ) {
if (pattern.matcher(uri).matches()) {
return true;
}
}
}
return false;
}
知道了拦截器是通过excludedPatterns来判断哪个url不被拦截,那么这个excludedPatterns的值是从哪里来的呢?初步猜测是在StrutsPreparedAndExecuteFilter初始化(init)的时候设置的…果不其然,看源码:
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
// 就是这里,创建了不匹配的url列表
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
跟进buildExcludedPatternsList方法:
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
// 由此可知是struts2是读取了STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配
return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN));
}
private List<Pattern> buildExcludedPatternsList( String patterns ) {
if (null != patterns && patterns.trim().length() != 0) {
List<Pattern> list = new ArrayList<Pattern>();
String[] tokens = patterns.split(",");
for ( String token : tokens ) {
list.