自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

設(shè)計(jì)REST風(fēng)格的Java MVC框架:WebWind

開(kāi)發(fā) 后端
傳統(tǒng)的 Java EE MVC 框架如 Struts 等都是基于 Action 設(shè)計(jì)的后綴式映射,然而,流行的 Web 趨勢(shì)是 REST 風(fēng)格的架構(gòu)。本文將講述如何從頭設(shè)計(jì)一個(gè)基于 REST 風(fēng)格的 Java MVC 框架,配合 Annotation,最大限度地簡(jiǎn)化 Web 應(yīng)用的開(kāi)發(fā)。

本文轉(zhuǎn)載自IBM Developer Works廖雪峰的文章,原文標(biāo)題為《設(shè)計(jì) REST 風(fēng)格的 MVC 框架》(地址:http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/?ca=drs-tp4608)。

Java 開(kāi)發(fā)者對(duì) MVC 框架一定不陌生,從 Struts 到 WebWork,Java MVC 框架層出不窮。我們已經(jīng)習(xí)慣了處理 *.do 或 *.action 風(fēng)格的 URL,為每一個(gè) URL 編寫(xiě)一個(gè)控制器,并繼承一個(gè) Action 或者 Controller 接口。然而,流行的 Web 趨勢(shì)是使用更加簡(jiǎn)單,對(duì)用戶(hù)和搜索引擎更加友好的 REST 風(fēng)格的 URL。例如,來(lái)自豆瓣的一本書(shū)的鏈接是 http://www.douban.com/subject/2129650/,而非 http://www.douban.com/subject.do?id=2129650。

有經(jīng)驗(yàn)的 Java Web 開(kāi)發(fā)人員會(huì)使用 URL 重寫(xiě)的方式來(lái)實(shí)現(xiàn)類(lèi)似的 URL,例如,為前端 Apache 服務(wù)器配置 mod_rewrite 模塊,并依次為每個(gè)需要實(shí)現(xiàn) URL 重寫(xiě)的地址編寫(xiě)負(fù)責(zé)轉(zhuǎn)換的正則表達(dá)式,或者,通過(guò)一個(gè)自定義的 RewriteFilter,使用 Java Web 服務(wù)器提供的 Filter 和請(qǐng)求轉(zhuǎn)發(fā)(Forward)功能實(shí)現(xiàn) URL 重寫(xiě),不過(guò),仍需要為每個(gè)地址編寫(xiě)正則表達(dá)式。

既然 URL 重寫(xiě)如此繁瑣,為何不直接設(shè)計(jì)一個(gè)原生支持 REST 風(fēng)格的 MVC 框架呢?

要設(shè)計(jì)并實(shí)現(xiàn)這樣一個(gè) MVC 框架并不困難,下面,我們從零開(kāi)始,仔細(xì)研究如何實(shí)現(xiàn) REST 風(fēng)格的 URL 映射,并與常見(jiàn)的 IoC 容器如 Spring 框架集成。這個(gè)全新的 MVC 框架暫命名為 WebWind。

術(shù)語(yǔ)

MVC:Model-View-Controller,是一種常見(jiàn)的 UI 架構(gòu)模式,通過(guò)分離 Model(模型)、View(視圖)和 Controller(控制器),可以更容易實(shí)現(xiàn)易于擴(kuò)展的 UI。在 Web 應(yīng)用程序中,Model 指后臺(tái)返回的數(shù)據(jù);View 指需要渲染的頁(yè)面,通常是 JSP 或者其他模板頁(yè)面,渲染后的結(jié)果通常是 HTML;Controller 指 Web 開(kāi)發(fā)人員編寫(xiě)的處理不同 URL 的控制器(在 Struts 中被稱(chēng)之為 Action),而 MVC 框架本身還有一個(gè)前置控制器,用于接收所有的 URL 請(qǐng)求,并根據(jù) URL 地址分發(fā)到 Web 開(kāi)發(fā)人員編寫(xiě)的 Controller 中。

IoC:Invertion-of-Control,控制反轉(zhuǎn),是目前流行的管理所有組件生命周期和復(fù)雜依賴(lài)關(guān)系的容器,例如 Spring 容器。

Template:模板,通過(guò)渲染,模板中的變量將被 Model 的實(shí)際數(shù)據(jù)所替換,然后,生成的內(nèi)容即是用戶(hù)在瀏覽器中看到的 HTML。模板也能實(shí)現(xiàn)判斷、循環(huán)等簡(jiǎn)單邏輯。本質(zhì)上,JSP 頁(yè)面也是一種模板。此外,還有許多第三方模板引擎,如 Velocity,F(xiàn)reeMarker 等。

設(shè)計(jì)目標(biāo)

和傳統(tǒng)的 Struts 等 MVC 框架完全不同,為了支持 REST 風(fēng)格的 URL,我們并不把一個(gè) URL 映射到一個(gè) Controller 類(lèi)(或者 Struts 的 Action),而是直接把一個(gè) URL 映射到一個(gè)方法,這樣,Web 開(kāi)發(fā)人員就可以將多個(gè)功能類(lèi)似的方法放到一個(gè) Controller 中,并且,Controller 沒(méi)有強(qiáng)制要求必須實(shí)現(xiàn)某個(gè)接口。一個(gè) Controller 通常擁有多個(gè)方法,每個(gè)方法負(fù)責(zé)處理一個(gè) URL。例如,一個(gè)管理 Blog 的 Controller 定義起來(lái)就像清單 1 所示。

