自定义WEB MVC框架 三 自定义配置和响应处理

来源:互联网 发布:斗龙战士1玩具淘宝 编辑:程序博客网 时间:2024/06/05 02:35

主控制器-第三版

  今天要对主控制器做一个调整,前面的设计过于僵硬,将很多东西都写死了,这在编程里叫硬编码是极不推荐的,为了解决这个问题,我们有很多解决方案。比如说将一些参数或者固定字段做配置文件,然后从配置文件读,当然这样做也不是最完美的。
  这里我要提一个词——COC,在程序设计范畴内大名响当当,全名叫Convenient over Configuration,意思是约定优于配置,不知道大家能不能理解。如果我简单举一个例子,好比之前我的约定,我假定所有的Controller都在com.bubbling.test包下,那么所有人都按照这个约定来,所谓硬编码又有什么不好的呢?既效率又简单。
  然而很多时候约定不一定能被所有人遵守,总是会有些个性化的需求,既然我写的是一个WEB框架,我就要满足这些需求,并且在某些懒人存在的可能下,我还要提供一套默认的配置——即约定!
  那么今天就要把主控制器做一个修改,大概的思路就是如果开发者有自定义配置,就按用户配置来,如果没有就按默认配置来。这一次我们要做一个比较规范的约定了,约定如下:
  1.Controller默认的类名后缀为Ctrl,例:UserCtrl
  2.JSP和其他视图默认的路径为:”/WEB-INF/view”

package com.bubbling.framework.dispatcher;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebInitParam;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.bubbling.util.ReflectionUtil;/** * @author 胡楠 *  *         主控制器,做请求分发 * *///@WebServlet(name = "MainDispatcher", urlPatterns = "/", loadOnStartup = 1, initParams = {//        @WebInitParam(name = "ViewPrefix", value = "/WEB-INF/view/"),//        @WebInitParam(name = "ControllerSuffix", value = "Ctrl") })//@MultipartConfig@WebServlet(name = "MainDispatcher", urlPatterns = "/", loadOnStartup = 1, initParams = {        @WebInitParam(name = "ViewPrefix", value = "/WEB-INF/view/")})@MultipartConfigpublic class MainDispatcher extends HttpServlet {    private static final long serialVersionUID = 2885097167484409970L;    private static final String DEFAULT_VIEW_PREFIX = "/WEB-INF/view/de";    private static final String DEFAULT_CONTROLLER_SUFFIX = "CtrlDe";    /**     * 保存所有Controller映射     */    private static Map<String, String> requestMapping = new HashMap<String, String>();    private String rootName;    private String viewPrefix;    private String controllerSuffix;    /**     * 添加Controller映射     *      * @param controllerName     * @param className     */    public static void addMapping(String controllerName, String className) {        requestMapping.put(controllerName, className);    }    /**     * 从配置文件中读取配置参数,如果没有配置则按默认配置设定     *      * @param config     * @throws ServletException     */    @Override    public void init(ServletConfig config) throws ServletException {        String initParam = config.getInitParameter("ViewPrefix");        viewPrefix = initParam != null ? initParam : DEFAULT_VIEW_PREFIX;        initParam = config.getInitParameter("ControllerSuffix");        controllerSuffix = initParam != null ? initParam : DEFAULT_CONTROLLER_SUFFIX;        System.out.println(viewPrefix);        System.out.println(controllerSuffix);        rootName = config.getServletContext().getRealPath("/") + "WEB-INF\\classes\\";        ControllerBind.autoBind(rootName, "com.bubbling.test");    }    @SuppressWarnings({ "rawtypes", "unchecked" })    @Override    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {        String servletPath = req.getServletPath();        String controller = PathParser.getController(servletPath);        String action = PathParser.getAction(servletPath);        try {            Class clazz = Class.forName(requestMapping.get(controller));            Object obj = clazz.newInstance();            ReflectionUtil.setSuperValue(obj, "request", req);            ReflectionUtil.setSuperValue(obj, "response", res);            Method method = clazz.getMethod(action);            method.invoke(obj);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        }    }}

  仔细看注解的配置,这里的注解作用跟web.xml中配置Servlet是一样的,如果有朋友不知道这些注解的作用,我建议你先去百度下Servlet3.0后新增的注解。而且我为了方便演示,仅在注解配置中填写一个参数,另一个看看是不是能通过默认配置得到,启动服务,看一下控制台:
这里写图片描述

  很好,这就是我们想要的结果,如果有配置,优先读取配置信息,如果没有配置则按默认配置设定,这里就能满足用户的需求了。在测试完成后,我会将注释掉的注解恢复,以便后续使用。


处理请求响应

  目前为止基础设施已经比较完备了,接下来继续完成设计,目前要做的是如何相应请求,设计之前需要搞清楚几个事情,请求过来之后,我们已经可以找到处理类——Controller,那么Controller的内部逻辑我们不管,也无从知晓,可是当主控制器拿到Controller的处理结果之后呢?
  随之而来两个问题:
  1.Controller处理结果模型
  2.主控制器后续处理控制
  先解决掉第一个问题,这需要定义要给处理结果模型,这个模型必须要告诉控制器它的处理结果,和请求主控制器后续执行的动作,定义如下:

package com.bubbling.framework.action.resutl;import com.google.gson.Gson;/** * @author 胡楠 *  *         请求处理结果,允许用户继承该类,并且所有set方法均返回自身,方便用户快速创建该对象,  *         快速创建过程: new Result().set().set().set()... * */public class Result {    /**     * 如果需要主控制器协助重定向或者转发则url有效     */    protected String url;    /**     * 处理结果类型     */    protected ResultType type;    /**     * 处理结果数据     */    protected Object data;    public Result() {        this.data = null;        this.type = ResultType.Forward;    }    public String getUrl() {        return url;    }    public Result setUrl(String url) {        this.url = url;        return this;    }    public ResultType getType() {        return type;    }    public Result setType(ResultType type) {        this.type = type;        return this;    }    public Object getData() {        return data;    }    public Result setData(Object data) {        this.data = data;        return this;    }    @Override    public String toString() {        return "Result [url=" + url + ", type=" + type + ", data=" + data + "]";    }    /**     * 将data转换为json格式     */    public void toJson() {        data = new Gson().toJson(data);    }    /**     * 允许将其他数据转换成json格式,并且当参数为null的时候,默认将data转换为json格式     *      * @param obj     * @return     */    public String toJson(Object obj) {        if (obj == null) {            obj = data;        }        return new Gson().toJson(obj);    }}

  这里我用到了很多有意思的设计,比如所set方法,如果将自身返回,那么用户就可以通过不断的set进行拼接,有没有感觉很像jquery?这些小细节都是以后写程序的时候很有帮助的。这里用的Gson需要添加Google的Gson包,有兴趣的朋友可自行百度。


主控制器-第四版本

  Ok,继续改写主控制器,因为已经做了响应结果,那么主控制器在拿到响应结果之后,需要根据结果类型,做相应的处理,如下:

