Struts 2詳細工作流程

Struts 2框架本身大致可以分爲3個部分:核心控制器FilterDispatcher、業務控制器Action和用戶實現的企業業務邏輯組件。

3.1.1  核心控制器FilterDispatcher

 

核心控制器FilterDispatcher是Struts 2框架的基礎,包含了框架內部的控制流程和處理機制。業務控制器Action和業務邏輯組件是需要用戶來自己實現的。用戶在開發Action和業務邏輯組件的同時,還需要編寫相關的配置文件,供核心控制器FilterDispatcher來使用。

Struts 2的工作流程相對於Struts 1要簡單,與WebWork框架基本相同,所以說Struts 2是WebWork的升級版本。Struts 2框架按照模塊來劃分,可以分爲Servlet Filters、Struts核心模塊、攔截器和用戶實現部分。Struts 2框架結構圖如圖3.1所示。

圖3.1  Struts 2框架結構圖

一個請求在Struts 2框架中的處理大概分爲以下幾個步驟。

 客戶端提交一個(HttpServletRequest)請求,如上文在瀏覽器中輸入http://localhost: 8080/bookcode/ch2/Reg.action就是提交一個(HttpServletRequest)請求。

 請求被提交到一系列(主要是3層)的過濾器(Filter),如(ActionContextCleanUp、其他過濾器(SiteMesh等)、FilterDispatcher)。注意:這裏是有順序的,先ActionContext CleanUp,再其他過濾器(Othter Filters、SiteMesh等),最後到FilterDispatcher。

  FilterDispatcher是控制器的核心,就是MVC的Struts 2實現中控制層(Controller)的核心。

  FilterDispatcher詢問ActionMapper是否需要調用某個Action來處理這個(HttpServlet Request)請求,如果ActionMapper決定需要調用某個Action,FilterDispatcher則把請求的處理交給ActionProxy。

  ActionProxy通過Configuration Manager(struts.xml)詢問框架的配置文件,找到需要調用的Action類。例如,用戶註冊示例將找到UserReg類。

  ActionProxy創建一個ActionInvocation實例,同時ActionInvocation通過代理模式調用Action。但在調用之前,ActionInvocation會根據配置加載Action相關的所有Interceptor(攔截器)。

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

Struts 2的核心控制器是FilterDispatcher,有3個重要的方法:destroy()、doFilter()和Init(),可以在Struts 2的下載文件夾中找到源代碼,如代碼3.1所示。

代碼3.1  核心控制器FilterDispatcher

 

public class FilterDispatcher implements StrutsStatics, Filter {

    /**

     * 定義一個Log實例

     */

private static final Log LOG = LogFactory.getLog(FilterDispatcher.class);

… ...

    /**

     * 存放屬性文件中的.STRUTS_I18N_ENCODING值

     */

    private static String encoding;

    /**

     * 定義ActionMapper實例

     */

    private static ActionMapper actionMapper;

    /**

     * 定義FilterConfig實例

     */

    private FilterConfig filterConfig;

    protected Dispatcher dispatcher;

    /**

     * 創建一個默認的dispatcher,初始化filter

     * 設置默認的packages     *

     */

    public void init(FilterConfig filterConfig) throws ServletException {

     this.filterConfig = filterConfig;

       dispatcher = createDispatcher(filterConfig);

        dispatcher.init();

        String param = filterConfig.getInitParameter("packages");

        String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging";

        if (param != null) {

            packages = param + " " + packages;

        }

        this.pathPrefixes = parse(packages);

    }

    //銷燬filter方法

   public void destroy() {

        if (dispatcher == null) {

            LOG.warn("something is seriously wrong, Dispatcher is not initialized (null) ");

        } else {

            dispatcher.cleanup();

        }

    }