清單 1. 管理 Blog 的 Controller 定義

				
public class Blog { 
    @Mapping("/create/$1") 
    Public void create(int userId) { ... } 

    @Mapping("/display/$1/$2") 
    Public void display(int userId, int postId) { ... } 

    @Mapping("/edit/$1/$2") 
    Public void edit(int userId, int postId) { ... } 

    @Mapping("/delete/$1/$2") 
    Public String delete(int userId, int postId) { ... } 
} 

@Mapping() 注解指示了這是一個(gè)處理 URL 映射的方法,URL 中的參數(shù) $1、$2 ……則將作為方法參數(shù)傳入。對(duì)于一個(gè)“/blog/1234/5678”的 URL,對(duì)應(yīng)的方法將自動(dòng)獲得參數(shù) userId=1234 和 postId=5678。同時(shí),也無(wú)需任何與 URL 映射相關(guān)的 XML 配置文件。

使用 $1、$2 ……來(lái)定義 URL 中的可變參數(shù)要比正則表達(dá)式更簡(jiǎn)單,我們需要在 MVC 框架內(nèi)部將其轉(zhuǎn)化為正則表達(dá)式,以便匹配 URL。

此外,對(duì)于方法返回值,也未作強(qiáng)制要求。

集成 IoC

當(dāng)接收到來(lái)自瀏覽器的請(qǐng)求,并匹配到合適的 URL 時(shí),應(yīng)該轉(zhuǎn)發(fā)給某個(gè) Controller 實(shí)例的某個(gè)標(biāo)記有 @Mapping 的方法,這需要持有所有 Controller 的實(shí)例。不過(guò),讓一個(gè) MVC 框架去管理這些組件并不是一個(gè)好的設(shè)計(jì),這些組件可以很容易地被 IoC 容器管理,MVC 框架需要做的僅僅是向 IoC 容器請(qǐng)求并獲取這些組件的實(shí)例。

為了解耦一種特定的 IoC 容器,我們通過(guò) ContainerFactory 來(lái)獲取所有 Controller 組件的實(shí)例,如清單 2 所示。

清單 2. 定義 ContainerFactory

				
public interface ContainerFactory { 

    void init(Config config); 

    List<Object> findAllBeans(); 

    void destroy(); 
} 

其中,關(guān)鍵方法 findAllBeans() 返回 IoC 容器管理的所有 Bean,然后,掃描每一個(gè) Bean 的所有 public 方法,并引用那些標(biāo)記有 @Mapping 的方法實(shí)例。

我們?cè)O(shè)計(jì)目標(biāo)是支持 Spring 和 Guice 這兩種容器,對(duì)于 Spring 容器,可以通過(guò) ApplicationContext 獲得所有的 Bean 引用,代碼見(jiàn)清單 3。

清單 3. 定義 SpringContainerFactory

				
public class SpringContainerFactory implements ContainerFactory { 
    private ApplicationContext appContext; 

    public List<Object> findAllBeans() { 
        String[] beanNames = appContext.getBeanDefinitionNames(); 
        List<Object> beans = new ArrayList<Object>(beanNames.length); 
        for (int i=0; i<beanNames.length; i++) { 
            beans.add(appContext.getBean(beanNames[i])); 
        } 
        return beans; 
    } 
    ... 
} 

對(duì)于 Guice 容器,通過(guò) Injector 實(shí)例可以返回所有綁定對(duì)象的實(shí)例,代碼見(jiàn)清單 4。

清單 4. 定義 GuiceContainerFactory

				
public class GuiceContainerFactory implements ContainerFactory { 
    private Injector injector; 

    public List<Object> findAllBeans() { 
        Map<Key<?>, Binding<?>> map = injector.getBindings(); 
        Set<Key<?>> keys = map.keySet(); 
        List<Object> list = new ArrayList<Object>(keys.size()); 
        for (Key<?> key : keys) { 
            Object bean = injector.getInstance(key); 
            list.add(bean); 
        } 
        return list; 
    } 
    ... 
} 

類(lèi)似的,通過(guò)擴(kuò)展 ContainerFactory,就可以支持更多的 IoC 容器,如 PicoContainer。

出于效率的考慮,我們緩存所有來(lái)自 IoC 的 Controller 實(shí)例,無(wú)論其在 IoC 中配置為 Singleton 還是 Prototype 類(lèi)型。當(dāng)然,也可以修改代碼,每次都從 IoC 容器中重新請(qǐng)求實(shí)例。

設(shè)計(jì)請(qǐng)求轉(zhuǎn)發(fā)

