1. 程式人生 > 實用技巧 >Struct2

Struct2

推薦文章:

官網

struts2:

https://www.cnblogs.com/mfmdaoyou/p/7189578.html

IBM:

https://www.ibm.com/developerworks/cn/java/j-lo-struct2/index.html?mhq=struts2%E5%8E%9F%E7%90%86&mhsrc=ibmsearch_a

WebWork:

http://www.blogjava.net/moxie/archive/2006/10/20/76375.html

剛開始出來找工作時,自己很菜,那時候在網上看了一系列的視訊,去學習struts2、spring、spring mvc,但是也就只是會用,沒有一點深入,現在在我打實了servlet基礎後,打算深入學習一下struts,雖然說這東西可能算是有些過時了,但是經典必然有其經典之處,希望自己能有較多收穫。

發展歷程

工作流程

流程

原始碼分析

例項

接下來要讀

webwork

struts2(2.1.3之前的版本使用的是FilterDispatcher,在其之後開始使用StrutsPrepareAndExecuteFilter或者StrutsPrepareFilter和StrutsExecuteFilterif。除此之外,還使用ActionContextCleanUp過濾器)

發展歷程

一門技術的出現絕非偶然,肯定是原來的東西讓人使用起來不方便。學習了Servlet之後,我並沒有開始繼續探索JSP技術,原因大概就是在我眼中JSP就是View檢視,MVC思想已經存在了我的腦海裡,所以我不直接去將大量的Java程式碼嵌入JSP中,但是在開始Struts時我還真的又沿著這條歷史發展線路,回頭看了一下JSP技術。

JSP技術是在Servlet之後產生的,原因就是使用Servlet的PrintWriter一直向頁面輸出HTML標籤過於麻煩,難受,才有了JSP。JSP最終是要被編譯成Servlet才能處理使用者請求,因此JSP擁有Servlet的所有功能和特性。既然JSP擁有了Servlet的所有功能和特性,這樣你就可以在一個JSP中即處理業務邏輯又能顯示到頁面,不將Servlet和JSP劃分開也行。那樣的話JSP中檢視和處理業務(操作bean)的程式碼將混雜在一起,某個JSP可能並不做展示用,而僅僅是操作javaBean,基於JSP的Web應用程式有時混合了資料庫程式碼,頁面設計程式碼和控制流程式碼,很難維護,這點你要回顧一下JSP筆記中的MVC發展線。oracle將MVC思想引入到java中,在web開發層面使層與層分開,各司其職,邏輯會更清晰。

struts2並不是是在MVC思想蔓延後的第一個mvc框架,但是它有許多優秀的理念值得學習。在學習struts2之前,你至少要深入的瞭解一下struts1,webwork是幫助你學習struts2不可缺少的一部分,你也該去了解一下。

工作流程

流程

初始的請求通過一條標準的過濾器鏈,到達 servlet 容器 ( 比如 tomcat 容器,WebSphere 容器 )。

過濾器鏈包括可選的 ActionContextCleanUp 過濾器,用於系統整合技術,如 SiteMesh 外掛。

接著呼叫 FilterDispatcher,FilterDispatcher 查詢 ActionMapper,以確定這個請求是否需要呼叫某個 Action。

如果 ActionMapper 確定需要呼叫某個 Action,FilterDispatcher 將控制權交給 ActionProxy。

ActionProxy 依照框架的配置檔案(struts.xml),找到需要呼叫的 Action 類。

ActionProxy 建立一個 ActionInvocation 的例項。ActionInvocation 先呼叫相關的攔截器 (Action 呼叫之前的部分),最後呼叫 Action。

一旦 Action 呼叫返回結果,ActionInvocation 根據 struts.xml 配置檔案,查詢對應的轉發路徑。返回結果通常是(但不總是,也可能是另外的一個 Action 鏈)JSP 技術或者 FreeMarker 的模版技術的網頁呈現。Struts2 的標籤和其他檢視層元件,幫助呈現我們所需要的顯示結果。在此,我想說清楚一些,最終的顯示結果一定是 HTML 標籤。標籤庫技術和其他檢視層技術只是為了動態生成 HTML 標籤。