package com.bubbling.framework.dispatcher;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebInitParam;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.bubbling.framework.action.result.Result;import com.bubbling.framework.action.result.ResultType;import com.bubbling.util.ReflectionUtil;/** * @author 胡楠 *  *         主控制器,做请求分发 * */@WebServlet(name = "MainDispatcher", urlPatterns = "/", loadOnStartup = 1, initParams = {        @WebInitParam(name = "ViewPrefix", value = "/WEB-INF/view/"),        @WebInitParam(name = "ControllerSuffix", value = "Ctrl"),        @WebInitParam(name = "ControllerPath", value = "com.bubbling.test")})@MultipartConfigpublic class MainDispatcher extends HttpServlet {    private static final long serialVersionUID = 2885097167484409970L;    private static final String DEFAULT_VIEW_PREFIX = "/WEB-INF/view/";    private static final String DEFAULT_CONTROLLER_SUFFIX = "Ctrl";    private static final String DEFAULT_CONTROLLER_PATH = "com.bubbling.controller";    private static final String DEFAULT_ROOTURL = "http://localhost:8080/BubblingWEB";    /**     * 保存所有Controller映射     */    private static Map<String, String> requestMapping = new HashMap<String, String>();    private String rootName;    private String contextPath;    private String viewPrefix;    private String ctrlSuffix;    private String ctrlPath;    /**     * 添加Controller映射     *      * @param controllerName     * @param className     */    public static void addMapping(String controllerName, String className) {        requestMapping.put(controllerName, className);    }    /**     * 从配置文件中读取配置参数,如果没有配置则按默认配置设定     *      * @param config     * @throws ServletException     */    @Override    public void init(ServletConfig config) throws ServletException {        String initParam = config.getInitParameter("ViewPrefix");        viewPrefix = initParam != null ? initParam : DEFAULT_VIEW_PREFIX;        initParam = config.getInitParameter("ControllerSuffix");        ctrlSuffix = initParam != null ? initParam : DEFAULT_CONTROLLER_SUFFIX;        initParam = config.getInitParameter("ControllerPath");        ctrlPath = initParam != null ? initParam : DEFAULT_CONTROLLER_PATH;        contextPath = config.getServletContext().getRealPath("/");        rootName = contextPath + "WEB-INF\\classes\\";        ControllerBind.autoBind(rootName, ctrlPath);    }    @SuppressWarnings({ "rawtypes", "unchecked" })    @Override    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {        String servletPath = req.getServletPath();        String controller = PathParser.getController(servletPath);        String action = PathParser.getAction(servletPath);        try {            Class clazz = Class.forName(requestMapping.get(controller));            Object obj = clazz.newInstance();            ReflectionUtil.setSuperValue(obj, "request", req);            ReflectionUtil.setSuperValue(obj, "response", res);            Method method = clazz.getMethod(action);            Object invokeRet = method.invoke(obj);            if(invokeRet != null) {                if(invokeRet instanceof Result) {                    ResultType type = ((Result)invokeRet).getType();                    switch (type) {                        case Forward: // 跳转                            System.out.println(DEFAULT_ROOTURL + ((Result) invokeRet).getUrl());                            req.getRequestDispatcher(((Result) invokeRet).getUrl()).forward(req, res);                            break;//                        case Redirect: // 重定向//                            resp.sendRedirect(resultContent.getUrl());//                            break;//                        case Ajax: // Ajax//                            PrintWriter pw = resp.getWriter();//                            pw.println(resultContent.getJson());//                            pw.close();//                            break;//                        case Chain://                            req.getRequestDispatcher(contextPath + resultContent.getUrl()).forward(req, resp);//                            break;//                        case RedirectChain://                            resp.sendRedirect(contextPath + resultContent.getUrl());//                            break;                        default:                    }                }            }        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        }    }}

  先放到这里,要吃午饭了,目前为止方向是明确的,但是仅仅实现了转发功能,而且逻辑不够严谨,没有实现具体调用jsp等功能,仅仅是调用了Controller的方法,这些会面会逐步的修补上,那么编写测试类:

package com.bubbling.test;import java.io.IOException;import java.io.PrintWriter;import com.bubbling.framework.action.BaseController;import com.bubbling.framework.action.annotation.ActionMapping;import com.bubbling.framework.action.annotation.ControllerMapping;import com.bubbling.framework.action.result.Result;import com.bubbling.framework.action.result.ResultType;@ControllerMapping("mytest")public class MyTestController extends BaseController {    @ActionMapping(action = "test")    public Result test() {        try {            PrintWriter pw = response.getWriter();            pw.println("successful");            return new Result().setType(ResultType.Forward).setUrl("/mytest/testForward");        } catch (IOException e) {            e.printStackTrace();        }        return null;    }    @ActionMapping(action = "testForward")    public void testForward() {        try {            PrintWriter pw = response.getWriter();            pw.println("forward successful");        } catch (IOException e) {            e.printStackTrace();        }    }    @Override    public String toString() {        return "MyTestController [request=" + request + ", response=" + response + "]";    }}

  测试的方向是通过调用test来转发到testForward,看看测试结果:

这里写图片描述

  下午有时间会把这些细节完善起来,感觉这个是坑啊,没有尽头呢……

0 0