和 Struts 等常見(jiàn) MVC 框架一樣,我們也需要實(shí)現(xiàn)一個(gè)前置控制器,通常命名為 DispatcherServlet,用于接收所有的請(qǐng)求,并作出合適的轉(zhuǎn)發(fā)。在 Servlet 規(guī)范中,有以下幾種常見(jiàn)的 URL 匹配模式:

  • /abc:精確匹配,通常用于映射自定義的 Servlet;
  • *.do:后綴模式匹配,常見(jiàn)的 MVC 框架都采用這種模式;
  • /app/*:前綴模式匹配,這要求 URL 必須以固定前綴開(kāi)頭;
  • /:匹配默認(rèn)的 Servlet,當(dāng)一個(gè) URL 沒(méi)有匹配到任何 Servlet 時(shí),就匹配默認(rèn)的 Servlet。一個(gè) Web 應(yīng)用程序如果沒(méi)有映射默認(rèn)的 Servlet,Web 服務(wù)器會(huì)自動(dòng)為 Web 應(yīng)用程序添加一個(gè)默認(rèn)的 Servlet。

REST 風(fēng)格的 URL 一般不含后綴,我們只能將 DispatcherServlet 映射到“/”,使之變?yōu)橐粋€(gè)默認(rèn)的 Servlet,這樣,就可以對(duì)任意的 URL 進(jìn)行處理。

由于無(wú)法像 Struts 等傳統(tǒng)的 MVC 框架根據(jù)后綴直接將一個(gè) URL 映射到一個(gè) Controller,我們必須依次匹配每個(gè)有能力處理 HTTP 請(qǐng)求的 @Mapping 方法。完整的 HTTP 請(qǐng)求處理流程如圖 1 所示。

圖 1. 請(qǐng)求處理流程

圖 1. 請(qǐng)求處理流程

當(dāng)掃描到標(biāo)記有 @Mapping 注解的方法時(shí),需要首先檢查 URL 與方法參數(shù)是否匹配,UrlMatcher 用于將 @Mapping 中包含 $1、$2 ……的字符串變?yōu)檎齽t表達(dá)式,進(jìn)行預(yù)編譯,并檢查參數(shù)個(gè)數(shù)是否符合方法參數(shù),代碼見(jiàn)清單 5。

清單 5. 定義 UrlMatcher

				
final class UrlMatcher { 
    final String url; 
    int[] orders; 
    Pattern pattern; 

    public UrlMatcher(String url) { 
        ... 
    } 
} 

將 @Mapping 中包含 $1、$2 ……的字符串變?yōu)檎齽t表達(dá)式的轉(zhuǎn)換規(guī)則是,依次將每個(gè) $n 替換為 ([^\\/]*),其余部分作精確匹配。例如,“/blog/$1/$2”變化后的正則表達(dá)式為:

 ^\\/blog\\/([^\\/]*)\\/([^\\/]*)$ 

請(qǐng)注意,Java 字符串需要兩個(gè)連續(xù)的“\\”表示正則表達(dá)式中的轉(zhuǎn)義字符“\”。將“/”排除在變量匹配之外可以避免很多歧義。

調(diào)用一個(gè)實(shí)例方法則由 Action 類(lèi)表示,它持有類(lèi)實(shí)例、方法引用和方法參數(shù)類(lèi)型,代碼見(jiàn)清單 6。

清單 6. 定義 Action

				
class Action { 
    public final Object instance; 
    public final Method method; 
    public final Class<?>[] arguments; 

    public Action(Object instance, Method method) { 
        this.instance = instance; 
        this.method = method; 
        this.arguments = method.getParameterTypes(); 
    } 
} 

負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)的 Dispatcher 通過(guò)關(guān)聯(lián) UrlMatcher 與 Action,就可以匹配到合適的 URL,并轉(zhuǎn)發(fā)給相應(yīng)的 Action,代碼見(jiàn)清單 7。

清單 7. 定義 Dispatcher

				
class Dispatcher  { 
    private UrlMatcher[] urlMatchers; 
    private Map<UrlMatcher, Action> urlMap = new HashMap<UrlMatcher, Action>(); 
    .... 
} 

當(dāng) Dispatcher 接收到一個(gè) URL 請(qǐng)求時(shí),遍歷所有的 UrlMatcher,找到***個(gè)匹配 URL 的 UrlMatcher,并從 URL 中提取方法參數(shù),代碼見(jiàn)清單 8。

清單 8. 匹配并從 URL 中提取參數(shù)

				
final class UrlMatcher { 
    ... 

    /** 
     * 根據(jù)正則表達(dá)式匹配 URL,若匹配成功,返回從 URL 中提取的參數(shù),
     * 若匹配失敗,返回 null 
     */ 
    public String[] getMatchedParameters(String url) { 
        Matcher m = pattern.matcher(url); 
        if (!m.matches()) 
            return null; 
        if (orders.length==0) 
            return EMPTY_STRINGS; 
        String[] params = new String[orders.length]; 
        for (int i=0; i<orders.length; i++) { 
            params[orders[i]] = m.group(i+1); 
        } 
        return params; 
    } 
} 

根據(jù) URL 找到匹配的 Action 后,就可以構(gòu)造一個(gè) Execution 對(duì)象,并根據(jù)方法簽名將 URL 中的 String 轉(zhuǎn)換為合適的方法參數(shù)類(lèi)型,準(zhǔn)備好全部參數(shù),代碼見(jiàn)清單 9。