接著按照相反次序執行攔截器鏈 ( 執行 Action 呼叫之後的部分 )。最後,響應通過濾器鏈返回(過濾器技術執行流程與攔截器一樣,都是先執行前面部分,後執行後面部)。如果過濾器鏈中存在 ActionContextCleanUp,FilterDispatcher 不會清理執行緒區域性的 ActionContext。如果不存在 ActionContextCleanUp 過濾器,FilterDispatcher 會清除所有執行緒區域性變數。

理解

客戶端初始化一個指向Servlet容器(例如Tomcat)的請求request

這個請求經過一系列的過濾器(Filter)。這些過濾器中有一個叫做ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其他框架的整合很有幫助,例如:SiteMesh Plugin。

接著StrutsPrepareAndExecuteFilter被呼叫,StrutsPrepareAndExecuteFilter詢問ActionMapper來決定這個請求是否需要呼叫某個Action;

如果ActionMapper決定需要呼叫某個Action,StrutsPrepareAndExecuteFilter把請求的處理交給ActionProxy;

ActionProxy通過ConfigurationManager詢問框架的配置檔案,找到需要呼叫的Action類;

ActionProxy建立一個ActionInvocation的例項。

ActionInvocation例項使用命名模式來呼叫,在呼叫Action的過程前後,涉及到相關攔截器(Intercepter)的呼叫。

一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果

原始碼分析

struts2.3.4版本分析。關於StrutsPrepareAndExecuteFilter的功能說明:處理Struts排程過程的準備和執行階段。下面是該過濾器大致程式碼:

