精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

設計REST風格的Java MVC框架:WebWind

開發 后端
傳統的 Java EE MVC 框架如 Struts 等都是基于 Action 設計的后綴式映射,然而,流行的 Web 趨勢是 REST 風格的架構。本文將講述如何從頭設計一個基于 REST 風格的 Java MVC 框架,配合 Annotation,最大限度地簡化 Web 應用的開發。

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

Java 開發者對 MVC 框架一定不陌生,從 Struts 到 WebWork,Java MVC 框架層出不窮。我們已經習慣了處理 *.do 或 *.action 風格的 URL,為每一個 URL 編寫一個控制器,并繼承一個 Action 或者 Controller 接口。然而,流行的 Web 趨勢是使用更加簡單,對用戶和搜索引擎更加友好的 REST 風格的 URL。例如,來自豆瓣的一本書的鏈接是 http://www.douban.com/subject/2129650/,而非 http://www.douban.com/subject.do?id=2129650

有經驗的 Java Web 開發人員會使用 URL 重寫的方式來實現類似的 URL,例如,為前端 Apache 服務器配置 mod_rewrite 模塊,并依次為每個需要實現 URL 重寫的地址編寫負責轉換的正則表達式,或者,通過一個自定義的 RewriteFilter,使用 Java Web 服務器提供的 Filter 和請求轉發(Forward)功能實現 URL 重寫,不過,仍需要為每個地址編寫正則表達式。

既然 URL 重寫如此繁瑣,為何不直接設計一個原生支持 REST 風格的 MVC 框架呢?

要設計并實現這樣一個 MVC 框架并不困難,下面,我們從零開始,仔細研究如何實現 REST 風格的 URL 映射,并與常見的 IoC 容器如 Spring 框架集成。這個全新的 MVC 框架暫命名為 WebWind。

術語

MVC:Model-View-Controller,是一種常見的 UI 架構模式,通過分離 Model(模型)、View(視圖)和 Controller(控制器),可以更容易實現易于擴展的 UI。在 Web 應用程序中,Model 指后臺返回的數據;View 指需要渲染的頁面,通常是 JSP 或者其他模板頁面,渲染后的結果通常是 HTML;Controller 指 Web 開發人員編寫的處理不同 URL 的控制器(在 Struts 中被稱之為 Action),而 MVC 框架本身還有一個前置控制器,用于接收所有的 URL 請求,并根據 URL 地址分發到 Web 開發人員編寫的 Controller 中。

IoC:Invertion-of-Control,控制反轉,是目前流行的管理所有組件生命周期和復雜依賴關系的容器,例如 Spring 容器。

Template:模板,通過渲染,模板中的變量將被 Model 的實際數據所替換,然后,生成的內容即是用戶在瀏覽器中看到的 HTML。模板也能實現判斷、循環等簡單邏輯。本質上,JSP 頁面也是一種模板。此外,還有許多第三方模板引擎,如 Velocity,FreeMarker 等。

設計目標

和傳統的 Struts 等 MVC 框架完全不同,為了支持 REST 風格的 URL,我們并不把一個 URL 映射到一個 Controller 類(或者 Struts 的 Action),而是直接把一個 URL 映射到一個方法,這樣,Web 開發人員就可以將多個功能類似的方法放到一個 Controller 中,并且,Controller 沒有強制要求必須實現某個接口。一個 Controller 通常擁有多個方法,每個方法負責處理一個 URL。例如,一個管理 Blog 的 Controller 定義起來就像清單 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() 注解指示了這是一個處理 URL 映射的方法,URL 中的參數 $1、$2 ……則將作為方法參數傳入。對于一個“/blog/1234/5678”的 URL,對應的方法將自動獲得參數 userId=1234 和 postId=5678。同時,也無需任何與 URL 映射相關的 XML 配置文件。

使用 $1、$2 ……來定義 URL 中的可變參數要比正則表達式更簡單,我們需要在 MVC 框架內部將其轉化為正則表達式,以便匹配 URL。

此外,對于方法返回值,也未作強制要求。

集成 IoC

當接收到來自瀏覽器的請求,并匹配到合適的 URL 時,應該轉發給某個 Controller 實例的某個標記有 @Mapping 的方法,這需要持有所有 Controller 的實例。不過,讓一個 MVC 框架去管理這些組件并不是一個好的設計,這些組件可以很容易地被 IoC 容器管理,MVC 框架需要做的僅僅是向 IoC 容器請求并獲取這些組件的實例。