清單 9. 構(gòu)造 Exectuion

				
class Execution { 
    public final HttpServletRequest request; 
    public final HttpServletResponse response; 
    private final Action action; 
    private final Object[] args; 
    ... 

    public Object execute() throws Exception { 
        try { 
            return action.method.invoke(action.instance, args); 
        } 
        catch (InvocationTargetException e) { 
            Throwable t = e.getCause(); 
            if (t!=null && t instanceof Exception) 
                throw (Exception) t; 
            throw e; 
        } 
    } 
} 

調(diào)用 execute() 方法就可以執(zhí)行目標(biāo)方法,并返回一個(gè)結(jié)果。請(qǐng)注意,當(dāng)通過(guò)反射調(diào)用方法失敗時(shí),我們通過(guò)查找 InvocationTargetException 的根異常并將其拋出,這樣,客戶(hù)端就能捕獲正確的原始異常。

為了***限度地增加靈活性,我們并不強(qiáng)制要求 URL 的處理方法返回某一種類(lèi)型。我們?cè)O(shè)計(jì)支持以下返回值:

  • String:當(dāng)返回一個(gè) String 時(shí),自動(dòng)將其作為 HTML 寫(xiě)入 HttpServletResponse;
  • void:當(dāng)返回 void 時(shí),不做任何操作;
  • Renderer:當(dāng)返回 Renderer 對(duì)象時(shí),將調(diào)用 Renderer 對(duì)象的 render 方法渲染 HTML 頁(yè)面。

***需要考慮的是,由于我們將 DispatcherServlet 映射為“/”,即默認(rèn)的 Servlet,則所有的未匹配成功的 URL 都將由 DispatcherServlet 處理,包括所有靜態(tài)文件,因此,當(dāng)未匹配到任何 Controller 的 @Mapping 方法后,DispatcherServlet 將試圖按 URL 查找對(duì)應(yīng)的靜態(tài)文件,我們用 StaticFileHandler 封裝,主要代碼見(jiàn)清單 10。

清單 10. 處理靜態(tài)文件

				
class StaticFileHandler { 
    ... 
    public void handle(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException { 
        String url = request.getRequestURI(); 
        String path = request.getServletPath(); 
        url = url.substring(path.length()); 
        if (url.toUpperCase().startsWith("/WEB-INF/")) { 
            response.sendError(HttpServletResponse.SC_NOT_FOUND); 
            return; 
        } 
        int n = url.indexOf('?'); 
        if (n!=(-1)) 
            url = url.substring(0, n); 
        n = url.indexOf('#'); 
        if (n!=(-1)) 
            url = url.substring(0, n); 
        File f = new File(servletContext.getRealPath(url)); 
        if (! f.isFile()) { 
            response.sendError(HttpServletResponse.SC_NOT_FOUND); 
            return; 
        } 
        long ifModifiedSince = request.getDateHeader("If-Modified-Since"); 
        long lastModified = f.lastModified(); 
        if (ifModifiedSince!=(-1) && ifModifiedSince>=lastModified) { 
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 
            return; 
        } 
        response.setDateHeader("Last-Modified", lastModified); 
        response.setContentLength((int)f.length()); 
        response.setContentType(getMimeType(f)); 
        sendFile(f, response.getOutputStream()); 
    } 
} 

處理靜態(tài)文件時(shí)要過(guò)濾 /WEB-INF/ 目錄,否則將造成安全漏洞。

集成模板引擎

作為示例,返回一個(gè)“<h1>Hello, world!</h1>”作為 HTML 頁(yè)面非常容易。然而,實(shí)際應(yīng)用的頁(yè)面通常是極其復(fù)雜的,需要一個(gè)模板引擎來(lái)渲染出 HTML。可以把 JSP 看作是一種模板,只要不在 JSP 頁(yè)面中編寫(xiě)復(fù)雜的 Java 代碼。我們的設(shè)計(jì)目標(biāo)是實(shí)現(xiàn)對(duì) JSP 和 Velocity 這兩種模板的支持。

和集成 IoC 框架類(lèi)似,我們需要解耦 MVC 與模板系統(tǒng),因此,TemplateFactory 用于初始化模板引擎,并返回 Template 模板對(duì)象。TemplateFactory 定義見(jiàn)清單 11。

清單 11. 定義 TemplateFactory

				
public abstract class TemplateFactory { 
    private static TemplateFactory instance; 
    public static TemplateFactory getTemplateFactory() { 
        return instance; 
    } 

    public abstract Template loadTemplate(String path) throws Exception; 
} 

Template 接口則實(shí)現(xiàn)真正的渲染任務(wù)。定義見(jiàn)清單 12。

清單 12. 定義 Template

				
public interface Template { 
    void render(HttpServletRequest request, HttpServletResponse response, 
        Map<String, Object> model) throws Exception; 
} 

以 JSP 為例,實(shí)現(xiàn) JspTemplateFactory 非常容易。代碼見(jiàn)清單 13。

清單 13. 定義 JspTemplateFactory

				
public class JspTemplateFactory extends TemplateFactory { 
    private Log log = LogFactory.getLog(getClass()); 