    /**

     * 處理一個Action或者資源請求

     * <p/>

     * filter嘗試將請求同action mapping相匹配

     * 如果找到,將執行dispatcher的serviceAction方法

     * 如果Action處理失敗, doFilter將建立一個異常

     * <p/>

     * 如果請求靜態資源

     * 資源將被直接複製給 response

     * <p/>

     * 如果找不到匹配Action 或者靜態資源,則直接跳出

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

        HttpServletRequest request = (HttpServletRequest) req;

        HttpServletResponse response = (HttpServletResponse) res;

        ServletContext servletContext = getServletContext();

        String timerKey = "FilterDispatcher_doFilter: ";

        try {

            UtilTimerStack.push(timerKey);

            request = prepareDispatcherAndWrapRequest(request, response);

            ActionMapping mapping;

            try {

                mapping=actionMapper.getMapping(request, dispatcher.getConfigurationManager());

            } catch (Exception ex) {

                LOG.error("error getting ActionMapping", ex);

                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);

                return;

            }

            if (mapping == null) {

                String resourcePath = RequestUtils.getServletPath(request);

                if ("".equals(resourcePath) && null != request.getPathInfo()) {

                    resourcePath = request.getPathInfo();

                }

                if (serveStatic && resourcePath.startsWith("/struts")) {

                    String name = resourcePath.substring("/struts".length());

                    findStaticResource(name, request, response);

                } else {

                    //爲一個普通的request, 則通過

                    chain.doFilter(request, response);

                }

                return;

            }

/**

*這個方法詢問ActionMapper是否需要調用某個Action來處理這個(request)請求,

*如果ActionMapper決定需要調用某個Action,

*FilterDispatcher則把請求的處理交給ActionProxy

            dispatcher.serviceAction(request, response, servletContext, mapping);

        } finally {

            try {

                ActionContextCleanUp.cleanUp(req);

            } finally {

                UtilTimerStack.pop(timerKey);

            }

        }

}

… …

}

 

在doFilter()方法中,將調用dispatcher.serviceAction,該方法如果找到相應的Action,將把用戶請求交給ActionProxy。serviceAction()代碼在Dispatcher.java中,如代碼3.2所示。

代碼3.2  Dispatcher類

 

public class Dispatcher {

...

/**

     * 爲mapping加載類,並調用相應的方法或者直接返回result

     * <p/>

     * 根據用戶請求的參數,建立Action上下文

     * 根據指定的Action’名稱和包空間名稱,加載一個Action代理 <tt>ActionProxy</tt> 

     * 然後Action的相應方法將被執行,

     */

    public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException {

        Map<String, Object> extraContext = createContextMap(request, response, mapping, context);

        //如果存在一個值棧,則建立一個新的並複製以備Action使用

        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

        if (stack!= null) {

            extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));

        }

        String timerKey = "Handling request from Dispatcher";

        try {

            UtilTimerStack.push(timerKey);

            String namespace = mapping.getNamespace();

            String name = mapping.getName();

            String method = mapping.getMethod();

            Configuration config = configurationManager.getConfiguration();

            //FilterDispatcher把請求的處理交給ActionProxy

            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, extraContext, true, false);

            proxy.setMethod(method);

            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

            //ActionMapping 直接返回一個result

            if (mapping.getResult() != null) {

                Result result = mapping.getResult();

                result.execute(proxy.getInvocation());

            } else {

                proxy.execute();

            }

            if (stack != null) {

                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);

            }

        } catch (ConfigurationException e) {

            LOG.error("Could not find action or result", e);

            sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);

        } catch (Exception e) {

            throw new ServletException(e);

        } finally {

            UtilTimerStack.pop(timerKey);

        }

    }





 

從上面代碼中可以看出來,Struts 2用於處理用戶請求的Action實例,並不是用戶實現的業務控制器,而是Action代理。關於Action代理相關內容,讀者可以參考攔截器章節的介紹。

★ 提示 ★

前面一直在說Action可以是一個普通的Java類,與Servlet API完全分離,但是爲了實現業務邏輯,Action需要使用HttpServletRequest內容。

 

Struts 2設計的精巧之處就是使用了Action代理,Action代理可以根據系統的配置,加載一系列的攔截器,由攔截器將HttpServletRequest參數解析出來,傳入Action。同樣,Action處理的結果也是通過攔截器傳入HttpServletResponse,然後由HttpServletRequest傳給用戶。

其實,該處理過程是典型的AOP(面向切面編程)的方式,讀者可以在後面詳細瞭解到。Struts 2處理過程模型如圖3.2所示。

圖3.2  Struts 2處理過程模型

★ 說明 ★

攔截器是Struts 2框架的核心,通過攔截器,實現了AOP(面向切面編程)。使用攔截器,可以簡化Web開發中的某些應用,例如,權限攔截器可以簡化Web應用中的權限檢查。

 

3.1.2  業務控制器Action

 

業務控制器Action是由開發者自己編寫實現的,Action類可以是一個簡單的Java類,與Servlet API完全分離。Action一般都有一個execute()方法,也可以定義其他業務控制方法,詳細內容將在後面介紹。

