Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 240|回复: 0

[设计模式学习]超轻量级MVC框架的设计和实现(源码)

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-6 00:02:30 | 显示全部楼层 |阅读模式
    设计目标:      一个最简单最小巧的MVC框架,花哨的功能一个不要,越简洁越好,并且不使用XML配置文件,而是完全用java 5注解配置。 功能列表: 1、组件必须用IoC配置;
                                                                                        
    2、处理HTTP请求的Action,类似WebWork每个请求都生成一个新实例,并自动填充属性; 3、类似Filter的Interceptor机制,但是在IoC容器中配置; 4、统一的异常处理;
    5、多视图支持。  
      
      
          由于组件需要用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;     /**
          * 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) {
             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<String, Object> 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<ActionContext> contextThreadLocal = new ThreadLocal<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; }     第一个版本支持JSP和Velocity两种View,其实支持其他的View完全是可扩展的,只需要参考现有的两种ViewResolver的实现再写一个实现即可,例如支持freemarker的ViewResolver。     到此为止,提供给客户端的API准备完毕。下一步是如何实现这些API。虽然概念和结构都来自WebWork和Spring,但是其具体实现却没有参考他们的源代码,因为读大块头的源码本身就是一件非常费力的事情,还不如自己身体力行,写代码往往比读懂代码更快。     在设计完API后,我们就需要实现这个MVC框架。MVC框架的核心是一个DispatcherServlet,用于接收所有的HTTP请求,并根据URL选择合适的Action对其进行处理。在这里,和Struts不同的是,所有的组件均被IoC容器管理,因此,DispatcherServlet需要实例化并持有Guice IoC容器,此外,DispatcherServlet还需要保存URL映射和Action的对应关系,一个Interceptor拦截器链,一个ExceptionResolver处理异常。DispatcherServlet定义如下: package com.javaeedev.lightweight.mvc; /**
      * Core dispatcher servlet.
      *
      * @author Xuefeng
      */
    public class DispatcherServlet extends HttpServlet {     private Log log = LogFactory.getLog(getClass());     private Map<String, ActionAndMethod> actionMap;
         private Interceptor[] interceptors = null;
         private ExceptionResolver exceptionResolver = null;
         private ViewResolver viewResolver = null;     private Injector injector = null; // Guice IoC容器     ...
    } Guice的配置完全由Java 5注解完成,而在DispatcherServlet中,我们需要主动从容器中查找某种类型的Bean,相对于客户端被动地使用IoC容器(客户端甚至不能感觉到IoC容器的存在),DispatcherServlet需要使用ServiceLocator模式主动查找Bean,写一个通用方法: private List<Key<?>> findKeysByType(Injector inj, Class<?> type) {
         Map<Key<?>, Binding<?>> map = inj.getBindings();
         List<Key<?>> keyList = new ArrayList<Key<?>>();
         for(Key<?> key : map.keySet()) {
             Type t = key.getTypeLiteral().getType();
             if(t instanceof Class<?>) {
                 Class<?> clazz = (Class<?>) t;
                 if(type==null || type.isAssignableFrom(clazz)) {
                     keyList.add(key);
                 }
             }
         }
         return keyList;
    } DispatcherServlet初始化时就要首先初始化Guice IoC容器: public void init(ServletConfig config) throws ServletException {
         String moduleClass = config.getInitParameter("module");
         if(moduleClass==null || moduleClass.trim().equals(""))
             throw new ServletException("Cannot find init parameter in web.xml: <servlet>"
                     + "<servlet-name>?</servlet-name><servlet-class>"
                     + getClass().getName()
                     + "</servlet-class><init-param><param-name>module</param-name><param-value>"
                     + "put-your-config-module-full-class-name-here</param-value></init-param></servlet>");
         ServletContext context = config.getServletContext();
         // init guice:
         injector = Guice.createInjector(Stage.PRODUCTION, getConfigModule(moduleClass.trim(), context));
         ...
    } 然后,从IoC容器中查找Action和URL的映射关系: private Map<String, ActionAndMethod> getUrlMapping(List<Key<?>> actionKeys) {
         Map<String, ActionAndMethod> urlMapping = new HashMap<String, ActionAndMethod>();
         for(Key<?> key : actionKeys) {
             Object obj = safeInstantiate(key);
             if(obj==null)
                 continue;
             Class<Action> actionClass = (Class<Action>) obj.getClass();
             Annotation ann = key.getAnnotation();
             if(ann instanceof Named) {
                 Named named = (Named) ann;
                 String url = named.value();
                 if(url!=null)
                     url = url.trim();
                 if(!"".equals(url)) {
                     log.info("Bind action [" + actionClass.getName() + "] to URL: " + url);
                     // link url with this action:
                     urlMapping.put(url, new ActionAndMethod(key, actionClass));
                 }
                 else {
                     log.warn("Cannot bind action [" + actionClass.getName() + "] to *EMPTY* URL.");
                 }
             }
             else {
                 log.warn("Cannot bind action [" + actionClass.getName() + "] because no @Named annotation found in config module. Using: binder.bind(MyAction.class).annotatedWith(Names.named("/url"));");
             }
         }
         return urlMapping;
    } 我们假定客户端是以如下方式配置Action和URL映射的: public class MyModule implements Module {     public void configure(Binder binder) {
             // bind actions:
             binder.bind(Action.class)
                   .annotatedWith(Names.named("/start.do"))
                   .to(StartAction.class);
             binder.bind(Action.class)
                   .annotatedWith(Names.named("/register.do"))
                   .to(RegisterAction.class);
             binder.bind(Action.class)
                   .annotatedWith(Names.named("/signon.do"))
                   .to(SignonAction.class);
             ...
         }
    } 即通过Guice提供的一个注解Names.named()指定URL。当然还可以用其他方法,比如标注一个@Url注解可能更方便,下一个版本会加上。 Interceptor,ExceptionResolver和ViewResolver也是通过查找获得的。 下面讨论DispatcherServlet如何真正处理用户请求。第一步是根据URL查找对应的Action: String contextPath = request.getContextPath();
    String url = request.getRequestURI().substring(contextPath.length());
    if(log.isDebugEnabled())
         log.debug("Handle for URL: " + url);
    ActionAndMethod am = actionMap.get(url);
    if(am==null) {
         response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404 Not Found
         return;
    } 没找到Action就直接给个404 Not Found,找到了进行下一步,实例化一个Action并填充参数: // init ActionContext:
    HttpSession session = request.getSession();
    ServletContext context = session.getServletContext(); ActionContext.setActionContext(request, response, session, context); // 每次创建一个新的Action实例:
    Action action = (Action) injector.getInstance(am.getKey());
    // 把HttpServletRequest的参数自动绑定到Action的属性中:
    List<String> props = am.getProperties();
    for(String prop : props) {
         String value = request.getParameter(prop);
         if(value!=null) {
             am.invokeSetter(action, prop, value);
         }
    } 注意,为了提高速度,所有的set方法已经预先缓存了,因此避免每次请求都用反射重复查找Action的set方法。 然后要应用所有的Interceptor以便拦截Action: InterceptorChainImpl chains = new InterceptorChainImpl(interceptors);
    chains.doInterceptor(action);
    ModelAndView mv = chains.getModelAndView(); 实现InterceptorChain看上去复杂,其实就是一个简单的递归,大家看InterceptorChainImpl代码就知道了: package com.javaeedev.lightweight.mvc; /**
      * Used for holds an interceptor chain.
      *
      * @author Xuefeng
      */
    class InterceptorChainImpl implements InterceptorChain {     private final Interceptor[] interceptors;
         private int index = 0;
         private ModelAndView mv = null;     InterceptorChainImpl(Interceptor[] interceptors) {
             this.interceptors = interceptors;
         }     ModelAndView getModelAndView() {
             return mv;
         }     public void doInterceptor(Action action) throws Exception {
             if(index==interceptors.length)
                 // 所有的Interceptor都执行完毕:
                 mv = action.execute();
             else {
                 // 必须先更新index,再调用interceptors[index-1],否则是一个无限递归:
                 index++;
                 interceptors[index-1].intercept(action, this);
             }
         }
    } 把上面的代码用try ... catch包起来,就可以应用ExceptionResolver了。 如果得到了ModelAndView,最后一步就是渲染View了,这个过程极其简单: // render view:
    private void render(ModelAndView mv, HttpServletRequest reqest, HttpServletResponse response) throws ServletException, IOException {
         String view = mv.getView();
         if(view.startsWith("redirect:")) {
             // 重定向:
             String redirect = view.substring("redirect:".length());
             response.sendRedirect(redirect);
             return;
         }
         Map<String, Object> model = mv.getModel();
         if(viewResolver!=null)
             viewResolver.resolveView(view, model, reqest, response);
    } 最简单的JspViewResolver的实现如下: package com.javaeedev.lightweight.mvc.view; /**
      * Let JSP render the model returned by Action.
      *
      * @author Xuefeng
      */
    public class JspViewResolver implements ViewResolver {     /**
          * Init JspViewResolver.
          */
         public void init(ServletContext context) throws ServletException {
         }     /**
          * Render view using JSP.
          */
         public void resolveView(String view, Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
             if(model!=null) {
                 Set<String> keys = model.keySet();
                 for(String key : keys) {
                     request.setAttribute(key, model.get(key));
                 }
             }
             request.getRequestDispatcher(view).forward(request, response);
         }
    } 至此,MVC框架的核心已经完成。
      
       
         
         
          
          

            
          

            
          
         
       

      


    源码下载:http://file.javaxxz.com/2014/11/6/000229796.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 11:40 , Processed in 0.397856 second(s), 50 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表