扩展使用 Filter 转发对于请求转发,除了使用 DispatcherServlet 外,还可以使用 Filter 来拦截所有请求,并直接在 Filter 内实现请求转发和处理。使用 Filter 的一个好处是如果 URL 没有被任何 Controller 的映射方法匹配到,则可以简单地调用 FilterChain.doFilter() 将 HTTP 请求传递给下一个 Filter,这样,我们就不必自己处理静态文件,而由 Web 服务器提供的默认 Servlet 处理,效率更高。和 DispatcherServlet 类似,我们编写一个 DispatcherFilter 作为前置处理器,负责转发请求,代码见清单 24。
清单 24. 定义 DispatcherFilter1
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. 声明 DispatcherFilter1
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. 定义 ActionContext1
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. 初始化 ActionContext1
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. 定义 MultipartHttpServletRequest1
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. 覆写 getParameter1
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 FileUpload1
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);
}
}
...
}
...
}
|
|