首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

设计 REST 风格的 MVC 框架-6 扩展

设计 REST 风格的 MVC 框架-6 扩展

扩展使用 Filter 转发对于请求转发,除了使用 DispatcherServlet 外,还可以使用 Filter 来拦截所有请求,并直接在 Filter 内实现请求转发和处理。使用 Filter 的一个好处是如果 URL 没有被任何 Controller 的映射方法匹配到,则可以简单地调用 FilterChain.doFilter() 将 HTTP 请求传递给下一个 Filter,这样,我们就不必自己处理静态文件,而由 Web 服务器提供的默认 Servlet 处理,效率更高。和 DispatcherServlet 类似,我们编写一个 DispatcherFilter 作为前置处理器,负责转发请求,代码见清单 24。
清单 24. 定义 DispatcherFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
5
6
7
8
<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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 ...
1
2
3
4
5
6
7
8
9
10
11
12
13
        }
        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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
            }
        }
        ...
    }
    ...
}

返回列表