Struts2: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,然後在根據返回結果進行頁面跳轉。
其他好的文章
在這裏插入圖片描述

在這裏插入圖片描述

發佈了40 篇原創文章 · 獲贊 22 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章