今天就把自己看的struts2源代码的体会与大家分享一下吧。
讲到struts2就先讲一下过滤器,因为struts2靠的就是过滤器。当一个http请求过来的时候首先经过一系列配置的过滤器,然后到达struts2的核心过滤器StrutsPrepareAndExecuteFilter进行struts2的流程处理。
过滤器和servlet差不多,实现servlet的方式是继承httpservlet而实现filter的方式是实现Filter接口。二者的区别是filter是在web容器启动的时候就加载了而servlet默认是第一次访问servlet时候才加载(注意是默认情况下,我们也可以进行配置在一启动的时候就加载),个人认为Filter可以用作servlet,而servelt不能用做filter,其主要原因是filter里涉及到了一种设计模式,就是责任链模式而servlet没有,如果servlet用作filter那么在这个servelt就停止了不能再调用下一个过滤器或servlet了。Filter调用的顺序和Filter配置的顺序有关,在前面的Filter先执行然后调用后面的Filter。正因为Filter有这个特性所以我们在做项目的时候要注意,比如说我现在想用filter来过滤乱码,那么这个时候要把这个过滤乱码的过滤器放在struts2过滤器的前面,不然会出现先调用了struts2的过滤器然后其实请求已经到达了目的可是乱码的过滤器还在到达目的的后面,那么乱码还是没有过滤。。。。
接下来就讲一下struts2的源码分析吧......
首先看的是struts2的核心过滤器StrutsPrepareAndExecuteFilter的init方法,这个方法只会执行一次,看一下这个方法:
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
Dispatcher dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
} finally {
init.cleanup();
}
}
一进来这个方法首先先初始化了一个初始化类 InitOperations 由这个初始化类来初始化一些别的对象。然后把filter的FilterConfig进行了包装了一下转化成了FilterHostConfig了,这个我感觉是为了解耦,不依赖filter的接口吧。。。然后由InitOperations的引用init调用initLogging方法。这个主要是用来初始化日志对象的利用的反射机制。
接下来就是关键操作,由InitOperations 的引用调用initDispatcher方法得到Dispatcher对象,我们看一下initDispatcher方法:
public Dispatcher initDispatcher(HostConfig filterConfig) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
这里调用了createDispatcher方法:
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);
}
把上下文和参数作为生成Dispatcher的参数生成一个Dispatcher对象,然后Dispatcher马上调用自己的init方法来初始化一些东西:
public void init() {
if (configurationManager == null) {
configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
}
init_DefaultProperties(); // [1]
init_TraditionalXmlConfigurations(); // [2]
init_LegacyStrutsProperties(); // [3]
init_CustomConfigurationProviders(); // [5]
init_FilterInitParameters() ; // [6]
init_AliasStandardObjects() ; // [7]
Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckConfigurationReloading(container);
init_CheckWebLogicWorkaround(container);
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
}
首先是判断ConfigurationManager是否存在如果不存在的话就创建一个默认为struts2的ConfigurationManager对像。然后初始化一些struts2的配置文件。我们主要看的是拿到
Container对象的过程:
private Container init_PreloadConfiguration() {
Configuration config = configurationManager.getConfiguration();
Container container = config.getContainer();
boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
LocalizedTextUtil.setReloadBundles(reloadi18n);
return container;
}
首先是由configurationManager得到Configuration:
public synchronized Configuration getConfiguration() {
if (configuration == null) {
setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
try {
configuration.reloadContainer(getContainerProviders());
} catch (ConfigurationException e) {
setConfiguration(null);
throw new ConfigurationException("Unable to load configuration.", e);
}
} else {
conditionalReload();
}
return configuration;
}
这里我主要想说的是configuration.reloadContainer(getContainerProviders());这个方法,这个方法主要是用来进行做校验的,不知道大家发现过没有如果struts2的action不写无参构造器只写一个有参构造器的话启动的服务的时候会报错。报错的原因就在于这个方法。这个方法我也就大概的讲一下,为什么会报错,其实他就是利用反射的机制去找有没有无参构造器没有的话就会报错。。得到Dispatcher后然后生成了PrepareOperations对象和ExecuteOperations,这2个对象的作用下面讲。
初始化工作结束了,接下来就讲一下一个struts2请求经过struts2过滤器的流程:
一个struts2请求经过一系列过滤器以后最终到了struts2的过滤器:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
prepare.setEncodingAndLocale(request, response);
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
} finally {
prepare.cleanupRequest(request);
}
}
这个就讲重点吧。。。首先由PrepareOperations的引用导调用findActionMapping方法来得到mapping,这一步的主要目的是看拦截的这个请求需不需要经过struts2的过滤器进行处理。跟进这个方法可以看到其实内部是利用的Dispatcher对象来得到ActionMapping对象的。至于Dispatcher对象是怎么来的,这个回答上面的init方法了在生成PrepareOperations的时候会把Dispatcher作为PrepareOperations的构造器的参数传入进去。。。
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
这里由dispatcher首先得到Container然后得到ActionMapping对象。getMapping这个方法我就大概的将下吧。就是根据得到的请求url来分析,如果这个url是以/XXX或是.acton结尾的则就返回Mapping对象认为这个请求是需要经过struts2的过滤器来处理的如果不是以这2个中的一个结尾的则返回null认为这个请求不需要struts2的过滤器来处理。
在mapping为null 的情况下还会去检查一边url:
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
这个就是看下这个url是否是以struts或static关键字开始的。如果是的话则步处理,如果不是的话则调用下一个filter。
如果mapping不为空则调用ExecuteOperations的executeAction方法,其实内部还是利用了dispatcher对象来进行处理:
dispatcher.serviceAction(request, response, servletContext, mapping);
这个serviceAction方法我就将一下主要的吧,别的因为自己能力不足还没看懂呵呵:
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
// WW-2874 Only log error if in devMode
if(devMode) {
LOG.error("Could not find action or result", e);
}
else {
LOG.warn("Could not find action or result", e);
}
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} finally {
UtilTimerStack.pop(timerKey);
}
我们就直接看生成的ActionProxy过程。首先由得到的Container调用createActionProxy方法:
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
在生成ActionProxy前首先先生成的是ActionInvocation对象,然后:
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
container.inject(proxy);
proxy.prepare();
return proxy;
}
再生成ActionProxy对象,在生成ActionProxy对象的过程中把ActionInvocation对象注入了进去。接着我们看下proxy.prepare();方法。这个方法主要是做一些前置工作,这里想说一下的是这个方法里面会调用ActionInvocation的init方法,在调用init方法时候会把DefaultActionProxy自己作为参数传入,然后在init方法里设置DefaultActionProxy为成员变量,主要是因为在后面会用到DefaultActionProxy里的getmethod方法得到method。。。然后这里面会利用反射来生成action的实例。
接下来就看一下 proxy.execute();方法:
public String execute() throws Exception {
ActionContext nestedContext = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
String retCode = null;
String profileKey = "execute: ";
try {
UtilTimerStack.push(profileKey);
retCode = invocation.invoke();
} finally {
if (cleanupContext) {
ActionContext.setContext(nestedContext);
}
UtilTimerStack.pop(profileKey);
}
return retCode;
}
action的代理中注入了ActionInvocation然后由ActionInvocation调用其invoke();这里运用了典型的代理模式。。。
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
}
finally {
UtilTimerStack.pop(profileKey);
}
这个方法首先是处理拦截器,然后处理玩拦截器以后调用最后的action。这里也是运用了反射来调用action中的方法得到一个返回的resultcode。然后再根据resultcode跳转到对应的页面。。。。呵呵这样一个struts2的请求流程就结束了。这里面由于个人能力所限还有很多东西没有讲到以为会继续学习。。。。上面如果哪里讲的不对大家可以指出来谢谢。。。。。。