/**
* 處理Struts排程過程的準備和執行階段。
* 當你沒有其他需要訪問操作上下文資訊(如SiteMesh)的篩選器時,
* 這個過濾器比較好用。
*/
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();
        try {
            // 包裝filterconfig的主機配置
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            /**
            * 從web.xml的filter配置中讀取param-name=loggerFactory的資訊
            * ,初始化日誌
            */
            init.initLogging(config);
            /**
            * 初始化轉發器。讀取web.xml內所有的配置資訊filter,filter-mapping
            * 在這裡filter-mapping通常配置的是攔截所有,也就是說讓轉發器處理
            * 所有請求。
            */
            Dispatcher dispatcher = init.initDispatcher(config);
            /**
            * 使用帶過濾器配置的載入程式初始化靜態內容
            */
            init.initStaticContentLoader(config, dispatcher);
            
            /**
            * 使用dispatcher和servletcontext建立兩個全域性物件,備用
            *   記住:prepare和execute裡面最開始時裝的內容是一樣的,
            *   都是dispatcher和servletContext
            */
            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);
        } finally {
            // 清掉ActionContext
            init.cleanup();
        }

    }

    //  no content in postInit method

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            // 給dispatcher設定Encoding
            prepare.setEncodingAndLocale(request, response);
            
            /**
            * 建立操作上下文並初始化本地執行緒
            *   1、每一個請求過來都會建立一個Action上下文,Action上下文中包
            *      含一些Servlet的物件,比如:request,session等等。
            *   2、初始化一個值堆疊(Action不僅作為控制器,還被當作一個實體)
            *      值堆疊中儲存的是該實體的相關屬性
            *   3、將Action上下文也放入值堆疊中
            */
            prepare.createActionContext(request, response);
            
            // 將排程程式分配給排程程式執行緒本地
            prepare.assignDispatcherToThread();
            
            /**
            * 如果只在web.xml中配置了filter-mapping.url-pattern=/*,那麼
            * StrutsPrepareAndExecuteFilter會攔截所有的請求(也就是下面只走else)
            * 但是如果你在struts.xml中配置了struts.action.excludePattern,那麼
            * 當一個請求匹配上了excludePattern中的正則表示式,就不會在讓
            * StrutsPrepareAndExecuteFilter去處理這個請求了。我嘗試在
            * struts.xml配置瞭如下項:
            *     <constant name="struts.action.excludePattern" value="/*.jsp"></constant>
            * debug啟動後,在如下if判斷裡的isUrlExcluded處打個斷點
            * 然後訪問專案:8080/Struts2Base/index.jsp,在詭異的一幕出現了,
            * 你會發現它明明執行了isUrlExcluded方法中的return true,但是
            * 最後又執行了這個方法裡最後的return false.這簡直讓我難以理解。其實
            * 我在struts.xml配置excludePattern只是為了幫助這個Filter省力
            * 而已,因為我看到它會在else裡處理對靜態檔案的請求(先去找有沒
            * 有Action,在去看看是不是靜態資源),所以我配置了excludePattern,
            * 對於靜態檔案讓它直接去找,這樣就不必在去找一遍Action了。然而我
            * 得到的是詭異的true-false.
            *
            * 當一個請求過來,它會先去檢查這個請求是否與“排除列表”中的某個
            * 字尾匹配,如果匹配就直接通過(chain.doFilter),不匹配就進入else。
            */
        if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
            chain.doFilter(request, response);
        } else {
          /**
          * 包裝request請求,以訪它是一個multipart/form-data請求。
          * 從Mapping列表中找有沒有該request請求的Mapping,如果
          * 有,就交給這個這個Action去執行。沒有的話,看看它訪問的是不是靜態
          * 資源,如果是就不處理,交給後面的chain.訪問的也不是靜態資源,
          * 那就不鳥這個請求。
          */
            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);
        }
    }
    // destroy method...
}
// 在上面execute.executeAction->serviceAction

    /**
     * Load Action類用於對映和呼叫相應的Action方法,或直接轉到Result。
     * 此方法首先從給定引數建立操作上下文,然後從給定操作名稱和命名空
     * 間載入ActionProxy。之後,執行Action方法並通過響應物件輸出通道。
     * 未找到的操作將使用404返回程式碼通過Dispatcher#sendError方法發回給使用者。
     * 丟擲ServletException報告所有其他錯誤。
     */
    public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {
        // 建立ContextMap和值棧這些東西基本上和WebWork是大差不差的,不羅嗦了。
        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 {
            // 獲取Action資訊,通過反射建立代理,設定被呼叫的方法
            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) {
                String reqStr = request.getRequestURI();
                if (request.getQueryString() != null) {
                    reqStr = reqStr + "?" + request.getQueryString();
                }
                LOG.error("Could not find action or result\n" + reqStr, e);
            }
            else {
                    if (LOG.isWarnEnabled()) {
                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);
        }
    }

如果你認真看過WebWork的原始碼並分析過後,你會發現為什麼說Struts2是包裝了WebWork核心的外殼了,Strut擁有強大的社群,WebWork有先進的技術,強強聯手。

對照著架構藍圖,閱讀起程式碼來,相對好理解很多。要補充的是在Webwork中一些關於

# webwork.properties
webwork.devMode=true
webwork.action.extension=action,do

等設定是放在webwork.properties裡的,在struts2中將這些設定用constant標籤包裝了一下,放在了struts.xml裡。

# struts.xml
<struts>

    <constant name="struts.devMode" value="true" />
    
    <constant name="struts.action.excludePattern" value="/*.jsp"></constant>

    <package name="basicstruts2" extends="struts-default">
        <action name="index">
            <result>/index.jsp</result>
        </action>

更多關於值棧等內容,可以參見webwork,想深入學習struts2就在回頭去探索webwork吧!

例項

本文附帶了一個簡單的例項案例,詳細可參見碼雲struts

官網上也有不少例項,比較推薦官網上,自己動手去搭建的。

接下來要讀

spring技術