Struts2是个非常优秀的开源框架,最近抽空看了一下源码,然后从互联网看了一下大家的总结,发现随着版本的更新,人的思想也需要及时更新啊。 关于其主要的工作原理,我也不想在阐述更多的了,如果你比较熟悉servlet,理解起来会更加容易,可以参考看一下优快云的这篇文章: 参考文章
大概的工作原理从图片中描述得很清楚了。 在总结的过程发现Struts的版本更新也很快,web.xml文件中配置的过滤在2.1.2版本之前都是配置FilterDispatcher,但是在之后版本配置的是一个全新的过滤器:StrutsPrepareAndExecuteFilter,从字面上看貌似后者必前者功能更加强大。FilterDispatcher是struts2.0.x到2.1.2版本的核心过滤器。StrutsPrepareAndExecuteFilter是自2.1.3开始就替代了FilterDispatcher的。这样的改革当然是有好处的。我们自己定义过滤器的话, 是要放在strtus2的过滤器之前的, 如果放在struts2过滤器之后,你自己的过滤器对action的过滤作用就废了。
在web.xml中是这样配置的:
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在该过滤器中有一个初始化的方法 init(FilterConfig filterConfig);看看其作用
public void init(FilterConfig filterConfig) throws ServletException {
//这只是一个普通类,封装下面的初始化函数,可以当做工具类一样
InitOperations init = new InitOperations();
try {
//封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在Iterator中,在后面的初始化dispatcher中使用
FilterHostConfig config = new FilterHostConfig(filterConfig);
//初始化strut的内部日志
init.initLogging(config);
//初始化dispatcher
Dispatcher dispatcher = init.initDispatcher(config);
//加载静态内容加载器
init.initStaticContentLoader(config, dispatcher);
//初始化上面定义的prepare ,execute ,会在下面doFilter中使用到,看起参数,放置了两个重要的东西
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
//这是一个回调方法
postInit(dispatcher, filterConfig);
} finally {
init.cleanup();
}
}
创建dispatcher对象,并初始化
public Dispatcher initDispatcher( HostConfig filterConfig ) {
//相当于初始两个参数ServletContext servletContext和Map<String, String> initParams
Dispatcher dispatcher = createDispatcher(filterConfig);
//包括初始换文件管理器,default.properties文件提供者,struts-default.xml,struts- //plugin.xml,struts.xml等文件,用户自己实现的ConfigurationProviders类,Filter的初始化参数等
dispatcher.init();
return dispatcher;
}
//将filterConfig 对象中参数去除并封装,然后new对象
private Dispatcher createDispatcher( HostConfig filterConfig ) {
Map<String, String> params = new HashMap<String, String>();
for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new Dispatcher(filterConfig.getServletContext(), params);
}
总体上来说,这个init初始化还是做了很多事情的,大致会根据配置来初始换参数,初始化适配器,初始化加载各种文件等。
下面doFilter是过滤器的执行方法,它拦截提交的HttpServletRequest请求,HttpServletResponse响应,作为strtus2的核心拦截器,在doFilter里面也做了很多工作,具体如下:
//设置编码和国际化,底层执行 dispatcher.prepare(request, response);方法很简单只是设置了
//encoding 、locale ,做的只是一些辅助的工作
prepare.setEncodingAndLocale(request, response);
//创建Action上下文(重点)
prepare.createActionContext(request, response);
创建ActionContext,ActionContext本来是一个容器,主要存储request、session、application、parameters等相关信息,并且它是一个线程的本地变量,不会造成线程之间的变量分享问题,这意味着不同的action之间不会共享一个ActionContext,当然不会存在线程安全的问题,
//创建Action上下文,初始化thread local
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// detected existing context, so we are probably in a forward
ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
} else {
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
//ActionContext保存到ThreadLocal
ActionContext.setContext(ctx);
return ctx;
}
dispatcher是怎么createContextMap的呢?
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping, ServletContext context) {
// request map wrapping the http request objects
Map requestMap = new RequestMap(request);
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
Map params = new HashMap(request.getParameterMap());
// session map wrapping the http session
Map session = new SessionMap(request);
// application map wrapping the ServletContext
Map application = new ApplicationMap(context);
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
可以看到这里获取得到request和context中获取相关参数,封装为map,其中
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
再次将各个参数map再封装到另外Map中,得到extraContext变量。这里的ActionMapping mapping为空,不会做任何处理。
在prepare将适配器分给线程之后,开始查找本次请求的ActionMapping
ActionMapping mapping = prepare.findActionMapping(request, response, true);
这里暂时不详细讨论是怎么根据request相关参数来形成ActionMapping 的,但是我们应该有感觉,上面已经加载了struts的相关xml配置文件,这部分另外介绍。在找到ActionMapping 之后就是执行action了,execute.executeAction(request, response, mapping);这部分详细见后面一章吧。