    public Template loadTemplate(String path) throws Exception { 
        if (log.isDebugEnabled()) 
            log.debug("Load JSP template '" + path + "'."); 
        return new JspTemplate(path); 
    } 

    public void init(Config config) { 
        log.info("JspTemplateFactory init ok."); 
    } 
} 

JspTemplate 用于渲染頁(yè)面,只需要傳入 JSP 的路徑,將 Model 綁定到 HttpServletRequest,就可以調(diào)用 Servlet 規(guī)范的 forward 方法將請(qǐng)求轉(zhuǎn)發(fā)給指定的 JSP 頁(yè)面并渲染。代碼見(jiàn)清單 14。

清單 14. 定義 JspTemplate

				
public class JspTemplate implements Template { 
    private String path; 

    public JspTemplate(String path) { 
        this.path = path; 
    } 

    public void render(HttpServletRequest request, HttpServletResponse response, 
            Map<String, Object> model) throws Exception { 
        Set<String> keys = model.keySet(); 
        for (String key : keys) { 
            request.setAttribute(key, model.get(key)); 
        } 
        request.getRequestDispatcher(path).forward(request, response); 
    } 
} 

另一種比 JSP 更加簡(jiǎn)單且靈活的模板引擎是 Velocity,它使用更簡(jiǎn)潔的語(yǔ)法來(lái)渲染頁(yè)面,對(duì)頁(yè)面設(shè)計(jì)人員更加友好,并且完全阻止了開(kāi)發(fā)人員試圖在頁(yè)面中編寫(xiě) Java 代碼的可能性。使用 Velocity 編寫(xiě)的頁(yè)面示例如清單 15 所示。

清單 15. Velocity 模板頁(yè)面

				
<html> 
    <head><title>${title}</title></head> 
    <body><h1>Hello, ${name}!</body> 
</html> 

通過(guò) VelocityTemplateFactory 和 VelocityTemplate 就可以實(shí)現(xiàn)對(duì) Velocity 的集成。不過(guò),從 Web 開(kāi)發(fā)人員看來(lái),并不需要知道具體使用的模板,客戶(hù)端僅需要提供模板路徑和一個(gè)由 Map<String, Object> 組成的 Model,然后返回一個(gè) TemplateRenderer 對(duì)象。代碼如清單 16 所示。

清單 16. 定義 TemplateRenderer

				
public class TemplateRenderer extends Renderer { 
    private String path; 
    private Map<String, Object> model; 

    public TemplateRenderer(String path, Map<String, Object> model) { 
        this.path = path; 
        this.model = model; 
    } 

    @Override 
    public void render(ServletContext context, HttpServletRequest request, 
            HttpServletResponse response) throws Exception { 
        TemplateFactory.getTemplateFactory() 
                .loadTemplate(path) 
                .render(request, response, model); 
    } 
} 

TemplateRenderer 通過(guò)簡(jiǎn)單地調(diào)用 render 方法就實(shí)現(xiàn)了頁(yè)面渲染。為了指定 Jsp 或 Velocity,需要在 web.xml 中配置 DispatcherServlet 的初始參數(shù)。配置示例請(qǐng)參考清單 17。

清單 17. 配置 Velocity 作為模板引擎

				
<servlet> 
    <servlet-name>dispatcher</servlet-name> 
    <servlet-class>org.expressme.webwind.DispatcherServlet</servlet-class> 
    <init-param> 
        <param-name>template</param-name> 
        <param-value>Velocity</param-value> 
    </init-param> 
</servlet> 

如果沒(méi)有該缺省參數(shù),那就使用默認(rèn)的 Jsp。

類(lèi)似的,通過(guò)擴(kuò)展 TemplateFactory 和 Template,就可以添加更多的模板支持,例如 FreeMarker。

設(shè)計(jì)攔截器

攔截器和 Servlet 規(guī)范中的 Filter 非常類(lèi)似,不過(guò) Filter 的作用范圍是整個(gè) HttpServletRequest 的處理過(guò)程,而攔截器僅作用于 Controller,不涉及到 View 的渲染,在大多數(shù)情況下,使用攔截器比 Filter 速度要快,尤其是綁定數(shù)據(jù)庫(kù)事務(wù)時(shí),攔截器能縮短數(shù)據(jù)庫(kù)事務(wù)開(kāi)啟的時(shí)間。

攔截器接口 Interceptor 定義如清單 18 所示。

清單 18. 定義 Interceptor

				
public interface Interceptor { 
    void intercept(Execution execution, InterceptorChain chain) throws Exception; 
} 

和 Filter 類(lèi)似,InterceptorChain 代表攔截器鏈。InterceptorChain 定義如清單 19 所示。

清單 19. 定義 InterceptorChain

				
public interface InterceptorChain { 
    void doInterceptor(Execution execution) throws Exception; 
} 

實(shí)現(xiàn) InterceptorChain 要比實(shí)現(xiàn) FilterChain 簡(jiǎn)單,因?yàn)?Filter 需要處理 Request、Forward、Include 和 Error 這 4 種請(qǐng)求轉(zhuǎn)發(fā)的情況,而 Interceptor 僅攔截 Request。當(dāng) MVC 框架處理一個(gè)請(qǐng)求時(shí),先初始化一個(gè)攔截器鏈,然后,依次調(diào)用鏈上的每個(gè)攔截器。請(qǐng)參考清單 20 所示的代碼。

