struts2:
前言
struts2的實現主要依賴一個核心過濾器,StrutsPrepareAndExecuteFilter。
StrutsPrepareAndExecuteFilter的源碼:
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;
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(dispatcher);
execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
/**
* Callback for post initialization
*/
protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
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);
}
}
public void destroy() {
prepare.cleanupDispatcher();
}
}
簡單說說什麼是過濾器?
Filter和Servlet一樣都是在服務器中運行的,因此也是需要在web.xml進行配置
過濾器實現了Filter接口,用於在請求資源或者響應資源,或者請求和響應資源的時候,執行過濾任務。
public class FilterTest implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
public void init(FilterConfig fConfig) throws ServletException {
System.out.println("filter被初始化了。。。。。。。。");
}
}
相關方法說明:
init():初始化,在創建過濾器對象的時候會被調用。在過濾器在web應用啓動時創建,就只創建一次,以後再調用也不會初始化。
doFilter():執行過濾的主要方法,用於過濾請求和響應,請求一次就調用一次,可以調用多次。
destory():銷燬方法,過濾器對象銷燬的時候會被調用,也只是調用一次。
StrutsPrepareAndExecuteFilter的init()方法—配置文件的加載順序
好了現在我們就說一說 StrutsPrepareAndExecuteFilter 在tomcat啓動的時候做了哪些初始化的工作。
首先我們來看StrutsPrepareAndExecuteFilter當中的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(dispatcher);
execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
然後呢,我們需要了解下面這句語句,這條語句的意思是 進行初始化,加載配置文件
dispatcher = init.initDispatcher(config);
然後我們查看 dispatcher = init.initDispatcher(config) 相關源碼。
/**
* Creates and initializes the dispatcher
*/
public Dispatcher initDispatcher( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
繼續查看 dispatcher.init(); 的源碼。
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_FileManager();
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_CheckWebLogicWorkaround(container);
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
errorHandler.init(servletContext);
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}
配置文件的加載順序:
init_DefaultProperties();----------------------加載default.properties
init_TraditionalXmlConfigurations();-------加載struts-default.xml、struts-plugin.xml、struts.xml
init_LegacyStrutsProperties();--------------加載struts.properties
init_CustomConfigurationProviders();----加載配置提供類
init_FilterInitParameters();-------------------加載web.xml中過濾器初始化參數
init_AliasStandardObjects();----------------加載Bean對象
default.properties
struts-default.xml
struts-plugin.xml
struts.xml
struts.properties
web.xml
注意:後配置的常量的值會覆蓋先配置的常量的值。
StrutsPrepareAndExecuteFilter的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
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);
}
}
ActionMapping mapping = prepare.findActionMapping(request, response, true);
這個句話的意思是,根據url創建一個ActionMapping對象。
ActionMapping部分源碼如下:
public class ActionMapping {
private String name;
private String namespace;
private String method;
private String extension;
private Map<String, Object> params;
private Result result;
//這裏呢?我只展示ActionMapping具有的屬性
}
然後我們接着查看findActionMapping(request, response, true);的源碼
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
//STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping"
//提取request中的ActionMapping
//如果有"struts.actionMapping"對應的request的,
//就執行dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
//如果沒有就執行
//mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
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, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
return mapping;
}
然後我們查看getMapping(request, dispatcher.getConfigurationManager())的源碼
這個方法是由DefaultActionMapper類提供
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
//創建一個ActionMapping
ActionMapping mapping = new ActionMapping();
//得到request裏面的url
String uri = RequestUtils.getUri(request);
int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
parseNameAndNamespace(uri, mapping, configManager);
handleSpecialParameters(request, mapping);
return parseActionName(mapping);
}
總的來說這個方法:創建一個ActionMapping,然後提取url,然後將url拆解,並將其賦值給ActionMapping中的
name;namespace;method;extension;params;這些屬性。
現在我們來看看這個方法中的 uri = dropExtension(uri, mapping); 查看相關源代碼
protected List<String> extensions = new ArrayList<String>() {{
add("action");
add("");
}};
protected String dropExtension(String name, ActionMapping mapping) {
if (extensions == null) {
return name;
}
for (String ext : extensions) {
if ("".equals(ext)) {
// This should also handle cases such as /foo/bar-1.0/description. It is tricky to
// distinquish /foo/bar-1.0 but perhaps adding a numeric check in the future could
// work
int index = name.lastIndexOf('.');
if (index == -1 || name.indexOf('/', index) >= 0) {
return name;
}
} else {
String extension = "." + ext;
if (name.endsWith(extension)) {
name = name.substring(0, name.length() - extension.length());
mapping.setExtension(ext);
return name;
}
}
}
return null;
}
我說一下這裏的大致意思:
我們都知道,在struts2中的 /org/apache/struts2/default.properties 的 這個屬性文件中有這樣一條屬性
struts.action.extension=action, ,
這個使用來規定我們struts2框架訪問服務器的時候需要設置的後綴名。(假設我們配置如下)
<package name="demo1" extends="struts-default" namespace="/">
<action name="actionDemo" class="com.items.struts.demo.ActionDemo"></action>
</package>
那我們想要訪問這個action的時候,我們需要輸入的url就是(在默認配置下)
http://localhost:8080/Struts2_test/actionDemo.action
或者
http://localhost:8080/Struts2_test/actionDemo
而這個dropExtension() 方法就是來判斷這個url的後綴是否滿足我們的配置,如果滿足就進行其他操作,
如果不滿足就返回null,那麼 url 也就等於 null 然後又向上返回 ,返回到findActionMapping() 方法,接着又向上返回,返回到StrutsPrepareAndExecuteFilter的doFilter() 方法,最終的到 mapping = null,然後放行。
下面展示,輸入錯誤的url後,遊覽器的相應結果圖:
http://localhost:8080/Struts2_test/actionDemo.cxf
然後當我們輸入正確的後綴名,但是沒有配置這個action。
http://localhost:8080/Struts2_test/actionDemo3.action
那麼這個時候服務器就會報錯:沒有找到目標action,這個就與ActionProxy這個類有關呢。
當我們將url分割然後賦值給ActionMapping後,然後我們就要執行
StrutsPrepareAndExecuteFilter的doFilter()中execute.executeAction(request, response, mapping);代碼
查看execute.executeAction(request, response, mapping); 源碼
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, mapping);
}
然後繼續查看 dispatcher.serviceAction(request, response, mapping); 源碼
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 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();
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) {
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) {
logConfigurationException(request, e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
這段代碼非常的長,但是它最主要的部分是:
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
然後我們查看createActionProxy(namespace, name, method, extraContext, true, false); 源碼
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);
}
在這個源碼中,我們會創建一個 ActionInvocation這個類,這個類呢,是用來執行struts2的默認的一些攔截器。下面我們會進行一些說明。
整體而言這個方法是,通過mapping,我們可以得到namespace,name ,method ,然後呢,根據這些條件,到我們配置的struts.xml 中找到目標action,沒有找到那麼就會想上面,我們輸入沒有配值action的url那樣,在控制檯上報錯,找到了就進行下面的操作,proxy.execute(); 操作。
然後查看proxy.execute(); 源碼
protected ActionInvocation invocation;
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;
}
這裏面最重要的就是invocation.invoke(); 執行stuts2的默認的攔截器
然後我們查看invocation.invoke();的源代碼
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) {
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);
}
}
代碼中最重要的部分就是這段
//判斷是否還又下一個攔截器
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
//執行攔截器中的intercept方法
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
然後我們查看interceptor.getInterceptor().intercept(DefaultActionInvocation.this);的源碼
因爲struts2中爲我們配飾的攔截器很多,所以我值展示一個攔截器的源碼,
DeprecationInterceptor 攔截器中的intercept方法
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (devMode) {
String message = validate();
if (message != null) {
LOG.debug(message);
}
}
return invocation.invoke();
}
然後我們又在次查看invocation.invoke();源碼
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();
}
//。。。。。。
}
然後我們又會再次回到ActionInvocation這個實現類。
最終我想說的是在struts2中運行攔截器,是用遞歸的方式運行的。
下面呢?在簡單的說說攔截器,在struts2中攔截器的配置在
struts2-core-2.3.24.jar/struts-default.xml這xml文件中
用長方形圈出來的就是struts2的攔截器,用橢圓圈出來的就是這個package的名字,這也是爲什麼我們在配置中要繼承struts-default的原因,還有隻有設置了abstract ="true"纔可以繼承。
最後:
當攔截器執行完了,就會執行目標Action,根據Action的返回的結果進行頁面跳轉。
總結—Struts2的執行流程
客戶端向服務器端發送一個action請求,然後經過,StrutsPrepareAndExecuteFilter 中的 doFilter() 方法,然後調用findActionMapping()方法,然後在執行裏面的getMapping() 方法,這個方法中將request中的url拆分,並將其拆分後的東西賦值給ActionMapping,然而,在這個拆分的過程中,會執行一個dropExtension()方法,這個方法就是,判斷這個url的後綴中是否滿足我們的配置(如:需要 .action結尾)如果不滿住,那麼就會不停的向上返回null,然後得到mapping = null,然後在退出這個StrutsPrepareAndExecuteFilter 過濾器,如果滿足,那麼就執行doFilter()中的executeAction() 方法,然後在方法的內部執行serviceAction() 方法,然後根據我們的到mapping,去struts.xml去尋找我們的目標action,如果沒有找到,那麼控制檯就會報錯,如果找到了,那麼就會創建一個ActionProxy 的代理對象,然後執行ActionProcxy中的execute() 方法,然後在這個execute()方法中會調用ActionInvocation的invoke() 方法,這個方法執行的是struts2中默認的攔截器(用遞歸的方法去執行),執行完攔截器之後,然後執行目標action,然後在根據返回結果進行頁面跳轉。
其他好的文章