设计 REST风格的 MVC框架

 

参考:http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/

 

流行的 Web趋势是使用更加简单,对用户和搜索引擎更加友好的REST风格的URL。例如,来自豆瓣的一本书的链接是http://www.douban.com/subject/2129650/,而非http://www.douban.com/subject.do?id=2129650

 

设计目标:

和传统的 StrutsMVC框架完全不同,为了支持REST风格的 URL,我们并不把一个URL映射到一个 Controller类(或者 StrutsAction),而是直接把一个URL映射到一个方法,这样,Web开发人员就可以将多个功能类似的方法放到一个Controller中,并且,Controller没有强制要求必须实现某个接口。一个Controller通常拥有多个方法,每个方法负责处理一个URL。例如,一个管理 BlogController定义起来:

 

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=1234postId=5678。同时,也无需任何与URL映射相关的 XML配置文件。

 

使用 $1$2……来定义 URL中的可变参数要比正则表达式更简单,我们需要在MVC框架内部将其转化为正则表达式,以便匹配URL

 

集成 IoC:

当接收到来自浏览器的请求,并匹配到合适的URL时,应该转发给某个Controller实例的某个标记有 @Mapping的方法,这需要持有所有Controller的实例。不过,让一个 MVC框架去管理这些组件并不是一个好的设计,这些组件可以很容易地被IoC容器管理,MVC框架需要做的仅仅是向 IoC容器请求并获取这些组件的实例。

 

为了解耦一种特定的IoC容器,我们通过ContainerFactory来获取所有 Controller组件的实例,如:

 

public interface ContainerFactory { 

    void init(Config config); 

    List<Object> findAllBeans(); 

    void destroy(); 
} 

 

 

其中,关键方法findAllBeans()返回 IoC容器管理的所有 Bean,然后,扫描每一个Bean的所有 public方法,并引用那些标记有@Mapping的方法实例。

 

我们设计目标是支持SpringGuice这两种容器,对于 Spring容器,可以通过ApplicationContext获得所有的 Bean引用:

 

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实例可以返回所有绑定对象的实例:

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; 
    } 
    ... 
} 

设计请求转发:

Struts等常见 MVC框架一样,我们也需要实现一个前置控制器,通常命名为DispatcherServlet,用于接收所有的请求,并作出合适的转发。在Servlet规范中,有以下几种常见的URL匹配模式:(php中常用的路由器设计模式相似)

* /abc:精确匹配,通常用于映射自定义的Servlet

**.do:后缀模式匹配,常见的MVC框架都采用这种模式;

*/app/*:前缀模式匹配,这要求URL必须以固定前缀开头;

*/:匹配默认的 Servlet,当一个URL没有匹配到任何 Servlet时,就匹配默认的 Servlet。一个Web应用程序如果没有映射默认的ServletWeb服务器会自动为 Web应用程序添加一个默认的Servlet

 

REST风格的 URL一般不含后缀,我们只能将DispatcherServlet映射到“/”,使之变为一个默认的Servlet,这样,就可以对任意的URL进行处理。

 

由于无法像 Struts等传统的 MVC框架根据后缀直接将一个URL映射到一个Controller,我们必须依次匹配每个有能力处理HTTP请求的 @Mapping方法。

 

当扫描到标记有@Mapping注解的方法时,需要首先检查URL与方法参数是否匹配,UrlMatcher用于将 @Mapping中包含 $1$2……的字符串变为正则表达式,进行预编译,并检查参数个数是否符合方法参数,例如:

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

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

 

@Mapping中包含 $1$2……的字符串变为正则表达式的转换规则是,依次将每个$n替换为([^\\/]*),其余部分作精确匹配。例如,“/blog/$1/$2”变化后的正则表达式为:^\\/blog\\/([^\\/]*)\\/([^\\/]*)$

 

调用一个实例方法则由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通过关联 UrlMatcherAction,就可以匹配到合适的URL,并转发给相应的Action:

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

 