為了解耦一種特定的 IoC 容器,我們通過 ContainerFactory 來獲取所有 Controller 組件的實例,如清單 2 所示。

清單 2. 定義 ContainerFactory

				
public interface ContainerFactory { 

    void init(Config config); 

    List<Object> findAllBeans(); 

    void destroy(); 
} 

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

我們設計目標是支持 Spring 和 Guice 這兩種容器,對于 Spring 容器,可以通過 ApplicationContext 獲得所有的 Bean 引用,代碼見清單 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; 
    } 
    ... 
} 

對于 Guice 容器,通過 Injector 實例可以返回所有綁定對象的實例,代碼見清單 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; 
    } 
    ... 
} 

類似的,通過擴展 ContainerFactory,就可以支持更多的 IoC 容器,如 PicoContainer。

出于效率的考慮,我們緩存所有來自 IoC 的 Controller 實例,無論其在 IoC 中配置為 Singleton 還是 Prototype 類型。當然,也可以修改代碼,每次都從 IoC 容器中重新請求實例。

設計請求轉發

和 Struts 等常見 MVC 框架一樣,我們也需要實現一個前置控制器,通常命名為 DispatcherServlet,用于接收所有的請求,并作出合適的轉發。在 Servlet 規范中,有以下幾種常見的 URL 匹配模式:

  • /abc:精確匹配,通常用于映射自定義的 Servlet;
  • *.do:后綴模式匹配,常見的 MVC 框架都采用這種模式;
  • /app/*:前綴模式匹配,這要求 URL 必須以固定前綴開頭;
  • /:匹配默認的 Servlet,當一個 URL 沒有匹配到任何 Servlet 時,就匹配默認的 Servlet。一個 Web 應用程序如果沒有映射默認的 Servlet,Web 服務器會自動為 Web 應用程序添加一個默認的 Servlet。

REST 風格的 URL 一般不含后綴,我們只能將 DispatcherServlet 映射到“/”,使之變為一個默認的 Servlet,這樣,就可以對任意的 URL 進行處理。

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

圖 1. 請求處理流程

圖 1. 請求處理流程

當掃描到標記有 @Mapping 注解的方法時,需要首先檢查 URL 與方法參數是否匹配,UrlMatcher 用于將 @Mapping 中包含 $1、$2 ……的字符串變為正則表達式,進行預編譯,并檢查參數個數是否符合方法參數,代碼見清單 5。

清單 5. 定義 UrlMatcher

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

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

將 @Mapping 中包含 $1、$2 ……的字符串變為正則表達式的轉換規則是,依次將每個 $n 替換為 ([^\\/]*),其余部分作精確匹配。例如,“/blog/$1/$2”變化后的正則表達式為:

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

請注意,Java 字符串需要兩個連續的“\\”表示正則表達式中的轉義字符“\”。將“/”排除在變量匹配之外可以避免很多歧義。

調用一個實例方法則由 Action 類表示,它持有類實例、方法引用和方法參數類型,代碼見清單 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(); 
    } 
} 

負責請求轉發的 Dispatcher 通過關聯 UrlMatcher 與 Action,就可以匹配到合適的 URL,并轉發給相應的 Action,代碼見清單 7。

清單 7. 定義 Dispatcher

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

當 Dispatcher 接收到一個 URL 請求時,遍歷所有的 UrlMatcher,找到***個匹配 URL 的 UrlMatcher,并從 URL 中提取方法參數,代碼見清單 8。

清單 8. 匹配并從 URL 中提取參數

				
final class UrlMatcher { 
    ... 

    /** 
     * 根據正則表達式匹配 URL,若匹配成功,返回從 URL 中提取的參數,
     * 若匹配失敗,返回 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; 
    } 
} 

根據 URL 找到匹配的 Action 后,就可以構造一個 Execution 對象,并根據方法簽名將 URL 中的 String 轉換為合適的方法參數類型,準備好全部參數,代碼見清單 9。

清單 9. 構造 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; 
        } 
    } 
} 

調用 execute() 方法就可以執行目標方法,并返回一個結果。請注意,當通過反射調用方法失敗時,我們通過查找 InvocationTargetException 的根異常并將其拋出,這樣,客戶端就能捕獲正確的原始異常。

為了***限度地增加靈活性,我們并不強制要求 URL 的處理方法返回某一種類型。我們設計支持以下返回值:

  • String:當返回一個 String 時,自動將其作為 HTML 寫入 HttpServletResponse;
  • void:當返回 void 時,不做任何操作;
  • Renderer:當返回 Renderer 對象時,將調用 Renderer 對象的 render 方法渲染 HTML 頁面。

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

清單 10. 處理靜態文件

				
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()); 
    } 
} 

處理靜態文件時要過濾 /WEB-INF/ 目錄,否則將造成安全漏洞。

集成模板引擎

作為示例,返回一個“<h1>Hello, world!</h1>”作為 HTML 頁面非常容易。然而,實際應用的頁面通常是極其復雜的,需要一個模板引擎來渲染出 HTML。可以把 JSP 看作是一種模板,只要不在 JSP 頁面中編寫復雜的 Java 代碼。我們的設計目標是實現對 JSP 和 Velocity 這兩種模板的支持。

和集成 IoC 框架類似,我們需要解耦 MVC 與模板系統,因此,TemplateFactory 用于初始化模板引擎,并返回 Template 模板對象。TemplateFactory 定義見清單 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 接口則實現真正的渲染任務。定義見清單 12。

清單 12. 定義 Template

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

以 JSP 為例,實現 JspTemplateFactory 非常容易。代碼見清單 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 用于渲染頁面,只需要傳入 JSP 的路徑,將 Model 綁定到 HttpServletRequest,就可以調用 Servlet 規范的 forward 方法將請求轉發給指定的 JSP 頁面并渲染。代碼見清單 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 更加簡單且靈活的模板引擎是 Velocity,它使用更簡潔的語法來渲染頁面,對頁面設計人員更加友好,并且完全阻止了開發人員試圖在頁面中編寫 Java 代碼的可能性。使用 Velocity 編寫的頁面示例如清單 15 所示。

清單 15. Velocity 模板頁面

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

通過 VelocityTemplateFactory 和 VelocityTemplate 就可以實現對 Velocity 的集成。不過,從 Web 開發人員看來,并不需要知道具體使用的模板,客戶端僅需要提供模板路徑和一個由 Map<String, Object> 組成的 Model,然后返回一個 TemplateRenderer 對象。代碼如清單 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 通過簡單地調用 render 方法就實現了頁面渲染。為了指定 Jsp 或 Velocity,需要在 web.xml 中配置 DispatcherServlet 的初始參數。配置示例請參考清單 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> 

如果沒有該缺省參數,那就使用默認的 Jsp。

類似的,通過擴展 TemplateFactory 和 Template,就可以添加更多的模板支持,例如 FreeMarker。

設計攔截器

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

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

清單 18. 定義 Interceptor

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

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

清單 19. 定義 InterceptorChain

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

實現 InterceptorChain 要比實現 FilterChain 簡單,因為 Filter 需要處理 Request、Forward、Include 和 Error 這 4 種請求轉發的情況,而 Interceptor 僅攔截 Request。當 MVC 框架處理一個請求時,先初始化一個攔截器鏈,然后,依次調用鏈上的每個攔截器。請參考清單 20 所示的代碼。

清單 20. 實現 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 表示當前鏈上的第 N 個攔截器,當***一個攔截器被調用后,InterceptorChain 才真正調用 Execution 對象的 execute() 方法,并保存其返回結果,整個請求處理過程結束,進入渲染階段。清單 21 演示了如何調用攔截器鏈的代碼。

清單 21. 調用攔截器鏈

				
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()); 
    } 
} 

當 Controller 方法被調用完畢后,handleResult() 方法用于處理執行結果。

渲染

由于我們沒有強制 HTTP 處理方法的返回類型,因此,handleResult() 方法針對不同的返回值將做不同的處理。代碼如清單 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,則認為 HTTP 請求已處理完成,不做任何處理;如果返回 Renderer,則調用 Renderer 對象的 render() 方法渲染視圖;如果返回 String,則根據前綴是否有“redirect:”判斷是重定向還是作為 HTML 返回給瀏覽器。這樣,客戶端可以不必訪問 HttpServletResponse 對象就可以非常方便地實現重定向。代碼如清單 23 所示。

清單 23. 重定向

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

擴展 Renderer 還可以處理更多的格式,例如,向瀏覽器返回 JavaScript 代碼等。

擴展

使用 Filter 轉發

對于請求轉發,除了使用 DispatcherServlet 外,還可以使用 Filter 來攔截所有請求,并直接在 Filter 內實現請求轉發和處理。使用 Filter 的一個好處是如果 URL 沒有被任何 Controller 的映射方法匹配到,則可以簡單地調用 FilterChain.doFilter() 將 HTTP 請求傳遞給下一個 Filter,這樣,我們就不必自己處理靜態文件,而由 Web 服務器提供的默認 Servlet 處理,效率更高。和 DispatcherServlet 類似,我們編寫一個 DispatcherFilter 作為前置處理器,負責轉發請求,代碼見清單 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,則我們需要過濾“/*”,在 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> 

訪問 Request 和 Response 對象

如何在 @Mapping 方法中訪問 Servlet 對象?如 HttpServletRequest,HttpServletResponse,HttpSession 和 ServletContext。ThreadLocal 是一個最簡單有效的解決方案。我們編寫一個 ActionContext,通過 ThreadLocal 來封裝對 Request 等對象的訪問。代碼見清單 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 中移除所有已綁定變量,代碼見清單 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 方法內部,可以隨時獲得需要的 Request、Response、 Session 和 ServletContext 對象。

處理文件上傳

Servlet API 本身并沒有提供對文件上傳的支持,要處理文件上傳,我們需要使用 Commons FileUpload 之類的第三方擴展包。考慮到 Commons FileUpload 是使用最廣泛的文件上傳包,我們希望能集成 Commons FileUpload,但是,不要暴露 Commons FileUpload 的任何 API 給 MVC 的客戶端,客戶端應該可以直接從一個普通的 HttpServletRequest 對象中獲取上傳文件。

要讓 MVC 客戶端直接使用 HttpServletRequest,我們可以用自定義的 MultipartHttpServletRequest 替換原始的 HttpServletRequest,這樣,客戶端代碼可以通過 instanceof 判斷是否是一個 Multipart 格式的 Request,如果是,就強制轉型為 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(); 
    }; 
} 

對于正常的 Field 參數,保存在成員變量 Map<String, List<String>> formItems 中,通過覆寫 getParameter()、getParameters() 等方法,就可以讓客戶端把 MultipartHttpServletRequest 也當作一個普通的 Request 來操作,代碼見清單 29。

清單 29. 覆寫 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()]); 
    } 
} 

為了簡化配置,在 Web 應用程序啟動的時候,自動檢測當前 ClassPath 下是否有 Commons FileUpload,如果存在,文件上傳功能就自動開啟,如果不存在,文件上傳功能就不可用,這樣,客戶端只需要簡單地把 Commons FileUpload 的 jar 包放入 /WEB-INF/lib/,不需任何配置就可以直接使用。核心代碼見清單 30。

清單 30. 檢測 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); 
            } 
        } 
        ... 
    } 
    ... 
} 

小結

#t#要從頭設計并實現一個 MVC 框架其實并不困難,設計 WebWind 的目標是改善 Web 應用程序的 URL 結構,并通過自動提取和映射 URL 中的參數,簡化控制器的編寫。WebWind 適合那些從頭構造的新的互聯網應用,以便天生支持 REST 風格的 URL。但是,它不適合改造已有的企業應用程序,企業應用的頁面不需要搜索引擎的索引,其用戶對 URL 地址的友好程度通常也并不關心。

責任編輯:yangsai 來源: IBMDW
相關推薦

2010-01-08 12:03:42

ibmdwREST

2011-04-22 09:26:57

MVC設計

2023-02-03 17:29:46

2017-08-28 15:00:20

軟件系統架構風格

2009-05-08 08:59:35

RESTORBSOA

2011-04-15 10:26:38

JavaMVC

2011-11-23 14:03:54

JavaSSHMVC

2012-10-22 16:55:48

JavaMVC

2011-02-24 15:11:00

MVC框架

2023-10-23 10:43:24

SpringRestful風格

2009-06-12 19:18:08

REST客戶端框架JavaScript

2023-01-10 09:48:03

RESTAPIJersey

2009-06-19 11:28:45

2013-03-21 13:56:21

JavaScriptBackBone

2009-02-02 09:08:38

MVC框架控制器CakePHP

2009-02-02 09:04:52

MVC框架Java

2017-11-23 17:21:31

Yii框架IntelYii框架深度剖析

2009-04-24 09:43:09

.NETASP.NET框架

2009-07-22 10:34:37

ActionInvokASP.NET MVC

2009-06-19 11:43:59

Spring MVC框
點贊
收藏

51CTO技術棧公眾號

天堂中文在线播放| 日韩一区二区三区中文字幕| 亚洲一级毛片| 亚洲第一精品自拍| 日本三级免费观看| 免费看a在线观看| 成人av免费在线| 国产精品久久久久福利| 青青操国产视频| 午夜精品影视国产一区在线麻豆| 欧美三级视频在线| 欧美国产视频一区| 91大神在线网站| 粉嫩高潮美女一区二区三区| 国产精品福利在线观看| 久久精品99久久久久久| 欧美少妇xxxx| 亚洲白虎美女被爆操| 亚洲色图 在线视频| 欧美性受ⅹ╳╳╳黑人a性爽| 国产蜜臀97一区二区三区| 成人免费在线一区二区三区| 亚洲午夜无码久久久久| 激情久久综合| 精品国产一区二区三区久久久| 日本黄色免费观看| 成人短视频软件网站大全app| 日韩欧美黄色动漫| 日本人妻伦在线中文字幕| 91在线看黄| 26uuu亚洲综合色欧美| 97夜夜澡人人双人人人喊| 中文字幕第31页| 99亚洲伊人久久精品影院红桃| 久久综合88中文色鬼| 我想看黄色大片| 青青草原在线亚洲| 精品国产91洋老外米糕| 午夜免费视频网站| 亚洲黑人在线| 欧美性xxxxxx少妇| 久久久久久久久久久久久久国产| 丁香花电影在线观看完整版 | 国产成+人+综合+亚洲欧洲| 久久久久久免费观看| 天天久久综合| 色av中文字幕一区| 国产三级黄色片| av一区二区高清| 亚洲人成电影在线| 少妇真人直播免费视频| 亚洲免费福利一区| 亚洲美女免费精品视频在线观看| 怡红院一区二区| 成人资源在线播放| 亚洲第一精品福利| 中文字幕一区二区人妻电影丶| 成人中文字幕视频| 亚洲精品aⅴ中文字幕乱码| www.男人天堂| 欧美日韩导航| 精品视频在线播放色网色视频| 国产十八熟妇av成人一区| julia中文字幕一区二区99在线| 欧美一二区视频| 久草免费资源站| 高清精品xnxxcom| 亚洲国产精品va在线看黑人动漫| 国产精品扒开腿做爽爽爽a片唱戏| 北条麻妃一区二区三区在线| 亚洲国产精品久久精品怡红院| 看全色黄大色黄女片18| 奇米777国产一区国产二区| 亚洲欧美日韩综合| 91麻豆制片厂| 91精品国产调教在线观看| 久久国产精品亚洲| 五月天综合在线| 久久国产精品久久久久久电车| 青青草原一区二区| 在线观看免费黄色小视频| 精品一区二区在线免费观看| 91视频网页| 五十路在线观看| 国产婷婷色一区二区三区四区| 色一情一乱一伦一区二区三欧美 | 久久av网站| 亚洲成人av中文字幕| 97人妻精品一区二区免费| 欧美疯狂party性派对| 欧美日韩国产成人在线| 好吊操这里只有精品| 久久夜色精品| 成人性生交大片免费观看嘿嘿视频 | 国产成人ay| 久久久久北条麻妃免费看| 久久精品这里有| 日韩成人精品在线观看| 7777奇米亚洲综合久久 | 成人在线视频一区二区| 欧美12av| 新版中文在线官网| 色综合久久中文综合久久97| 国产欧美精品一二三| 欧美黑人巨大videos精品| 中文亚洲视频在线| 久青草视频在线观看| 日本在线不卡视频| 丁香婷婷久久久综合精品国产| 国产中文字幕在线观看| 亚洲综合一二区| 国产欧美高清在线| 伊人久久亚洲| 日韩网站免费观看高清| 成人毛片18女人毛片| 麻豆国产欧美日韩综合精品二区 | 免费成人av| 欧美大尺度在线观看| 中文字幕免费高清网站| 成人免费的视频| 亚洲区成人777777精品| 国产精品扒开腿做爽爽爽视频软件| 欧美一区二区观看视频| 国产午夜精品福利视频| 国产精品一国产精品k频道56| 亚洲精品欧美日韩| 二区三区在线| 精品久久久中文| 色诱av手机版| 午夜激情久久| 国产精品嫩草影院久久久| 日韩福利一区二区| 亚洲va欧美va人人爽| 永久免费黄色片| 91亚洲国产高清| 国产精品视频xxx| 九九在线视频| 色综合婷婷久久| 免费无码一区二区三区| 亚洲性图久久| av在线亚洲男人的天堂| 在线免费观看的av| 在线综合视频播放| 日本一级片免费| 免费看黄色91| 亚洲人成影视在线观看| 日韩成人高清| 亚洲性猛交xxxxwww| 亚洲欧美偷拍视频| 久久众筹精品私拍模特| 欧美老熟妇喷水| 首页亚洲中字| 68精品国产免费久久久久久婷婷 | 国产精品一区在线观看乱码| 色撸撸在线观看| 成人在线分类| 欧美成人精品影院| 亚洲h视频在线观看| 亚洲自拍偷拍综合| 成年人小视频在线观看| 一区二区三区四区五区精品视频| 国产精品一区二区不卡视频| а√在线中文在线新版 | 麻豆av电影在线观看| 色综合色狠狠综合色| 国产精久久一区二区三区| 蜜臀99久久精品久久久久久软件| 婷婷久久五月天| 成人污污www网站免费丝瓜| 九九久久精品一区| 天天操天天操天天操| 欧美日韩国产在线| 人妻aⅴ无码一区二区三区| 奇米精品一区二区三区在线观看 | 国产成人综合一区| 色偷偷综合网| www.久久艹| 伊人久久视频| 久久精视频免费在线久久完整在线看| av观看在线免费| 天天免费综合色| 久久亚洲AV无码专区成人国产| 九九视频精品免费| 国产美女作爱全过程免费视频| 久久人人爽人人爽人人片av不| 人人做人人澡人人爽欧美| av在线1区2区| 日韩欧美一级在线播放| 亚洲第一网站在线观看| 亚洲色图清纯唯美| 91精品人妻一区二区| 久久国产精品99久久人人澡| 欧美视频在线观看视频| 精品午夜久久| 不卡视频一区二区三区| 桃色一区二区| 另类图片亚洲另类| 理论视频在线| 日韩欧美专区在线| 免费黄色小视频在线观看| 亚洲黄一区二区三区| www.88av| 韩国v欧美v亚洲v日本v| 99精品人妻少妇一区二区| 久久久久久影院| 欧美激情专区| 这里视频有精品| 国产精品久久久久久久久久免费| 欧美1234区| 中文字幕欧美国内| 图片区 小说区 区 亚洲五月| 欧美日韩国产经典色站一区二区三区 | 亚洲天堂中文网| 欧美日韩视频免费播放| 三级影片在线看| 国产日韩亚洲欧美综合| 中文字幕a在线观看| 精品一区二区三区的国产在线播放| 丰满爆乳一区二区三区| 欧美精品网站| 黄色高清视频网站| 激情综合网站| 久久伦理网站| 久久精品亚洲成在人线av网址| 成人h片在线播放免费网站| 美女福利一区二区| 国模私拍视频一区| a在线免费观看| 久久精品国产清自在天天线| 国产天堂在线| 亚洲人高潮女人毛茸茸| 偷拍精品一区二区三区| 亚洲成人久久久久| 一级黄色录像大片| 欧美午夜片在线看| 无码人妻一区二区三区免费| 精品欧美国产一区二区三区| 国产亚洲第一页| 一区二区免费在线播放| 欧美日韩色视频| 国产精品久久久久毛片软件| 伊人影院综合网| 日本一区二区三区国色天香| 中文字幕免费看| 国产亚洲精品bt天堂精选| 亚洲第一成人网站| 久久精品视频在线免费观看 | 亚洲一区 中文字幕| 欧美午夜精品理论片a级按摩| 成年人av网站| 日韩欧美在线观看视频| 波多野结衣啪啪| 欧美在线不卡一区| 亚洲性在线观看| 91精品国产综合久久久久久久久久 | 亚州国产精品| 蜜桃视频日韩| 亚洲精品国产setv| 日本一区二区在线| 欧美gay男男猛男无套| 自拍偷拍亚洲色图欧美| 女人香蕉久久**毛片精品| 成人国产一区二区三区| 好看的亚洲午夜视频在线| 精品久久久久久无码中文野结衣| 夜夜嗨av一区二区三区网站四季av| 日韩欧美视频网站| 久久香蕉精品| 午夜视频在线网站| 国产精品资源在线观看| 成人免费看片载| 91丨porny丨首页| 国产在线综合视频| 亚洲色图.com| 国产无套在线观看| 色吊一区二区三区| 一级片视频网站| 精品蜜桃在线看| 激情小说 在线视频| 久久久91精品国产| 黄毛片在线观看| 国产精品视频免费在线观看| 精品视频在线观看免费观看 | 中文在线免费一区三区高中清不卡| 纪美影视在线观看电视版使用方法| 亚洲欧美国产三级| 色屁屁影院www国产高清麻豆| 欧洲另类一二三四区| 精品国产伦一区二区三区| 日韩高清免费在线| 日本黄色片在线观看| 高清欧美一区二区三区| 欧美性理论片在线观看片免费| 91九色视频导航| 青青久久av| 桥本有菜av在线| 亚洲伊人网站| 欧美国产日韩另类| 91美女在线视频| 91香蕉视频在线播放| 欧美日韩国产在线| 国产情侣av在线| 亚洲欧美制服第一页| 中文在线观看免费| 国产精品av电影| 动漫3d精品一区二区三区乱码| 日韩在线三级| 亚洲精品一级| 欧美性受xxxx黒人xyx性爽| 久久久久久久精| 免费一级黄色大片| 欧美美女一区二区| 国产中文字幕在线| 欧美亚洲国产成人精品| 欧美特黄不卡| 在线视频不卡国产| 久久蜜桃精品| 艳妇乳肉亭妇荡乳av| 亚洲综合色噜噜狠狠| 一级特黄aaa大片| 亚洲欧美精品中文字幕在线| 美洲精品一卡2卡三卡4卡四卡| 国产日韩av在线| 欧美色女视频| 国产精品va无码一区二区| 国产高清亚洲一区| 美国一级片在线观看| 欧美优质美女网站| 日本国产在线| 91国产精品91| 国语一区二区三区| 久久精品xxx| 国产老妇另类xxxxx| 日本激情视频一区二区三区| 在线一区二区视频| 免费在线黄色影片| 91精品国产91久久久久久| 91麻豆精品国产91久久久久推荐资源| 中文字幕一区二区三区最新 | 国产精品6699| 免费欧美一区| 老司机午夜av| 国产日韩亚洲欧美综合| 亚洲精品国产无码| 中文字幕精品在线视频| 国产激情久久| 亚洲一区二区三区精品动漫| 久久精品国产77777蜜臀| 国产一区二区三区视频播放| 欧美亚洲综合久久| h视频网站在线观看| 国产精品一区二区三| 成人免费在线观看av| 亚洲高清免费在线观看| 国产精品水嫩水嫩| 国产孕妇孕交大片孕| 欧美成人小视频| 超碰97久久| 成年人午夜视频在线观看| 99久久99久久精品国产片果冻| 国产成人在线观看网站| 亚洲精品自在久久| 免费观看成人性生生活片| 亚洲高清在线观看一区| 久久成人18免费观看| 蜜臀av午夜精品久久| 精品少妇一区二区三区在线播放 | 亚洲精品久久久久久久久久| 国产+人+亚洲| 在线观看欧美理论a影院| 国产理论在线播放| 综合久久综合久久| 午夜精品久久久久久久91蜜桃| 午夜精品视频在线| 免费看成人吃奶视频在线| 五月天婷婷亚洲| 亚洲国产欧美一区二区三区丁香婷| 天堂中文在线看| 国产欧美久久久久久| 国产精品vip| 性少妇bbw张开| 欧美高清性hdvideosex| jizz一区二区三区| 欧美另类高清视频在线| 激情久久久久久久久久久久久久久久| 久久精品www人人爽人人| 亚洲人成毛片在线播放| 97精品资源在线观看| 国产一二三在线视频| 日本一区二区不卡视频| 精品人妻少妇AV无码专区| 2020国产精品视频| 午夜激情久久| 久久人人爽人人爽人人片| 欧美精品vⅰdeose4hd| 久热在线观看视频| 秋霞在线一区二区| 久久久久久久久久看片| 国产99999| 国产精品久久一区|