清單 20. 實(shí)現(xiàn) InterceptorChain 接口

				
class InterceptorChainImpl implements InterceptorChain { 
    private final Interceptor[] interceptors; 
    private int index = 0; 
    private Object result = null; 

    InterceptorChainImpl(Interceptor[] interceptors) { 
        this.interceptors = interceptors; 
    } 

    Object getResult() { 
        return result; 
    } 

    public void doInterceptor(Execution execution) throws Exception { 
        if(index==interceptors.length) 
            result = execution.execute(); 
        else { 
            // must update index first, otherwise will cause stack overflow: 
            index++; 
            interceptors[index-1].intercept(execution, this); 
        } 
    } 
} 

成員變量 index 表示當(dāng)前鏈上的第 N 個(gè)攔截器,當(dāng)***一個(gè)攔截器被調(diào)用后,InterceptorChain 才真正調(diào)用 Execution 對(duì)象的 execute() 方法,并保存其返回結(jié)果,整個(gè)請(qǐng)求處理過(guò)程結(jié)束,進(jìn)入渲染階段。清單 21 演示了如何調(diào)用攔截器鏈的代碼。

清單 21. 調(diào)用攔截器鏈

				
class Dispatcher  { 
    ... 
    private Interceptor[] interceptors; 
    void handleExecution(Execution execution, HttpServletRequest request, 
        HttpServletResponse response) throws ServletException, IOException { 
        InterceptorChainImpl chains = new InterceptorChainImpl(interceptors); 
        chains.doInterceptor(execution); 
        handleResult(request, response, chains.getResult()); 
    } 
} 

當(dāng) Controller 方法被調(diào)用完畢后,handleResult() 方法用于處理執(zhí)行結(jié)果。

渲染

由于我們沒(méi)有強(qiáng)制 HTTP 處理方法的返回類(lèi)型,因此,handleResult() 方法針對(duì)不同的返回值將做不同的處理。代碼如清單 22 所示。

清單 22. 處理返回值

				
class Dispatcher  { 
    ... 
    void handleResult(HttpServletRequest request, HttpServletResponse response, 
            Object result) throws Exception { 
        if (result==null) 
            return; 
        if (result instanceof Renderer) { 
            Renderer r = (Renderer) result; 
            r.render(this.servletContext, request, response); 
            return; 
        } 
        if (result instanceof String) { 
            String s = (String) result; 
            if (s.startsWith("redirect:")) { 
                response.sendRedirect(s.substring(9)); 
                return; 
            } 
            new TextRenderer(s).render(servletContext, request, response); 
            return; 
        } 
        throw new ServletException("Cannot handle result with type '"
                + result.getClass().getName() + "'."); 
    } 
} 

如果返回 null,則認(rèn)為 HTTP 請(qǐng)求已處理完成,不做任何處理;如果返回 Renderer,則調(diào)用 Renderer 對(duì)象的 render() 方法渲染視圖;如果返回 String,則根據(jù)前綴是否有“redirect:”判斷是重定向還是作為 HTML 返回給瀏覽器。這樣,客戶(hù)端可以不必訪問(wèn) HttpServletResponse 對(duì)象就可以非常方便地實(shí)現(xiàn)重定向。代碼如清單 23 所示。

清單 23. 重定向

				
@Mapping("/register") 
String register() { 
    ... 
    if (success) 
        return "redirect:/reg/success"; 
    return "redirect:/reg/failed"; 
} 

擴(kuò)展 Renderer 還可以處理更多的格式,例如,向?yàn)g覽器返回 JavaScript 代碼等。

擴(kuò)展

使用 Filter 轉(zhuǎn)發(fā)

對(duì)于請(qǐng)求轉(zhuǎn)發(fā),除了使用 DispatcherServlet 外,還可以使用 Filter 來(lái)攔截所有請(qǐng)求,并直接在 Filter 內(nèi)實(shí)現(xiàn)請(qǐng)求轉(zhuǎn)發(fā)和處理。使用 Filter 的一個(gè)好處是如果 URL 沒(méi)有被任何 Controller 的映射方法匹配到,則可以簡(jiǎn)單地調(diào)用 FilterChain.doFilter() 將 HTTP 請(qǐng)求傳遞給下一個(gè) Filter,這樣,我們就不必自己處理靜態(tài)文件,而由 Web 服務(wù)器提供的默認(rèn) Servlet 處理,效率更高。和 DispatcherServlet 類(lèi)似,我們編寫(xiě)一個(gè) DispatcherFilter 作為前置處理器,負(fù)責(zé)轉(zhuǎn)發(fā)請(qǐng)求,代碼見(jiàn)清單 24。

清單 24. 定義 DispatcherFilter

				
public class DispatcherFilter implements Filter { 
    ... 
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
    throws IOException, ServletException { 
        HttpServletRequest httpReq = (HttpServletRequest) req; 
        HttpServletResponse httpResp = (HttpServletResponse) resp; 
        String method = httpReq.getMethod(); 
        if ("GET".equals(method) || "POST".equals(method)) { 
            if (!dispatcher.service(httpReq, httpResp)) 
                chain.doFilter(req, resp); 
            return; 
        } 
        httpResp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); 
    } 
} 