Dispatcher接收到一个 URL请求时,遍历所有的UrlMatcher,找到第一个匹配URLUrlMatcher,并从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转换为合适的方法参数类型,准备好全部参数:

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封装:

 

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代码。我们的设计目标是实现对JSPVelocity这两种模板的支持。

 

和集成 IoC框架类似,我们需要解耦MVC与模板系统,因此,TemplateFactory用于初始化模板引擎,并返回Template模板对象:

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

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

Template接口则实现真正的渲染任务。

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

 

JSP为例,实现 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页面并渲染。

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代码的可能性。

 

通过VelocityTemplateFactoryVelocityTemplate就可以实现对 Velocity的集成。不过,从 Web开发人员看来,并不需要知道具体使用的模板,客户端仅需要提供模板路径和一个由Map<String,Object> 组成的 Model,然后返回一个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方法就实现了页面渲染。为了指定JspVelocity,需要在web.xml中配置 DispatcherServlet的初始参数

 

<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

类似的,通过扩展TemplateFactoryTemplate,就可以添加更多的模板支持,例如FreeMarker

 

设计拦截器:

拦截器和 Servlet规范中的 Filter非常类似,不过 Filter的作用范围是整个HttpServletRequest的处理过程,而拦截器仅作用于Controller,不涉及到View的渲染,在大多数情况下,使用拦截器比Filter速度要快,尤其是绑定数据库事务时,拦截器能缩短数据库事务开启的时间。

 

拦截器接口Interceptor定义:

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

 

Filter类似,InterceptorChain代表拦截器链。InterceptorChain定义:

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

 

实现 InterceptorChain要比实现 FilterChain简单,因为 Filter需要处理RequestForwardIncludeError4种请求转发的情况,而Interceptor仅拦截 Request。当MVC框架处理一个请求时,先初始化一个拦截器链,然后,依次调用链上的每个拦截器。

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演示了如何调用拦截器链的代码。

 

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()方法针对不同的返回值将做不同的处理。

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对象就可以非常方便地实现重定向。

@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作为前置处理器,负责转发请求,

 

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中添加声明

<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> 

访问 RequestResponse对象

 

如何在 @Mapping方法中访问 Servlet对象?如HttpServletRequestHttpServletResponseHttpSessionServletContextThreadLocal 是一个最简单有效的解决方案。我们编写一个ActionContext,通过ThreadLocal来封装对 Request等对象的访问。

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

 

DispatcherhandleExecution()方法中,初始化ActionContext,并在finally中移除所有已绑定变量

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方法内部,可以随时获得需要的RequestResponseSessionServletContext对象。

 

处理文件上传

 

ServletAPI 本身并没有提供对文件上传的支持,要处理文件上传,我们需要使用CommonsFileUpload 之类的第三方扩展包。考虑到CommonsFileUpload 是使用最广泛的文件上传包,我们希望能集成CommonsFileUpload,但是,不要暴露CommonsFileUpload 的任何 APIMVC的客户端,客户端应该可以直接从一个普通的HttpServletRequest对象中获取上传文件。

 

要让 MVC客户端直接使用HttpServletRequest,我们可以用自定义的MultipartHttpServletRequest替换原始的HttpServletRequest,这样,客户端代码可以通过instanceof判断是否是一个 Multipart格式的 Request,如果是,就强制转型为MultipartHttpServletRequest,然后,获取上传的文件流。

 

核心思想是从HttpServletRequestWrapper派生MultipartHttpServletRequest,这样,MultipartHttpServletRequest具有 HttpServletRequest接口。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来操作

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下是否有 CommonsFileUpload,如果存在,文件上传功能就自动开启,如果不存在,文件上传功能就不可用,这样,客户端只需要简单地把CommonsFileUpload jar包放入/WEB-INF/lib/,不需任何配置就可以直接使用。核心代码:

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

posted on 2010-12-15 23:16  菊次郎  阅读(834)  评论(0)    收藏  举报