- 浏览: 210066 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
HeSanJava:
谢谢,文章很有用
spring定时任务执行两次 -
JobinBai:
xiaoqiang2008 写道执行两次的原因是什么,好像楼主 ...
spring定时任务执行两次 -
zhangguicheng12:
果然是tomcat配置的问题
多谢了!
spring定时任务执行两次 -
xiaoqiang2008:
执行两次的原因是什么,好像楼主没弄清楚啊!是不是在web.xm ...
spring定时任务执行两次 -
zx527291227:
你好!按照你的说我尝试了下但是还是会访问两次,能帮忙看下是什么 ...
spring定时任务执行两次
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 所示。
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。
此外,对于方法返回值,也未作强制要求。
当接收到来自浏览器的请求,并匹配到合适的 URL 时,应该转发给某个 Controller 实例的某个标记有 @Mapping 的方法,这需要持有所有 Controller 的实例。不过,让一个 MVC 框架去管理这些组件并不是一个好的设计,这些组件可以很容易地被 IoC 容器管理,MVC 框架需要做的仅仅是向 IoC 容器请求并获取这些组件的实例。
为了解耦一种特定的 IoC 容器,我们通过 ContainerFactory 来获取所有 Controller 组件的实例,如清单 2 所示。
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 所示。
当扫描到标记有 @Mapping 注解的方法时,需要首先检查 URL 与方法参数是否匹配,UrlMatcher 用于将 @Mapping 中包含 $1、$2 ……的字符串变为正则表达式,进行预编译,并检查参数个数是否符合方法参数,代码见清单 5。
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。
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。
class Dispatcher { private UrlMatcher[] urlMatchers; private Map<UrlMatcher, Action> urlMap = new HashMap<UrlMatcher, Action>(); .... } |
当 Dispatcher 接收到一个 URL 请求时,遍历所有的 UrlMatcher,找到第一个匹配 URL 的 UrlMatcher,并从 URL 中提取方法参数,代码见清单 8。
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。
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。
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。
public abstract class TemplateFactory { private static TemplateFactory instance; public static TemplateFactory getTemplateFactory() { return instance; } public abstract Template loadTemplate(String path) throws Exception; } |
Template 接口则实现真正的渲染任务。定义见清单 12。
public interface Template { void render(HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) throws Exception; } |
以 JSP 为例,实现 JspTemplateFactory 非常容易。代码见清单 13。
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。
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 所示。
<html> <head><title>${title}</title></head> <body><h1>Hello, ${name}!</body> </html> |
通过 VelocityTemplateFactory 和 VelocityTemplate 就可以实现对 Velocity 的集成。不过,从 Web 开发人员看来,并不需要知道具体使用的模板,客户端仅需要提供模板路径和一个由 Map<String, Object> 组成的 Model,然后返回一个 TemplateRenderer 对象。代码如清单 16 所示。
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。
<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 所示。
public interface Interceptor { void intercept(Execution execution, InterceptorChain chain) throws Exception; } |
和 Filter 类似,InterceptorChain 代表拦截器链。InterceptorChain 定义如清单 19 所示。
public interface InterceptorChain { void doInterceptor(Execution execution) throws Exception; } |
实现 InterceptorChain 要比实现 FilterChain 简单,因为 Filter 需要处理 Request、Forward、Include 和 Error 这 4 种请求转发的情况,而 Interceptor 仅拦截 Request。当 MVC 框架处理一个请求时,先初始化一个拦截器链,然后,依次调用链上的每个拦截器。请参考清单 20 所示的代码。
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() 方法针对不同的返回值将做不同的处理。代码如清单 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 所示。
@Mapping("/register") String register() { ... if (success) return "redirect:/reg/success"; return "redirect:/reg/failed"; } |
扩展 Renderer 还可以处理更多的格式,例如,向浏览器返回 JavaScript 代码等。
对于请求转发,除了使用 DispatcherServlet 外,还可以使用 Filter 来拦截所有请求,并直接在 Filter 内实现请求转发和处理。使用 Filter 的一个好处是如果 URL 没有被任何 Controller 的映射方法匹配到,则可以简单地调用 FilterChain.doFilter() 将 HTTP 请求传递给下一个 Filter,这样,我们就不必自己处理静态文件,而由 Web 服务器提供的默认 Servlet 处理,效率更高。和 DispatcherServlet 类似,我们编写一个 DispatcherFilter 作为前置处理器,负责转发请求,代码见清单 24。
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 所示。
<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> |
如何在 @Mapping 方法中访问 Servlet 对象?如 HttpServletRequest,HttpServletResponse,HttpSession 和 ServletContext。ThreadLocal 是一个最简单有效的解决方案。我们编写一个 ActionContext,通过 ThreadLocal 来封装对 Request 等对象的访问。代码见清单 26。
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。
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。
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。
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); } } ... } ... } |
要从头设计并实现一个 MVC 框架其实并不困难,设计 WebWind 的目标是改善 Web 应用程序的 URL 结构,并通过自动提取和映射 URL 中的参数,简化控制器的编写。WebWind 适合那些从头构造的新的互联网应用,以便天生支持 REST 风格的 URL。但是,它不适合改造已有的企业应用程序,企业应用的页面不需要搜索引擎的索引,其用户对 URL 地址的友好程度通常也并不关心。
发表评论
-
使用 Spring Security 保护 Web 应用的安全
2011-01-04 10:57 1430在 Web 应用开发中,安全一直是非常重要的一个方 ... -
SimpleJdbcTemplate log4j
2010-12-07 18:21 1182log4j.logger.org.springframewor ... -
default-autowire="byType"冲突解决
2010-12-07 17:59 3446当我这样配置p6spy时, ... -
SimpleJdbcTemplate batchUpdate 推荐使用方式
2010-12-07 16:54 4024SimpleJdbcTemplate 类提供了另外一 ... -
多种技术框架的融合转型为一种框架的大一统(spring3.x)
2010-11-25 16:44 1862也许spring3.x all i ... -
详解 Spring 3.0 基于 Annotation 的依赖注入实现
2010-11-18 10:56 964使用 @Repository、@Service、@Contro ...
相关推荐
pandas whl安装包,对应各个python版本和系统(具体看资源名字),找准自己对应的下载即可! 下载后解压出来是已.whl为后缀的安装包,进入终端,直接pip install pandas-xxx.whl即可,非常方便。 再也不用担心pip联网下载网络超时,各种安装不成功的问题。
基于java的大学生兼职信息系统答辩PPT.pptx
基于java的乐校园二手书交易管理系统答辩PPT.pptx
tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
有学生和教师两种角色 登录和注册模块 考场信息模块 考试信息模块 点我收藏 功能 监考安排模块 考场类型模块 系统公告模块 个人中心模块: 1、修改个人信息,可以上传图片 2、我的收藏列表 账号管理模块 服务模块 eclipse或者idea 均可以运行 jdk1.8 apache-maven-3.6 mysql5.7及以上 tomcat 8.0及以上版本
tornado-6.1b2-cp38-cp38-macosx_10_9_x86_64.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
matlab
基于java的毕业生就业信息管理系统答辩PPT.pptx
随着高等教育的普及和毕业设计的日益重要,为了方便教师、学生和管理员进行毕业设计的选题和管理,我们开发了这款基于Web的毕业设计选题系统。 该系统主要包括教师管理、院系管理、学生管理等多个模块。在教师管理模块中,管理员可以新增、删除教师信息,并查看教师的详细资料,方便进行教师资源的分配和管理。院系管理模块则允许管理员对各个院系的信息进行管理和维护,确保信息的准确性和完整性。 学生管理模块是系统的核心之一,它提供了学生选题、任务书管理、开题报告管理、开题成绩管理等功能。学生可以在此模块中进行毕业设计的选题,并上传任务书和开题报告,管理员和教师则可以对学生的报告进行审阅和评分。 此外,系统还具备课题分类管理和课题信息管理功能,方便对毕业设计课题进行分类和归档,提高管理效率。在线留言功能则为学生、教师和管理员提供了一个交流互动的平台,可以就毕业设计相关问题进行讨论和解答。 整个系统设计简洁明了,操作便捷,大大提高了毕业设计的选题和管理效率,为高等教育的发展做出了积极贡献。
这个数据集来自世界卫生组织(WHO),包含了2000年至2015年期间193个国家的预期寿命和相关健康因素的数据。它提供了一个全面的视角,用于分析影响全球人口预期寿命的多种因素。数据集涵盖了从婴儿死亡率、GDP、BMI到免疫接种覆盖率等多个维度,为研究者提供了丰富的信息来探索和预测预期寿命。 该数据集的特点在于其跨国家的比较性,使得研究者能够识别出不同国家之间预期寿命的差异,并分析这些差异背后的原因。数据集包含22个特征列和2938行数据,涉及的变量被分为几个大类:免疫相关因素、死亡因素、经济因素和社会因素。这些数据不仅有助于了解全球健康趋势,还可以辅助制定公共卫生政策和社会福利计划。 数据集的处理包括对缺失值的处理、数据类型转换以及去重等步骤,以确保数据的准确性和可靠性。研究者可以使用这个数据集来探索如教育、健康习惯、生活方式等因素如何影响人们的寿命,以及不同国家的经济发展水平如何与预期寿命相关联。此外,数据集还可以用于预测模型的构建,通过回归分析等统计方法来预测预期寿命。 总的来说,这个数据集是研究全球健康和预期寿命变化的宝贵资源,它不仅提供了历史数据,还为未来的研究和政策制
基于微信小程序的高校毕业论文管理系统小程序答辩PPT.pptx
基于java的超市 Pos 收银管理系统答辩PPT.pptx
基于java的网上报名系统答辩PPT.pptx
基于java的网上书城答辩PPT.pptx
婚恋网站 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
基于java的戒烟网站答辩PPT.pptx
基于微信小程序的“健康早知道”微信小程序答辩PPT.pptx
Capital Bikeshare 数据集是一个包含从2020年5月到2024年8月的自行车共享使用情况的数据集。这个数据集记录了华盛顿特区Capital Bikeshare项目中自行车的租赁模式,包括了骑行的持续时间、开始和结束日期时间、起始和结束站点、使用的自行车编号、用户类型(注册会员或临时用户)等信息。这些数据可以帮助分析和预测自行车共享系统的需求模式,以及了解用户行为和偏好。 数据集的特点包括: 时间范围:覆盖了四年多的时间,提供了长期的数据观察。 细节丰富:包含了每次骑行的详细信息,如日期、时间、天气条件、季节等,有助于深入分析。 用户分类:数据中区分了注册用户和临时用户,可以分析不同用户群体的使用习惯。 天气和季节因素:包含了天气情况和季节信息,可以研究这些因素对骑行需求的影响。 通过分析这个数据集,可以得出关于自行车共享使用模式的多种见解,比如一天中不同时间段的使用高峰、不同天气条件下的使用差异、季节性变化对骑行需求的影响等。这些信息对于城市规划者、交通管理者以及自行车共享服务提供商来说都是非常宝贵的,可以帮助他们优化服务、提高效率和满足用户需求。同时,这个数据集也