struts2執行流程
寫在前面: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.add(Pattern.compile(token.trim()));
}
return Collections.unmodifiableList(list);
} else {
return null;
}
}
struts2是根據STRUTS_ACTION_EXCLUDE_PATTERN常量的值來判斷哪些請求不需要匹配,所以我們如果想要設定某些請求不被struts2匹配就可以設定這個常量struts.action.excludePattern,多個pattern之間用逗號隔開(pattern的寫法和web.xml中配置的類似)
2.3 設定請求和響應的編碼以及國際化的相關資訊
prepare.setEncodingAndLocale(request, response),是由Dispatcher在準備的過程中完成的,原始碼如下:
/**
* Sets the request encoding and locale on the response
* 設定請求和響應的國際化編碼
*/
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
dispatcher.prepare(request, response);
}
跟進prepare方法一看究竟:
/**
* Prepare a request, including setting the encoding and locale.
*
* @param request The request
* @param response The response
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
// check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
encoding = "UTF-8";
}
Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}
if (encoding != null) {
applyEncoding(request, encoding);
}
if (locale != null) {
response.setLocale(locale);
}
if (paramsWorkaroundEnabled) {
request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
}
}
設定編碼的具體實現。嗯,程式碼寫的蠻好…
2.4 建立一個Action的上下文,並初始化一個本地執行緒
/**
* Creates the action context and initializes the thread local
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
// 計數器,作用:記錄這個Action被訪問的次數,與後續釋放ActionContext的記憶體空間有關,初始化為1
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
// 如果request域裡面已經有一個計數器,說明這個Action已經被例項化呼叫過了,那麼就將這個計數器的值加1
if (oldCounter != null) {
counter = oldCounter + 1;
}
// 和上面的計數器類似,嘗試從request獲取這個Action的上下文物件,如果存在就直接使用這個ActionContext
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 {
// 不存在就建立一個值棧(儲存了與這個Action相關的一些屬性)和一個ActionContext
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
ctx = new ActionContext(stack.getContext());
}
// 把這個計數器放到request域裡面
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
// 跟進這個方法後有這麼個註釋:Sets the action context for the current thread(把這個Action上下文物件放入當前執行緒)
ActionContext.setContext(ctx);
return ctx;
}
2.4.1 建立Action計數器
這個計數器可是有點用處:它用來記錄一個Action被呼叫的次數。那麼為什麼要記錄它被呼叫的次數呢?這裡先提前看一下doFilter方法的最後一步:prepare.cleanupRequest(request);這一步是用來清理掉該次請求所佔用的記憶體,跟進原始碼:
/**
* Cleans up a request of thread locals
*/
public void cleanupRequest(HttpServletRequest request) {
// 獲取request域中計數器
Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (counterVal != null) {
// 這個計數器的值不為空就把它減一
counterVal -= 1;
// 重新放入request域,用CLEARUP_RECURSION_COUNTER這個常量儲存
request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
// 關鍵:如果這個計數器在減一之後的值仍然大於0,那麼就不釋放它所佔用的記憶體,記錄一條日誌就直接返回了
if (counterVal > 0 ) {
if (log.isDebugEnabled()) {
log.debug("skipping cleanup counter="+counterVal);
}
return;
}
}
// 否則就clearUp掉
// always clean up the thread request, even if an action hasn't been executed
try {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.setContext(null);
Dispatcher.setInstance(null);
}
}
相信看到這裡,大家大致已經明白了這個計數器存在的意義:記錄Action被請求的次數,如果請求的次數非常頻繁,說明這個Action被呼叫的次數非常多,那麼就暫時不釋放掉它所佔用的記憶體,反之,如果只請求了一次或者是幾次,那麼在這個Action執行完畢後就會釋放掉它所佔用的記憶體。
2.4.2 建立ActionContext
跟進原始碼看一下具體的實現:
public class ActionContext implements Serializable {
static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();
// 處理請求的Action的name
public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";
// 與這個Action相關的值棧
public static final String VALUE_STACK = ValueStack.VALUE_STACK;
// session相關
public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";
// application域
public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
...
這個ActionContext儲存了一些與當前Action相關的資訊。
2.5 把dispatcher指派給本地執行緒-prepare.assignDispatcherToThread()
其實從上一步我們就不難看出struts2為每一個Action建立一個執行緒,這也體現了struts2相比於struts1的優勢:執行緒安全,因為每一個Action都由一個單獨的執行緒來負責,不存在共享資料,所以安全。我們進入PrepareOperations類的assignDispatcherToThread()方法的原始碼看一下:
public void assignDispatcherToThread() {
// 將當前的這個dispatcher例項化
Dispatcher.setInstance(dispatcher);
}
再進入Dispatcher的setInstance方法中看一下:
public static void setInstance(Dispatcher instance) {
// 呼叫了這個Dispatcher的instance屬性的set方法
// ps:這個instance是一個ThreadLocal物件
Dispatcher.instance.set(instance);
}
再進入set方法中看一下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
終於找到根源了,獲取到當前的執行緒,把dispatcher放入當前執行緒中。那麼這個Dispatcher有什麼作用呢?看一下原始碼,這個原始碼有點多,就摘抄一個重要的方法來看下吧:
/**
* 初始化一系列的配置檔案:default.properties, struts-default.xml, struts.properties, struts.xml...檔案,並且是按順序載入
*/
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_FileManager();
init_DefaultProperties(); // [1]default.properties
init_TraditionalXmlConfigurations(); // [2]struts-default.xml...
init_LegacyStrutsProperties(); // [3]struts.properties
init_CustomConfigurationProviders(); // [5]struts.xml
init_FilterInitParameters() ; // [6]初始化過濾器配置的引數
init_AliasStandardObjects() ; // [7]別名什麼的..這裡就不深究了
Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckWebLogicWorkaround(container);
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}
Dispatcher可以用來初始化一系列的配置檔案,並且是按序載入。
2.6 包裝一下request
request = prepare.wrapRequest(request);
wrapRequest方法的原始碼如下:
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
// Wrap request first, just in case it is multipart/form-data
// 為了防止這個請求是一個multipart/form-data(上傳檔案)型別的請求
// parameters might not be accessible through before encoding (ww-1278)
// 因為這種型別請求的引數如果不經過處理可能獲取不到
request = dispatcher.wrapRequest(request, servletContext);
} catch (IOException e) {
throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
}
return request;
}
為了防止這個請求是一個multipart/form-data(上傳檔案)型別的請求,將它包裝一下。因為這種型別請求的引數如果不經過處理可能獲取不到。繼續進入dispatcher.wrapRequest的原始碼中:
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
// don't wrap more than once
// 只包裝一次
if (request instanceof StrutsRequestWrapper) {
return request;
}
String content_type = request.getContentType();
// 如果請求型別不為空並且是multipart/form-data型別的請求,那麼就包裝一下
if (content_type != null && content_type.contains("multipart/form-data")) {
MultiPartRequest mpr = getMultiPartRequest();
LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
}
return request;
}
2.7 查詢該請求相關的資訊actionMapping
prepare.findActionMapping(request, response, true),原始碼如下:
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
// 這裡就開始詢問ActionMaper是否存在與該請求對應的業務控制類,詳見下面的第三大步
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} catch (Exception ex) {
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
return mapping;
}
發現這個ActionMapping其實是通過ActionMapper的getMapping方法來獲得的
ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);
發現這個ActionMapper是一個介面,這裡我啟動我的專案debug發現是呼叫了它的實現類:DefaultActionMapper的getMapping方法,實現細節如下:
tips:在eclipse中,按Ctrl+T可以檢視當前類的子類或者實現類
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
// 通過request工具類獲取當前請求的uri
String uri = RequestUtils.getUri(request);
int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
// 去掉字尾.action...
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
// 解析出configManager裡面設定的actionName和namespace並放入ActionMapping
parseNameAndNamespace(uri, mapping, configManager);
handleSpecialParameters(request, mapping);
return parseActionName(mapping);
}
在getMaping方法中,獲得當前請求中的uri以及配置檔案中配置的action的name和namespace,看一下這個parseNameAndNamespace方法的實現細節:
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
String namespace, name;
int lastSlash = uri.lastIndexOf("/");
...
// 將解析出來的name和namespace放入mapping
mapping.setNamespace(namespace);
mapping.setName(cleanupActionName(name));
}
從倒數兩行程式碼可以看出了該方法的最終目的:將解析出來的name和namespace放入mapping,交給呼叫者來根據這個actionMapping判斷請求是否有對應的業務控制類
2.8 沒有找到請求對應的業務控制類所進行的操作
說明並沒有為這個請求配置相應的業務控制類Action,就說明這個請求可能是一個靜態的資源請求,於是就有了如下程式碼:
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
// 這裡會根據上一步的返回值來確定是否執行
if (!handled) {
chain.doFilter(request, response);
}
}
檢視executeStaticResourceRequest方法的具體實現:
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// there is no action in this request, should we look for a static resource?
// 老外還挺逗:這個請求沒有對應的action來處理,我們應不應該看看它是不是請求一個靜態資源?
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
// 如果這個請求請求的資源在這個專案的資源路徑下,返回true
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
// The framework did its job here
// 屬於struts2管轄範圍的靜態資源,由struts2來處理
return true;
} else {
// this is a normal request, let it pass through
// 一個普通的請求,放行
return false;
}
}
簡單點說就是:這個靜態資源在專案中存在就返回,不存在就放行,交給其他的過濾器處理。
2.9 如果找到了這個請求對應的業務控制類Action
那就呼叫ExecuteOperations的executeAction方法去執行這個Action,原始碼如下:
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);
}
它又呼叫了Dispatcher的serviceAction方法,原始碼如下:
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {
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);
// 獲取ActionMapping中的namespace,name,method
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
// 建立ActionProxy代理
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!
// 如果mapping中有結果集,那麼就去執行結果集
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
// 之前已經有值棧了,就把它放入當前request域裡面
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
這一步主要做了三件事:建立Action的代理,封裝結果集Result,設定值棧。
實際上是通過呼叫DefaultActionProxyFactory的createActionProxy方法來建立的Action的代理
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
// 建立ActionInvocation負責迭代攔截器和執行Action
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
// 放入容器中
container.inject(inv);
// 呼叫這個過載的createActionProxy方法
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
在方法返回的時候呼叫過載的createActionProxy方法,進入原始碼看一下:
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開始準備
proxy.prepare();
return proxy;
}
看這裡:proxy.prepare();,proxy開始準備,繼續跟原始碼:
protected void prepare() {
String profileKey = "create DefaultActionProxy: ";
try {
UtilTimerStack.push(profileKey);
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
if (config == null) {
throw new ConfigurationException(getErrorMessage());
}
resolveMethod();
if (!config.isAllowedMethod(method)) {
throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
}
// 關鍵:開始初始化invocation
invocation.init(this);
} finally {
UtilTimerStack.pop(profileKey);
}
}
準備的關鍵程式碼就是初始化invocation,點進去一看又是一個介面,Ctrl+T,選中DefaultActionInvocation這個實現類,檢視它的init方法:
public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();
// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
// 建立Action(關鍵)
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
// get a new List so we don't get problems with the iterator if someone changes the list
// 看到了吧,在這裡獲得了與action相關的攔截器,共19個
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
// 迭代(就是執行所有的攔截器)
interceptors = interceptorList.iterator();
}
終於找到了建立Action的程式碼了,繼續跟進原始碼:
protected void createAction(Map<String, Object> contextMap) {
// load action
String timerKey = "actionCreate: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
// 可以看到在這裡通過工廠類來建立Action
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
} catch (InstantiationException e) {
...
}
經過這一步,終於通過了DefaultActionInvocation建立了Action。
然後我們回到上一步,成功建立Action之後就獲取與之相關的攔截器列表,並用一個list集合裝起來,依次迭代它們。
// get a new List so we don't get problems with the iterator if someone changes the list
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
攔截器執行完畢後再執行Action,再封裝結果集,再出攔截器,給客戶端響應。
2.10 最終Action執行完畢一定要clear掉
防止記憶體洩漏(記憶體洩漏是指分配出去的記憶體不再使用,但是無法回收),當然在clear的時候還是要根據前面提到的計數器來判斷是否清除。
struts1和struts2的區別
- struts1的業務控制類必須繼承ActionSupport,struts2可以不用繼承
- struts1是單例的,存線上程安全問題,struts2是多例的,不存線上程安全問題
- struts1的業務控制類需要依賴servletAPI,struts2不需要
- struts1對於頁面請求的引數是通過一個ActionForm表單來收集的,struts2直接通過攔截器注入
- struts1的業務流程是固定的(可以參考我的另一篇部落格struts1原理),struts2可以通過攔截器改變這個流程
- struts1是通過servlet來匹配所有的請求,struts2是通過filter來匹配所有的請求