這個過程是從客戶端發起訪問,到服務端響應,並返回結果的整個過程。
以struts-2.5.5作爲研究對象
第一部分 從StrutsPrepareAndExecuteFilter開始
StrutsPrepareAndExecuteFilter作爲struts2的一個核心過濾器(Filter),接受所有客戶端的請求,經過一系列的處理纔到我們的Action。對於Filter,他首先執行init方法初始化,因此我們先看看StrutsPrepareAndExecuteFilter的初始化做了什麼。
InitOperations
Dispatcher
Container
ActionMapping
ActionProxy
Interceptor
StrutsPrepareAndExecuteFilter.init
public void init(FilterConfig filterConfig) throws ServletException {
//初始化InitOperations
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
//獲取web.xml的Filter內的init-param參數配置
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
//當前請求運行之前的包含初始化的操作--僅產生PrepareOperations對象
prepare = new PrepareOperations(dispatcher);
//所有過濾器所包含運行操作--僅產生ExecuteOperations對象
execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
接着我們看具體dispatcher 的創建過程,createDispatcher方法。
InitOperations.initDispatcher
public Dispatcher initDispatcher( HostConfig filterConfig ) {
**Dispatcher** dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
InitOperations.createDispatcher
private Dispatcher createDispatcher( HostConfig filterConfig ) {
Map<String, String> params = new HashMap<>();
//遍歷filterConfig把init-param全部拿出來,就是那些在web.xml配置在Filter標籤裏面的初始化參數
for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
//構造dispatcher
return new Dispatcher(filterConfig.getServletContext(), params);
}
Dispatcher.init
//Load configurations, including both XML and zero-configuration strategies,
// and update optional settings, including whether to reload configurations and resource files.
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_FileManager();
init_DefaultProperties(); //加入默認配置供給器,解析org/apache/struts2/default.properties
init_TraditionalXmlConfigurations(); // [2] struts-default.xml
init_LegacyStrutsProperties(); // [3] struts-plugin.xml
init_CustomConfigurationProviders(); // [5] struts.properties
init_FilterInitParameters() ; // [6] default.properties
init_AliasStandardObjects() ; // [7] struts.custom.properties
// 預加載配置,構建容器
// 構造出容器,因此容器是擁有這些常量和配置裏面指定的bean的實例化的能力。說到容器的實例化的能力,其實實例化的過程有一部分是依賴於容器的依賴注入功能的,容器實例化類的時候是通過構造函數注入(ConstructorInjector)方式,通過反射獲取指定類的構造函數,如果有無參數的構造函數就直接使用無參數構造函數完成初始化,如果只有有參數的構造函數,就分析參數上面有沒有注入的標誌,如果有就通過容器的參數注入(ParameterInjector)方式注入依賴值給這個參數,然後通過這些有值的參數調用這個構造函數,達到實例化的目地,實例化之後還需要對其成員進行注入依賴,容器對成員注入是使用成員注入(MemberInjector,分別有MethodInjector和FieldInjector,針對方法還是屬性判斷使用哪一種方式)的方式,對靜態成員還有靜態注入(StaticInjector,其實質是通過MemberInjector實現的,只不過不需要穿入實例對象)方式;對於容器提供的依賴注入能力,其前提條件就是使用注入的類要配置在bean裏面,也就是說類要交給容器管理,並且在使用注入依賴的屬性、參數、方法要使用@Inject註解去指明,否則沒有辦法注入,或者注入了null,或者出現異常。
然後通過container.getInstance(ObjectFactory.class) , 創建出我們的ObjectFactory,使用默認工廠對象創建這個ObjectFactory的實例,根據之前的介紹,可以看到struts2的struts.objectFactory被註釋了,並沒有指定,意味着是使用struts作爲name的ObjectFactory的bean,就是使用org.apache.struts2.impl.StrutsObjectFactory作爲真正的ObjectFactory實現。
我們之後的Action、Result、Interceptor等等一系列與我們密切相關的對象都是通過ObjectFactory創建的,它可以說是struts的心臟。**ObjectFactory實例化對象的默認方式不是使用容器的,而是通過一般的Class.newIntance()方法**,而注入纔是藉助容器的inject(Container.inject)方法給實例化後的對象注入依賴值,對於這樣的操作,一個解析就是避免影響核心類的性能,如果把所有的Action都交給容器來管理,會造成容器過於龐大,因爲有時候Action可能會是成千上萬的,這樣會拖慢容器的執行;另外一個原因,因爲Action、Result、Interceptor等都是需要多例的,而非單例,方便起見就直接用ObjectFactory,通過Class.newIntance()創建一個實例好了;估計還有一個方面想就是爲了擴展,因爲如果都交給容器,其他集成框架無法獲取到struts2的容器的,無法操作他,所以爲了擴展就使用了Class.newIntance()方法實例化。
**Container** container = init_PreloadConfiguration();
container.inject(this);
init_CheckWebLogicWorkaround(container);
//觸發監聽器初始化事件
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
errorHandler.init(servletContext);
} catch (Exception ex) {
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}
Dispatcher.init_CustomConfigurationProviders
//加入自定義的供給器
private void init_CustomConfigurationProviders() {
String configProvs = initParams.get("configProviders");
if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
if (prov instanceof ServletContextAwareConfigurationProvider) {
((ServletContextAwareConfigurationProvider)prov).initWithContext(servletContext);
}
configurationManager.addContainerProvider(prov);
} catch (InstantiationException e) {
throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Unable to access provider: "+cname, e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Unable to locate provider class: "+cname, e);
}
}
}
}
從上面代碼可以看出struts2對配置文件的加載順序:
1、org/apache/struts2/default.properties
2、struts-default.xml
3、struts-plugin.xml(如果有)
4、struts.xml
5、struts.properties(如果有)
6、default.properties(如果有)
7、struts.custom.properties(如果有)
org/apache/struts2/default.properties是配置了struts2的常量的默認值,比如struts.i18n.encoding 、struts.action.extension、struts.i18n.reload等常量。
struts-default.xml裏面配置了struts2的核心bean,以及用constant配置的常量,比如ObjectFactory、ActionProxyFactory、ActionMapper等bean。
struts-plugin.xml是struts2的插件的配置文件,比如struts2-spring-plugin。
struts.xml是使用struts2時候基本配置,比如action、result、interceptor、constant等。
struts.properties、default.properties、struts.custom.properties是原來舊版本的一些配置。
注意:後面加載的配置文件可以覆蓋前面的配置文件的配置
從上面代碼可以知道,init_XXXX的方法是爲配置管理器收集配置供給器的信息,而最後調用init_PreloadConfiguration方法,構建出配置對象和容器,他們是struts2中非常重要的兩個對象,配置對象裏面包含了struts2的所有的配置信息,容器裏面包含了struts2中所有的對象的工廠對象。以後使用到的Action、攔截器(Interceptor)、Result等對象都是通過他們兩個協同創建出來的。
全部的配置都在這裏解析完成,並能夠組織起來,構成運行時的結構了
#第二部分:doFilter的工作過程
*StrutsPrepareAndExecuteFilter.doFilter*
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
String uri = RequestUtils.getUri(request);
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Checking if {} is a static resource", uri);
*protected ExecuteOperations execute;*
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Assuming uri {} as a normal action", uri);
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
`**ActionMapping**` mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for {}, passing to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping {} for {}", mapping, uri);
execute.executeAction(request, response, mapping);
}
}
}
} finally {
prepare.cleanupRequest(request);
}
}
*ActionMapping*
public ActionMapping(String name, String namespace, String method, Map<String, Object> params) {
this.name = name;
this.namespace = namespace;
this.method = method;
this.params = params;
}
*// Tries to execute a request for a static resource*
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// there is no action in this request, should we look for a static resource?
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
// The framework did its job here
return true;
} else {
// this is a normal request, let it pass through
return false;
}
}
*ExecuteOperations類*
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, mapping);
}
*Dispatcher類*
//Dispatcher.serviceAction是我們所有action的統一處理入口,進入這個方法,就是開始了action的處理了。Dispatcher.serviceAction主要是完成以下幾個工作,
1、創建Action對象,以代理對象的形式
2、執行mapping.execute或者開始調用攔截器(就是proxy.execute);如果執行了mapping.execute(執行Result返回客戶端),就停止下面操作了
3、通過反射調用Action的指定方法,並返回
4、執行Result返回客戶端,可以是頁面(視圖)跳轉或者其他操作
這些都是重點啊!我們先總體看看Dispatcher.serviceAction方法
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping);
*// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
//重新創建ValueStack,這裏的ValueStack是我們後面的action用到的那個ValueStack*
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
//用指定的ValueStack來創建一個新的ActionContext,並設置爲本線程的ActionContext,
//注意這裏已經不是之前的引導容器創建的ActionContext了,而是我們action使用的那個ActionContext,
//引導容器的ActionContext已經在DefaultConfiguration.reloadContainer方法退出時候被清空了,
//現在的ValueStack和ActionContext都是全新的。
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();
//創建ActionProxy動態代理
**ActionProxy** proxy = 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) {
//匹配到的action方法比如 login
如果是result.execute直接執行的情況,到這裏doFilter對一個請求的處理過程就執行完了
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
//action中默認執行的方法
//proxy.execute,我們action執行的過程,當然在action真正執行之前,當然還有攔截器的執行,然後action執行完後返回一個resultCode,再根據resultCode找到對應的Result,執行頁面(視圖)跳轉,返回客戶端之前當然還會生成視圖
proxy.execute();
}**
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
//設置值棧
//再把修改過的ValueStack設置到request中,在Result之後返回視圖時候可以用得着這些參數,
//因爲Action執行方法時候可能會改變了ValueStack的裏面的值
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
e.printStackTrace();
if (handleException || devMode) {
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
*DefaultActionInvocatio默認action代理類*
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
//防止多次執行action,保證action只執行一次
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 {
//通過映射對象獲取攔截器,執行**Interceptor攔截器**,注意,intercept參數是當前的action調用對象,
//其實這裏面也調用DefaultActionInvocation.invoke,他們這樣是遞歸調用,通過這樣的方法遍歷完 interceptors
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
} finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
//當遍歷完interceptors時候,執行正真的action操作,這裏是一個重點
//返回的resultCode會用來匹配對應的Result
resultCode = invokeActionOnly();
}
//action是否已經被調用,用來判斷是否第一次執行Result操作,這樣寫是因爲action執行完後,
//攔截器會一個一個執行下面的操作,然後返回上一個調用的攔截器,就是按調用攔截器時候反過來的順序執行,
//這是**遞歸的原理**,所以如果每個攔截器都執行Result那麼程序就會出錯,
//所以當action執行完Result之後,就通過這種方式不允許攔截器執行Result
if (!executed) { //第一次執行Result有效
//執行相關的監聽器,Result執行前的監聽器
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);
}
}
}
//如果支持執行Result,就執行,這個是構建action代理對象時候傳入去的參數,是true
if (proxy.getExecuteResult()) {
//執行我們的Result,這裏是另一個重點
executeResult();
}
//標識已經被執行過
executed = true;
}
//返回執行代碼
return resultCode;
} finally {
UtilTimerStack.pop(profileKey);
}
}
當攔截器執行完時候,執行action的地方,在上面我們看到了 resultCode = invokeActionOnly()
*DefaultActionInvocation默認action代理類*
public String invokeActionOnly() throws Exception {
return invokeAction(getAction(), proxy.getConfig());
}
*DefaultActionInvocation默認action代理類*
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();
LOG.debug("Executing action method = {}", methodName);
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
//獲取action,然後根據methodName的名稱,通過反射獲取這個方法對象,這個是沒有參數的方法
methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
} catch (MethodFailedException e) {
// if reason is missing method, try checking UnknownHandlers
if (e.getReason() instanceof NoSuchMethodException) {
if (unknownHandlerManager.hasUnknownHandlers()) {
try {
//執行這個方法,設置action爲執行實例,大多數情況都是執行這個方法,返回值大多數情況也是String類型
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
} catch (NoSuchMethodException ignore) {
// throw the original one
throw e;
}
} else {
// throw the original one
throw e;
}
// throw the original exception as UnknownHandlers weren't able to handle invocation as well
if (methodResult == null) {
throw e;
}
} else {
// exception isn't related to missing action method, throw it
throw e;
}
}
return saveResult(actionConfig, methodResult);
} catch (NoSuchPropertyException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (MethodFailedException e) {
// We try to return the source exception.
Throwable t = e.getCause();
if (actionEventListener != null) {
String result = actionEventListener.handleException(t, getStack());
if (result != null) {
return result;
}
}
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw e;
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
從上面可以看出,這個就是我們的Action的方法執行的地方,然後根據返回值,如果這個值是Result類型,就直接用這個Result來執行會面的操作;如果是String類型,就是resultCode,就用這個resultCode來獲取Result,就是匹配result標籤裏面的name,這些配置早就儲存在result配置對象了,然後用獲取到的Result執行下面的操作,我們回到DefaultActionInvocation.invoke,看executeResult裏面的處理過程
*DefaultActionInvocation默認action代理類*
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = 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) {
LOG.trace("Executing PreResultListeners for result [{}]", result);
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);
}
}
*DefaultActionInvocation默認action代理類*
private void executeResult() throws Exception {
//先創建一個Result
result = createResult();
String timerKey = "executeResult: " + getResultCode();
try {
UtilTimerStack.push(timerKey);
if (result != null) {
//如果創建成功
result.execute(this);
} else if (resultCode != null && !Action.NONE.equals(resultCode)) { //如果有返回值,並且Action不是沒有視圖的就拋出異常
throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode(), proxy.getConfig());
} else { //沒有視圖,就是通過response直接寫回響應給客戶端哪些情況,比如json
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation());
}
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
“`
上面情況我們先看看createResult方法,我們假設返回的Result是一個org.apache.struts2.dispatcher.ServletDispatcherResult類型,就是默認類型,Action的默認頁面(視圖)跳轉方式,需要返回值,有視圖
當創建Result成功就可以執行了,前面說過假設是返回默認的Result ———— org.apache.struts2.dispatcher.ServletDispatcherResult,我們針對ServletDispatcherResult.execute來展開
result執行完了,跳轉到指定的頁面(視圖),然後正真生成好頁面,再返回給客戶端
好累呀