Action的execute()返回一個String類型值,這與Struts 1返回的ActionForward相比,簡單易懂。Struts 2提供了一個ActionSupport工具類,該類實現了Action接口和validate()方法,一般開發者編寫Action可以直接繼承ActionSupport類。編寫Action類後,開發者還必須在配置文件中配置Action。一個Action的配置應該包含下面幾個元素:

— 該Action的name,即用戶請求所指向的URL。

— Action所對應的class元素,對應Action類的位置。

— 指定result邏輯名稱和實際資源的定位。

Action是業務控制器,筆者建議在編寫Action的時候,儘量避免將業務邏輯放到其中,儘量減少Action與業務邏輯模塊或者組件的耦合程度。

3.1.3  業務模型組件

 

業務模型組件可以是實現業務邏輯的模塊,可以是EJB、POJO或者JavaBean,在實際開發中,對業務模型組件的區分和定義也是比較模糊的,實際上也超出了Struts 2框架的範圍。不同的開發者或者團隊,都有自己的方式來實現業務邏輯模塊,Struts 2框架的目的就是使用Action來調用業務邏輯模塊。例如一個銀行存款的業務邏輯模塊,如代碼3.3所示。

代碼3.3  模擬一個銀行業務的實現模塊

 

package ch3;

public class Bank {

    //定義銀行賬戶

    private String accounts;

    //定義操作金額

    private double money;

    //屬性的getter和setter方法

    public String getAccounts() {

        return accounts;

    }

    public void setAccounts(String accounts) {

        this.accounts = accounts;

    }

    public double getMoney() {

        return money;

    }

    public void setMoney(double money) {

        this.money = money;

    }

    //模擬銀行存款方法

    public boolean saving(String accounts, double money) {

        //調用DAO等模塊讀寫數據庫

        return dosomeing();

    }

 

}

 

上面實例在實際開發中沒有任何意義,這裏只是作爲業務邏輯模塊來說明,在執行saving(String accounts,double money)方法時,可以調用相應的數據庫訪問其他組件,來實現存款操作。使用Action調用該業務邏輯組件可以在execute()方法中實現,如代碼3.4所示。

代碼3.4  業務控制器Bank_Saving_Action

package ch3;

import java.util.Map;

import com.opensymphony.xwork2.ActionContext;

import com.opensymphony.xwork2.ActionSupport;

 

public class Bank_Saving_Action extends ActionSupport {

    //定義銀行賬戶

    private String accounts;

    //定義操作金額

    private double money;

    

    public String execute() throws Exception {

        //創建Bank實例

        Bank bk=new Bank();

        //調用存款方法

        if (bk.saving(accounts, money)){

        return SUCCESS;

        }else{

        return ERROR;

        }

}

    //屬性的getter和setter方法

    public String getAccounts() {

        return accounts;

    }

 

    public void setAccounts(String accounts) {

        this.accounts = accounts;

    }

 

    public double getMoney() {

        return money;

    }

 

    public void setMoney(double money) {

        this.money = money;

    }

 

Bank_Saving_Action演示了對銀行存款業務邏輯組件的調用,這裏是通過在Action中創建業務邏輯組件實例的方式實現的。在實際開發中,可以使用靜態工廠獲得業務邏輯組件的實例或者使用IoC容器來管理。Action中不實現任何業務邏輯,只是負責組織調度業務邏輯組件。調用關係如圖3.3所示。

圖3.3  調用業務邏輯組件

★ 說明 ★

業務控制器Action一般情況下不是直接創建業務邏輯組件實例,而是使用工廠模式或者是從Spring容器中獲得業務邏輯組件實例,這樣可以提高系統的性能。

 

3.1.4  視圖組件

 

Struts 1只能支持JSP作爲視圖資源,而Struts 2的進步之處就是可以使用其他視圖技術,如FreeMarker、Velocity等。通過前面的學習和示例,讀者會知道Action的返回結果只是一個簡單的字符串,也就是一個邏輯上的視圖名稱,要與實際視圖資源對應,必須通過配置文件來實現。

在struts.xml配置文件中,每一個Aciton定義都有name和class屬性,同時還要指定result元素。result元素指定了邏輯視圖名稱和實際視圖的對應關係。每個result都有一個type屬性,前面介紹的struts.xml中並沒有顯式指定type值,即使用了默認的type類型:dispatcher,該結果類型支持JSP所謂視圖資源。

對於Struts 2的視圖技術和result返回類型,後面將詳細介紹。總結Strurs 2的框架工作流程,發現與WebWork基本相同,可以參考第1章關於WebWork框架的介紹和流程圖(如圖1.8所示)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章