前段时间准备做一个小网站,但是又不想用Spring/Struts/WebWork这样的大块头,因此决定自己写一个MVC框架。花了3天左右时间完成,目前运行良好,整个MVC框架仅21KB,感兴趣的朋友可以从http://code.google.com/p/lightweight-mvc/downloads/list下载完整的源代码和jar包。 设计目标: 一个最简单最小巧的MVC框架,花哨的功能一个不要,越简洁越好,并且不使用XML配置文件,而是完全用Java 5注解配置。 功能列表: 组件必须用IoC配置; 处理HTTP请求的Action,类似WebWork每个请求都生成一个新实例,并自动填充属性; 类似Filter的Interceptor机制,但是在IoC容器中配置; 统一的异常处理; 多视图支持。 由于组件需要用IoC容器配置,因此,第一步就是寻找小巧的IoC容器,Google Guice是一个很不错的选择,并且完全用Java 5注解配置组件。这个MVC框架唯一依赖的也就是Guice和Commons Logging两个jar包,如果使用Velocity作为视图则还需要Velocity的jar包。 下一步,开始设计各主要功能类: 负责处理Http请求的Action类必须实现的Action接口: package com.javaeedev.lightweight.mvc; public interface Action { ModelAndView execute() throws Exception; } 从WebWork抄过来,不过返回值由String改成了ModelAndView(从Spring抄过来的),好处是不必再次根据String查找视图的绝对路径,直接在ModelAndView中包含了。用Spring的MVC其实可以发现,ModelAndView同时包含一个Model(本质是一个Map)和View的路径,减少了Struts和WebWork需要的一个XML映射文件,而维护XML配置文件是一件相当令人头疼的问题,往往改了代码还要改配置,索性写死在代码中得了,视图路径又不会经常改变,没必要为了额外的灵活性给自己搞一堆XML配置文件。 Action返回的ModelAndView: package com.javaeedev.lightweight.mvc; public final class ModelAndView { private String view; private Map<string object> model;</string> /** * Construct a View with empty model. * * @param view View's logic name. */ public ModelAndView(String view) { this.view = view; this.model = Collections.emptyMap(); } /** * Construct a View with model. * * @param view View's logic name. * @param model Model as a Map. */ public ModelAndView(String view, Map<string object> model) {<br> this.view = view;<br> this.model = model;<br> }</string> /** * Return View. * * @return View's logic name. */ public String getView() { return view; } /** * Return model. * * @return Model as a Map. */ public Map<string object> getModel() {<br> return model;<br> }</string> } 这个完全是从Spring MVC抄过来的,Map改成了泛型,View路径可以以"redirect:"开头表示重定向,这个和Spring MVC一致。虽然直接调用HttpServletResponse也可以重定向,但是遇到事务处理起来会很麻烦,还是让MVC框架自己来处理会好一些。 WebWork的Action设计的好处是大大简化了参数的绑定,不过很多时候也需要在Action中访问HttpSession等对象,因此还需要设计一个ActionContext类,通过ThreadLocal让Action对象能轻易地访问到这些对象: package com.javaeedev.lightweight.mvc; public final class ActionContext { private static ThreadLocal<actioncontext> contextThreadLocal = new ThreadLocal<actioncontext>();</actioncontext></actioncontext> /** * Get current ActionContext. * * @return ActionContext. */ public static ActionContext getActionContext() { return contextThreadLocal.get(); } private HttpServletRequest request; private HttpServletResponse response; private HttpSession session; private ServletContext context; /** * Initiate all servlet objects as thread local. * * @param request HttpServletRequest object. * @param response HttpServletResponse object. * @param session HttpSession object. * @param context ServletContext object. */ static void setActionContext(HttpServletRequest request, HttpServletResponse response, HttpSession session, ServletContext context) { ActionContext actionContext = new ActionContext(); actionContext.setRequest(request); actionContext.setResponse(response); actionContext.setSession(session); actionContext.setServletContext(context); contextThreadLocal.set(actionContext); } /** * Remove all servlet objects from thread local. */ static void remove() { contextThreadLocal.remove(); } /** * Get HttpServletRequest object. * * @return HttpServletRequest object. */ public HttpServletRequest getRequest() { return request; } /** * Set HttpServletRequest object. * * @param request HttpServletRequest object. */ void setRequest(HttpServletRequest request) { this.request = request; } /** * Get HttpServletResponse object. * * @return HttpServletResponse object. */ public HttpServletResponse getResponse() { return response; } /** * Set HttpServletResponse object. * * @param response HttpServletResponse object. */ void setResponse(HttpServletResponse response) { this.response = response; } /** * Get HttpSession object. * * @return HttpSession object. */ public HttpSession getSession() { return session; } /** * Set HttpSession object. * * @param session HttpSession object. */ void setSession(HttpSession session) { this.session = session; } /** * Get ServletContext object. * * @return ServletContext object. */ public ServletContext getServletContext() { return context; } /** * Set ServletContext object. * * @param context ServletContext object. */ void setServletContext(ServletContext context) { this.context = context; } } 接下来是定义类似Filter功能的Interceptor接口: package com.javaeedev.lightweight.mvc; /** * Intercept action's execution like servlet Filter, but interceptors are * configured and managed by IoC container. Another difference from Filter * is that Interceptor is executed around Action's execution, but before * rendering view. * * @author Xuefeng */ public interface Interceptor { /** * Do intercept and invoke chain.doInterceptor() to process next interceptor. * NOTE that process will not continue if chain.doInterceptor() method is not * invoked. * * @param action Action instance to handle http request. * @param chain Interceptor chain. * @throws Exception If any exception is thrown, process will not continued. */ void intercept(Action action, InterceptorChain chain) throws Exception; } InterceptorChain对象和FilterChain是一样的,它允许一个拦截器是否将请求继续交给下一拦截器处理,还是中断当前请求的处理: package com.javaeedev.lightweight.mvc; /** * Holds all interceptors as a chain. * * @author Xuefeng */ public interface InterceptorChain { /** * Apply next interceptor around the execution of Action. * * @param action Target Action to execute. * @throws Exception Any exception if error occured. */ void doInterceptor(Action action) throws Exception; } 最后是支持多种View的ViewResolver,这个也抄自Spring MVC: package com.javaeedev.lightweight.mvc; import java.io.IOException; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * To resolve and render a view. * * @author Xuefeng */ public interface ViewResolver { /** * Init this ViewResolver. * * @param context ServletContext object that holds information of current * web application. * @throws ServletException If init failed. */ void init(ServletContext context) throws ServletException; /** * To resolve view's name and render view if necessary. * * @param view View's logic name. * @param model Model represent as a generic Map. * @param request HttpServletRequest object. * @param response HttpServletResponse object. * @throws ServletException If any ServletException occur. * @throws IOException If any IOException occur. */ void resolveView(String view, Map<string object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;</string> } 第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。 到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。 后面我们会讲述如何实现该MVC框架。
|