如果用 DispatcherFilter 代替 DispatcherServlet,則我們需要過(guò)濾“/*”,在 web.xml 中添加聲明如清單 25 所示。

清單 25. 聲明 DispatcherFilter

				
<filter> 
    <filter-name>dispatcher</servlet-name> 
    <filter-class>org.expressme.webwind.DispatcherFilter</servlet-class> 
</filter> 
<filter-mapping> 
    <filter-name>dispatcher</servlet-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

訪問(wèn) Request 和 Response 對(duì)象

如何在 @Mapping 方法中訪問(wèn) Servlet 對(duì)象?如 HttpServletRequest,HttpServletResponse,HttpSession 和 ServletContext。ThreadLocal 是一個(gè)最簡(jiǎn)單有效的解決方案。我們編寫(xiě)一個(gè) ActionContext,通過(guò) ThreadLocal 來(lái)封裝對(duì) Request 等對(duì)象的訪問(wèn)。代碼見(jiàn)清單 26。

清單 26. 定義 ActionContext

				
public final class ActionContext { 
    private static final ThreadLocal<ActionContext> actionContextThreadLocal 
            = new ThreadLocal<ActionContext>(); 

    private ServletContext context; 
    private HttpServletRequest request; 
    private HttpServletResponse response; 

    public ServletContext getServletContext() { 
        return context; 
    } 

    public HttpServletRequest getHttpServletRequest() { 
        return request; 
    } 

    public HttpServletResponse getHttpServletResponse() { 
        return response; 
    } 

    public HttpSession getHttpSession() { 
        return request.getSession(); 
    } 

    public static ActionContext getActionContext() { 
        return actionContextThreadLocal.get(); 
    } 

    static void setActionContext(ServletContext context, 
            HttpServletRequest request, HttpServletResponse response) { 
        ActionContext ctx = new ActionContext(); 
        ctx.context = context; 
        ctx.request = request; 
        ctx.response = response; 
        actionContextThreadLocal.set(ctx); 
    } 

    static void removeActionContext() { 
        actionContextThreadLocal.remove(); 
    } 
} 

在 Dispatcher 的 handleExecution() 方法中,初始化 ActionContext,并在 finally 中移除所有已綁定變量,代碼見(jiàn)清單 27。

清單 27. 初始化 ActionContext

				
class Dispatcher { 
    ... 
    void handleExecution(Execution execution, HttpServletRequest request, 
    HttpServletResponse response) throws ServletException, IOException { 
        ActionContext.setActionContext(servletContext, request, response); 
        try { 
            InterceptorChainImpl chains = new InterceptorChainImpl(interceptors); 
            chains.doInterceptor(execution); 
            handleResult(request, response, chains.getResult()); 
        } 
        catch (Exception e) { 
            handleException(request, response, e); 
        } 
        finally { 
            ActionContext.removeActionContext(); 
        } 
    } 
} 

這樣,在 @Mapping 方法內(nèi)部,可以隨時(shí)獲得需要的 Request、Response、 Session 和 ServletContext 對(duì)象。

處理文件上傳

Servlet API 本身并沒(méi)有提供對(duì)文件上傳的支持,要處理文件上傳,我們需要使用 Commons FileUpload 之類(lèi)的第三方擴(kuò)展包??紤]到 Commons FileUpload 是使用最廣泛的文件上傳包,我們希望能集成 Commons FileUpload,但是,不要暴露 Commons FileUpload 的任何 API 給 MVC 的客戶(hù)端,客戶(hù)端應(yīng)該可以直接從一個(gè)普通的 HttpServletRequest 對(duì)象中獲取上傳文件。

要讓 MVC 客戶(hù)端直接使用 HttpServletRequest,我們可以用自定義的 MultipartHttpServletRequest 替換原始的 HttpServletRequest,這樣,客戶(hù)端代碼可以通過(guò) instanceof 判斷是否是一個(gè) Multipart 格式的 Request,如果是,就強(qiáng)制轉(zhuǎn)型為 MultipartHttpServletRequest,然后,獲取上傳的文件流。

核心思想是從 HttpServletRequestWrapper 派生 MultipartHttpServletRequest,這樣,MultipartHttpServletRequest 具有 HttpServletRequest 接口。MultipartHttpServletRequest 的定義如清單 28 所示。

清單 28. 定義 MultipartHttpServletRequest

				
public class MultipartHttpServletRequest extends HttpServletRequestWrapper { 
    final HttpServletRequest target; 
    final Map<String, List<FileItemStream>> fileItems; 
    final Map<String, List<String>> formItems; 

    public MultipartHttpServletRequest(HttpServletRequest request, long maxFileSize) 
    throws IOException { 
        super(request); 
        this.target = request; 
        this.fileItems = new HashMap<String, List<FileItemStream>>(); 
        this.formItems = new HashMap<String, List<String>>(); 
        ServletFileUpload upload = new ServletFileUpload(); 
        upload.setFileSizeMax(maxFileSize); 
        try { 

...解析Multipart ...

        } 
        catch (FileUploadException e) { 
            throw new IOException(e); 
        } 
    } 

    public InputStream getFileInputStream(String fieldName) throws IOException { 
        List<FileItemStream> list = fileItems.get(fieldName); 
        if (list==null) 
            throw new IOException("No file item with name '" + fieldName + "'."); 
        return list.get(0).openStream(); 
    }; 
} 

對(duì)于正常的 Field 參數(shù),保存在成員變量 Map<String, List<String>> formItems 中,通過(guò)覆寫(xiě) getParameter()、getParameters() 等方法,就可以讓客戶(hù)端把 MultipartHttpServletRequest 也當(dāng)作一個(gè)普通的 Request 來(lái)操作,代碼見(jiàn)清單 29。

清單 29. 覆寫(xiě) getParameter

				
public class MultipartHttpServletRequest extends HttpServletRequestWrapper { 
    ... 
    @Override 
    public String getParameter(String name) { 
        List<String> list = formItems.get(name); 
        if (list==null) 
            return null; 
        return list.get(0); 
    } 

    @Override 
    @SuppressWarnings("unchecked") 
    public Map getParameterMap() { 
        Map<String, String[]> map = new HashMap<String, String[]>(); 
        Set<String> keys = formItems.keySet(); 
        for (String key : keys) { 
            List<String> list = formItems.get(key); 
            map.put(key, list.toArray(new String[list.size()])); 
        } 
        return Collections.unmodifiableMap(map); 
    } 

    @Override 
    @SuppressWarnings("unchecked") 
    public Enumeration getParameterNames() { 
        return Collections.enumeration(formItems.keySet()); 
    } 

    @Override 
    public String[] getParameterValues(String name) { 
        List<String> list = formItems.get(name); 
        if (list==null) 
            return null; 
        return list.toArray(new String[list.size()]); 
    } 
} 

為了簡(jiǎn)化配置,在 Web 應(yīng)用程序啟動(dòng)的時(shí)候,自動(dòng)檢測(cè)當(dāng)前 ClassPath 下是否有 Commons FileUpload,如果存在,文件上傳功能就自動(dòng)開(kāi)啟,如果不存在,文件上傳功能就不可用,這樣,客戶(hù)端只需要簡(jiǎn)單地把 Commons FileUpload 的 jar 包放入 /WEB-INF/lib/,不需任何配置就可以直接使用。核心代碼見(jiàn)清單 30。

清單 30. 檢測(cè) Commons FileUpload

				
class Dispatcher { 
    private boolean multipartSupport = false; 
    ... 
    void initAll(Config config) throws Exception { 
        try { 
            Class.forName("org.apache.commons.fileupload.servlet.ServletFileUpload"); 
            this.multipartSupport = true; 
        } 
        catch (ClassNotFoundException e) { 
            log.info("CommonsFileUpload not found."); 
        } 
        ... 
    } 

    void handleExecution(Execution execution, HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException { 
        if (this.multipartSupport) { 
            if (MultipartHttpServletRequest.isMultipartRequest(request)) { 
                request = new MultipartHttpServletRequest(request, maxFileSize); 
            } 
        } 
        ... 
    } 
    ... 
} 

小結(jié)

#t#要從頭設(shè)計(jì)并實(shí)現(xiàn)一個(gè) MVC 框架其實(shí)并不困難,設(shè)計(jì) WebWind 的目標(biāo)是改善 Web 應(yīng)用程序的 URL 結(jié)構(gòu),并通過(guò)自動(dòng)提取和映射 URL 中的參數(shù),簡(jiǎn)化控制器的編寫(xiě)。WebWind 適合那些從頭構(gòu)造的新的互聯(lián)網(wǎng)應(yīng)用,以便天生支持 REST 風(fēng)格的 URL。但是,它不適合改造已有的企業(yè)應(yīng)用程序,企業(yè)應(yīng)用的頁(yè)面不需要搜索引擎的索引,其用戶(hù)對(duì) URL 地址的友好程度通常也并不關(guān)心。

責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2010-01-08 12:03:42

ibmdwREST

2011-04-22 09:26:57

MVC設(shè)計(jì)

2023-02-03 17:29:46

2009-05-08 08:59:35

RESTORBSOA

2017-08-28 15:00:20

軟件系統(tǒng)架構(gòu)風(fēng)格

2011-04-15 10:26:38

JavaMVC

2011-11-23 14:03:54

JavaSSHMVC

2011-02-24 15:11:00

MVC框架

2012-10-22 16:55:48

JavaMVC

2023-10-23 10:43:24

SpringRestful風(fēng)格

2023-01-10 09:48:03

RESTAPIJersey

2009-06-12 19:18:08

REST客戶(hù)端框架JavaScript

2017-11-23 17:21:31

Yii框架IntelYii框架深度剖析

2009-02-02 09:04:52

MVC框架Java

2009-07-22 10:34:37

ActionInvokASP.NET MVC

2009-06-19 11:43:59

Spring MVC框

2009-04-24 09:43:09

.NETASP.NET框架

2009-02-02 09:08:38

MVC框架控制器CakePHP

2013-03-21 13:56:21

JavaScriptBackBone

2009-06-19 11:28:45

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)