超轻量级MVC框架的设计和实现 (1)

超轻量RESTful框架—SlwRest(Super Lightweight)客户端及服务端Demo代码 RequestMethod支持GET, POST, DELETE, PUT 参数获取支持 @FormParam, @PathParam, @QueryParam, @HeaderParam等 内容支持常用的JSON, XML, TEXT 能够方便地实现Java复杂对象, List, Map对象与JSON/XML的互转换 支持多文件、多参数上传,支持文件下 立即下载

前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




前段时间准备做一个小网站,但是又不想用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 model;

    /**
     * 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 model) {
        this.view = view;
        this.model = model;
    }

    /**
     * Return View.
     *
     * @return View's logic name.
     */
    public String getView() {
        return view;
    }

    /**
     * Return model.
     *
     * @return Model as a Map.
     */
    public Map getModel() {
        return model;
    }

}

这个完全是从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 contextThreadLocal = new ThreadLocal ();

    /**
     * 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 model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持FreeMarker的ViewResolver。

到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。

后面我们会讲述如何实现该MVC框架。 




Java开发者的福音:Aspose 25.10全系列(Cells/Words/PDF/Slides)最新版破解与集成保姆级教程 本文为Java开发者提供了Aspose 25.10全系列组件(Cells/Words/PDF/Slides)的最新版破解与集成保姆级教程。通过详细的Maven依赖配置、字节码修改技术绕过限制,以及Spring Boot项目中的实战集成指南,帮助开发者高效实现Office文档处理功能,提升开发效率。 阅读详情

相关推荐

【SpringBoot香樟转转】debugDay01

在学黑马程序员的苍穹外卖项目,刚开始学,想用maven编译一下代码,结果报错,找不到符号。本着先理解原则,看着能找到这个变量呀。

m0_72696598的博客 1741

MVC框架简易实现【精细】

model ----------> 业务领域的核心对象(Entity,Service,Dao)View ----------> 视图层,做数据的提交和渲染 (JSP、HTML.........)Controller --------> 控制层,专门用于处理Http请求(Servlet)简化控制层以及视图渲染的操作,将这些API进行更高层封装,使用起来更加简便,可以隐藏Servlet的操作,同时还可以提供简便的部署方式。

惜若 1665

避坑指南:ShardingSphere连接达梦/金仓数据库时SQL解析器的那些‘坑‘

本文深入探讨了ShardingSphere与国产数据库(如达梦、金仓)集成时遇到的SQL解析器差异问题,提供了详细的解决方案和优化策略。通过分析SQL方言差异、JDBC实现差异及解析器兼容性,帮助开发者有效避坑,提升分布式数据库架构设计的效率与稳定性。

weixin_29309475的博客 392

轻量级MVC框架搭建

本实验主要描述轻量级MVC框架(jquery+Spring MVC+Spring+Mybatis)的搭建。本实验分为两步骤进行介绍,第一步讲述Spring MVC框架环境的搭建,第二步讲述Spring与Mybatis框架的整合配置。学习轻量级MVC框架的搭建。通过本次实验要掌握Spring MVC框架的搭建,以及Spring与Mybatis框架的整合,并掌握Spring 事务的配置。环境搭建  S...

ironhide1994的博客 373

双三相电机控制避坑指南:为什么你的SVPWM谐波大?四矢量调制实战解析

本文深入解析双三相电机控制中SVPWM谐波大的问题,并提供四矢量调制实战方案。通过分析谐波来源、四矢量调制原理及工程实现步骤,显著降低电流THD至3-5%,提升系统效率2.3个百分点。特别适合需要优化电机控制的工程师参考。

weixin_30645617的博客 398

Agent 团队管理手册:如何设定目标、分配角色与解决冲突

本文从Agent团队的底层逻辑出发,完整讲解了Agent团队管理的三大核心模块:目标设定(分层拆解+对齐校验)、角色分配(能力匹配+最优算法)、冲突处理(分类处理+仲裁投票),并提供了可直接运行的完整代码示例。通过本文的方法,你可以快速搭建一个高效协作的Agent团队,大幅提升复杂任务的完成效率和准确率。Agent团队是未来AI应用的核心形态,现在正是落地的黄金期,建议你从一个小场景开始动手实践,逐步优化自己的Agent团队管理方法论。

AI天才研究院 238

Spring MVC:轻量级MVC框架2.0

Rest(Representational State Transfer:表现层状态转移)是一种软件架构风格,其核心是面向资源的一种设计。@ResponseBody注解是类或方法级注解。当方法上添加@ResponseBody注解后,控制单元方法返回值将不再被视图解析器进行解析|不会使用转发。而是把返回值放入到响应流中进行响应。@RequestBody注解底层依赖的依然是Jackson工具包,其作用是把客户端传递过来的请求体中JSON或XML数据转换为Map、类、List<类>、List<Map>等类型。

weixin_64022848的博客 424

MFC实现图书管理系统(附完整源码)

MFC实现图书管理系统(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 804

SpringMVC+Maven搭建一个增删改查小项目(无连接数据库)

1.创建Maven项目, 2.配置pom.xml文件,添加依赖的jar包 做web项目必须将打包方式改成war Maven仓库地址:https://mvnrepository.com/ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instanc

qq_53261858的博客 1475

射频微波电路设计【1.4】

AppCAD是一组射频设计工具和计算机应用说明,它使射频工程师能以更快和更容易的方式设计他们的产品。该软件的理 念是为工程师提供一个“个人设计助理”,即功能强大、快速和容易使用的辅助设计工具。该软件包括如下基本功能: ·各种传输线电气参数的分析与计算;·各种匹配电路的分析与计算;·各种有源和无源简单电路的分析与计算;·射频系统指标的计算:增益、噪声系数、三阶交调、信噪比、灵敏度等;·器件可靠性分析。

qq_43416206的博客 2326

姿态传感器/陀螺仪之ICM-42670-P应用原理图设计

ICM-42670-P采用LGA-14封装,尺寸为2.5x3x0.76毫米,工作电压范围为1.71V至3.6V,支持I3CSM、I2C和SPI串行通信接口。其设计目标是提供高精度、低功耗和小型化的解决方案,以满足消费类和工业应用的需求。ICM-42670-P是一款由TDK InvenSense推出的高性能六轴MEMS运动追踪传感器,集成了3轴陀螺仪和3轴加速度计,适用于多种应用场景,包括可穿戴设备、游戏控制器、无人机、AR/VR设备等。

corlin6688的博客 1207

手把手教你写MVC基础框架

VC模式下,客户直接发送请求到控制器,控制器根据用户请求的资源分发到相对应的模型来处理,模型完成了业务逻辑后,把所要的数据发送到视图,视图显示返回给客户。这就是web 或是说B/S架构的MVC工作流程。 控制器: 用户的所有请求会发送到控制器,由控制器来根据需要调用模型和视图。比如用户请求index.php 控制器文件,index.php里面不会设计到任何的数据库操作、逻辑操作。它只会寻找...

327

使用 ABAP Development Tool 登录 SAP BTP ABAP 环境时,遇到弹出框应该怎么办?

但在实际环境里,隐私模式、第三方 Cookie 禁止、跨站追踪限制、缓存代理误命中、混合内容拦截等因素,都可能让这条跳转链路被打断。等),浏览器会弹出系统原生登录框,请求用户输入账号和口令或做集成认证。只要它们在响应头里出现,浏览器就会理直气壮地弹框,这是协议层的强约定。,最终弹出原生登录框。前端与网关领域有个通用的经验规则:如果你看到了原生登录框,而期望的是带样式的。环境里的登录方式表述为基于浏览器的前端认证,也就是这类重定向式的交互。环境的具体落地,拆出几类常见诱因与排障路径,并穿插真实世界的案例。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 375

1.写一个Mvc框架_超轻量级MVC框架的设计和实现

超轻量级MVC框架的设计和实现 2009-11-17 16:01:15 分类: Java 1.设计背景    前段时间准备做一个小网站,但是又不想用Spring/Struts/WebWork这样的大块头,因此决定自己写一个MVC框架。花了3天左右时间完成,目前运行良好,整个MVC框架仅21KB,感兴趣的朋友可以从http://code.google.com/p/l

codepython的专栏 597

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

上一篇: Subclipse入门指南
下一篇: Google手机操作系统Android应用开发入门
techweb
博客等级 码龄19年 226粉丝 3842原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值