博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零开始实现一个简易的Java MVC框架(九)--优化MVC代码
阅读量:6458 次
发布时间:2019-06-23

本文共 16896 字,大约阅读时间需要 56 分钟。

前言

在中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。

优化的目标是1.去除DispatcherServlet请求分发器中的http逻辑代码;2.将ControllerHandlerResultRender中代码按功能细分出来,使其各司其职。

修改DispatcherServlet

创建接口

先在com.zbw.mvc包下创建两个包handler和render,分别用于放ControllerHandlerResultRender拆分出来的功能类。

再在这两个包下创建两个接口,以便后面的功能类都按这个接口规范。

package com.zbw.mvc.handler;import com.zbw.mvc.RequestHandlerChain;/** * 请求执行器 Handler */public interface Handler {    /**     * 请求的执行器     */    boolean handle(final RequestHandlerChain handlerChain) throws Exception;}复制代码
package com.zbw.mvc.render;import com.zbw.mvc.RequestHandlerChain;/** * 渲染请求结果 interface */public interface Render {    /**     * 执行渲染     */    void render(RequestHandlerChain handlerChain) throws Exception;}复制代码

实现RequestHandlerChain

上面两个接口都有个参数RequestHandlerChain,这个类是整个请求的执行链,用于存储整个请求需要保存的一些属性和串联整个请求。

在com.zbw.mvc下创建这个类

package com.zbw.mvc;import .../** * http请求处理链 */@Data@Slf4jpublic class RequestHandlerChain {    /**     * Handler迭代器     */    private Iterator
handlerIt; /** * 请求request */ private HttpServletRequest request; /** * 请求response */ private HttpServletResponse response; /** * 请求http方法 */ private String requestMethod; /** * 请求http路径 */ private String requestPath; /** * 请求状态码 */ private int responseStatus; /** * 请求结果处理器 */ private Render render; public RequestHandlerChain(Iterator
handlerIt, HttpServletRequest request, HttpServletResponse response) { this.handlerIt = handlerIt; this.request = request; this.response = response; this.requestMethod = request.getMethod(); this.requestPath = request.getPathInfo(); this.responseStatus = HttpServletResponse.SC_OK; } /** * 执行请求链 */ public void doHandlerChain() { try { while (handlerIt.hasNext()) { if (!handlerIt.next().handle(this)) { break; } } } catch (Exception e) { log.error("doHandlerChain error", e); render = new InternalErrorRender(); } } /** * 执行处理器 */ public void doRender() { if (null == render) { render = new DefaultRender(); } try { render.render(this); } catch (Exception e) { log.error("doRender", e); throw new RuntimeException(e); } }}复制代码

在这个类中除了存储http请求信息以外,还有Handler迭代器handlerIt和请求结果处理器Render

doHandlerChain()方法就会迭代执行handlerIt中的Handler的handle()方法,并且会根据每个Handler返回的值来判断是否继续往下执行下一个Handler。

doRender()方法用于调用Render中的render()方法。

更改DispatcherServlet

接下来就可以修改DispatcherServlet请求转发器了。

package com.zbw.mvc;import .../** * DispatcherServlet 所有http请求都由此Servlet转发 */@Slf4jpublic class DispatcherServlet extends HttpServlet {    /**     * 请求执行链     */    private final List
HANDLER = new ArrayList<>(); /** * 执行请求 */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { RequestHandlerChain handlerChain = new RequestHandlerChain(HANDLER.iterator(), req, resp); handlerChain.doHandlerChain(); handlerChain.doRender(); }}复制代码

可以看到现在DispatcherServlet已经很简洁了,把请求的逻辑代码交给RequestHandlerChain处理,自己没有多余的http逻辑代码。

实现几种Handler

上面只创建了Handler的接口没有实现类,现在就实现几个Handler的实现类。这些实现类只实现了简单的一些http请求的功能,大家可以自己根据情况开发更多的实现类。

PreRequestHandler

首先是PreRequestHandler,用于预处理http的一些信息,比如设置http编码,处理请求url,打印一些信息等。

package com.zbw.mvc.handler;import .../** * 请求预处理 */@Slf4jpublic class PreRequestHandler implements Handler {    @Override    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {        // 设置请求编码方式        handlerChain.getRequest().setCharacterEncoding("UTF-8");        String requestPath = handlerChain.getRequestPath();        if (requestPath.length() > 1 && requestPath.endsWith("/")) {            handlerChain.setRequestPath(requestPath.substring(0, requestPath.length() - 1));        }        log.info("[Doodle] {} {}", handlerChain.getRequestMethod(), handlerChain.getRequestPath());        return true;    }}复制代码

SimpleUrlHandler

接下来是SimpleUrlHandler,用于处理静态资源,当碰到资源是静态资源时就直接转发请求到Tomcat默认的servlet去。

package com.zbw.mvc.handler;import .../** * 普通url请求执行 * 主要处理静态资源 */@Slf4jpublic class SimpleUrlHandler implements Handler {    /**     * tomcat默认RequestDispatcher的名称     * TODO: 其他服务器默认的RequestDispatcher.如WebLogic为FileServlet     */    private static final String TOMCAT_DEFAULT_SERVLET = "default";    /**     * 默认的RequestDispatcher,处理静态资源     */    private RequestDispatcher defaultServlet;    public SimpleUrlHandler(ServletContext servletContext) {        defaultServlet = servletContext.getNamedDispatcher(TOMCAT_DEFAULT_SERVLET);        if (null == defaultServlet) {            throw new RuntimeException("没有默认的Servlet");        }        log.info("The default servlet for serving static resource is [{}]", TOMCAT_DEFAULT_SERVLET);    }    @Override    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {        if (isStaticResource(handlerChain.getRequestPath())) {            defaultServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());            return false;        }        return true;    }    /**     * 是否为静态资源     */    private boolean isStaticResource(String url) {        return url.startsWith(Doodle.getConfiguration().getAssetPath());    }}复制代码

JspHandler

然后是处理jsp页面的实现类JspHandler,当碰到资源是jsp页面时就直接转发请求到Tomcat的jsp的servlet去。

package com.zbw.mvc.handler;import .../** * jsp请求处理 * 主要负责jsp资源请求 */public class JspHandler implements Handler {    /**     * jsp请求的RequestDispatcher的名称     */    private static final String JSP_SERVLET = "jsp";    /**     * jsp的RequestDispatcher,处理jsp资源     */    private RequestDispatcher jspServlet;    public JspHandler(ServletContext servletContext) {        jspServlet = servletContext.getNamedDispatcher(JSP_SERVLET);        if (null == jspServlet) {            throw new RuntimeException("没有jsp Servlet");        }    }    @Override    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {        if (isPageView(handlerChain.getRequestPath())) {            jspServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());            return false;        }        return true;    }    /**     * 是否为jsp资源     */    private boolean isPageView(String url) {        return url.startsWith(Doodle.getConfiguration().getViewPath());    }}复制代码

ControllerHandler

最后就是ControllerHandler,这个和中的ControllerHandler功能一样,用于处理请求中数据和controller对应的关系。

package com.zbw.mvc.handler;import .../** * Controller请求处理 */@Slf4jpublic class ControllerHandler implements Handler {    /**     * 请求信息和controller信息关系map     */    private Map
pathControllerMap = new ConcurrentHashMap<>(); /** * bean容器 */ private BeanContainer beanContainer; public ControllerHandler() { beanContainer = BeanContainer.getInstance(); Set
> mappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class); this.initPathControllerMap(mappingSet); } @Override public boolean handle(final RequestHandlerChain handlerChain) throws Exception { String method = handlerChain.getRequestMethod(); String path = handlerChain.getRequestPath(); ControllerInfo controllerInfo = pathControllerMap.get(new PathInfo(method, path)); if (null == controllerInfo) { handlerChain.setRender(new NotFoundRender()); return false; } Object result = invokeController(controllerInfo, handlerChain.getRequest()); setRender(result, controllerInfo, handlerChain); return true; } /** * 执行controller方法 */ private Object invokeController(ControllerInfo controllerInfo, HttpServletRequest request) { Map
requestParams = getRequestParams(request); List
methodParams = instantiateMethodArgs(controllerInfo.getMethodParameter(), requestParams); Object controller = beanContainer.getBean(controllerInfo.getControllerClass()); Method invokeMethod = controllerInfo.getInvokeMethod(); invokeMethod.setAccessible(true); Object result; try { if (methodParams.size() == 0) { result = invokeMethod.invoke(controller); } else { result = invokeMethod.invoke(controller, methodParams.toArray()); } } catch (Exception e) { throw new RuntimeException(e); } return result; } /** * 设置请求结果执行器 */ private void setRender(Object result, ControllerInfo controllerInfo, RequestHandlerChain handlerChain) { if (null == result) { return; } Render render; boolean isJson = controllerInfo.getInvokeMethod().isAnnotationPresent(ResponseBody.class); if (isJson) { render = new JsonRender(result); } else { render = new ViewRender(result); } handlerChain.setRender(render); } /** * 初始化pathControllerMap */ private void initPathControllerMap(Set
> mappingSet) { mappingSet.forEach(this::addPathController); } /** * 添加controllerInfo到pathControllerMap中 */ private void addPathController(Class
clz) { RequestMapping requestMapping = clz.getAnnotation(RequestMapping.class); String basePath = requestMapping.value(); if (!basePath.startsWith("/")) { basePath = "/" + basePath; } for (Method method : clz.getDeclaredMethods()) { if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping methodRequest = method.getAnnotation(RequestMapping.class); String methodPath = methodRequest.value(); if (!methodPath.startsWith("/")) { methodPath = "/" + methodPath; } String url = basePath + methodPath; Map
> methodParams = this.getMethodParams(method); String httpMethod = String.valueOf(methodRequest.method()); PathInfo pathInfo = new PathInfo(httpMethod, url); if (pathControllerMap.containsKey(pathInfo)) { log.warn("url:{} 重复注册", pathInfo.getHttpPath()); } ControllerInfo controllerInfo = new ControllerInfo(clz, method, methodParams); this.pathControllerMap.put(pathInfo, controllerInfo); log.info("mapped:[{},method=[{}]] controller:[{}@{}]", pathInfo.getHttpPath(), pathInfo.getHttpMethod(), controllerInfo.getControllerClass().getName(), controllerInfo.getInvokeMethod().getName()); } } } /** * 获取执行方法的参数 */ private Map
> getMethodParams(Method method) { Map
> map = new HashMap<>(); for (Parameter parameter : method.getParameters()) { RequestParam param = parameter.getAnnotation(RequestParam.class); // TODO: 不使用注解匹配参数名字 if (null == param) { throw new RuntimeException("必须有RequestParam指定的参数名"); } map.put(param.value(), parameter.getType()); } return map; } /** * 获取HttpServletRequest中的参数 */ private Map
getRequestParams(HttpServletRequest request) { Map
paramMap = new HashMap<>(); //GET和POST方法是这样获取请求参数的 request.getParameterMap().forEach((paramName, paramsValues) -> { if (ValidateUtil.isNotEmpty(paramsValues)) { paramMap.put(paramName, paramsValues[0]); } }); // TODO: Body、Path、Header等方式的请求参数获取 return paramMap; } /** * 实例化方法参数 */ private List
instantiateMethodArgs(Map
> methodParams, Map
requestParams) { return methodParams.keySet().stream().map(paramName -> { Class
type = methodParams.get(paramName); String requestValue = requestParams.get(paramName); Object value; if (null == requestValue) { value = CastUtil.primitiveNull(type); } else { value = CastUtil.convert(type, requestValue); // TODO: 实现非原生类的参数实例化 } return value; }).collect(Collectors.toList()); }}复制代码

初始化HANDLER列表和去除TomcatServer的多余代码

刚才实现的几个HANDLER还需要初始化,就在DispatcherServletinit()方法中初始化。注意初始化的顺序会决定其在RequestHandlerChain执行链中执行的先后。

...@Slf4jpublic class DispatcherServlet extends HttpServlet {	...    /**     * 初始化Servlet     */    @Override    public void init() throws ServletException {        HANDLER.add(new PreRequestHandler());        HANDLER.add(new SimpleUrlHandler(getServletContext()));        HANDLER.add(new JspHandler(getServletContext()));        HANDLER.add(new ControllerHandler());    }	...}复制代码

然后去除TomcatServerJspServletDefaultServlet两个servlet的初始化,因为已经在 JspHandlerSimpleUrlHandler中初始化了这两个servlet。

...@Slf4jpublic class TomcatServer implements Server {	...    public TomcatServer(Configuration configuration) {        try {            this.tomcat = new Tomcat();            tomcat.setBaseDir(configuration.getDocBase());            tomcat.setPort(configuration.getServerPort());            File root = getRootFolder();            File webContentFolder = new File(root.getAbsolutePath(), configuration.getResourcePath());            if (!webContentFolder.exists()) {                webContentFolder = Files.createTempDirectory("default-doc-base").toFile();            }            log.info("Tomcat:configuring app with basedir: [{}]", webContentFolder.getAbsolutePath());            StandardContext ctx = (StandardContext) tomcat.addWebapp(configuration.getContextPath(), webContentFolder.getAbsolutePath());            ctx.setParentClassLoader(this.getClass().getClassLoader());            WebResourceRoot resources = new StandardRoot(ctx);            ctx.setResources(resources);			            // 去除了JspHandler和SimpleUrlHandler这两个servlet的注册                        tomcat.addServlet(configuration.getContextPath(), "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0);            ctx.addServletMappingDecoded("/*", "dispatcherServlet");        } catch (Exception e) {            log.error("初始化Tomcat失败", e);            throw new RuntimeException(e);        }    }}复制代码

实现几种Render

上面创建的Render接口也需要一些实现类。同样的,这些Render也只是实现基本的功能 ,大家可以自己根据情况开发更多。

DefaultRender

这个是默认的Render,设置HttpServletResponse中的status为RequestHandlerChain中StatusCode。

package com.zbw.mvc.render;import .../** * 默认渲染 200 */public class DefaultRender implements Render {    @Override    public void render(RequestHandlerChain handlerChain) throws Exception {        int status = handlerChain.getResponseStatus();        handlerChain.getResponse().setStatus(status);    }}复制代码

InternalErrorRender

这个Render返回StatusCode为500

package com.zbw.mvc.render;import .../** * 渲染500 */public class InternalErrorRender implements Render {    @Override    public void render(RequestHandlerChain handlerChain) throws Exception {		handlerChain.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);    }}复制代码

NotFoundRender

这个Render返回StatusCode为404

package com.zbw.mvc.render;import .../** * 渲染404 */public class NotFoundRender implements Render {    @Override    public void render(RequestHandlerChain handlerChain) throws Exception {        handlerChain.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND);    }}复制代码

JsonRender

这个Render返回json数据,当Handler请求发现返回数据为json格式时,就用这个Render

package com.zbw.mvc.render;import .../** * 渲染json */@Slf4jpublic class JsonRender implements Render {    private Object jsonData;    public JsonRender(Object jsonData) {        this.jsonData = jsonData;    }    @Override    public void render(RequestHandlerChain handlerChain) throws Exception {        // 设置响应头        handlerChain.getResponse().setContentType("application/json");        handlerChain.getResponse().setCharacterEncoding("UTF-8");        // 向响应中写入数据        try (PrintWriter writer = handlerChain.getResponse().getWriter()) {            writer.write(JSON.toJSONString(jsonData));            writer.flush();        }    }}复制代码

ViewRender

这个Render跳转到页面,将ModelAndView中的信息存到HttpServletRequest中并跳转到对应页面

package com.zbw.mvc.render;import .../** * 渲染页面 */@Slf4jpublic class ViewRender implements Render {    private ModelAndView mv;    public ViewRender(Object mv) {        if (mv instanceof ModelAndView) {            this.mv = (ModelAndView) mv;        } else if (mv instanceof String) {            this.mv = new ModelAndView().setView((String) mv);        } else {            throw new RuntimeException("返回类型不合法");        }    }    @Override    public void render(RequestHandlerChain handlerChain) throws Exception {        HttpServletRequest req = handlerChain.getRequest();        HttpServletResponse resp = handlerChain.getResponse();        String path = mv.getView();        Map
model = mv.getModel(); model.forEach(req::setAttribute); req.getRequestDispatcher(Doodle.getConfiguration().getViewPath() + path).forward(req, resp); }}复制代码

结语

至此,MVC的优化完成了,同时整个框架的代码也算是完成了。

虽然doodle早已完成,但是讲解的文章托托延延到现在才完成。

在刚完成doodle时感觉整个框架已经成型了,但是在写这个系列文章的过程中才真正发现欠缺的还有非常非常多,甚至觉得把它称为框架都有些抬举它了呢。

只能说在实现它然后再写这个系列的文章之后对spring的崇拜之心更加深了,其被javaer广泛使用和拜读果然是有原因的。

另外也感谢大家阅读这个系列的文章,如果对大家有所帮助的话可以去给我的项目加个star,有什么问题和建议也可以提出来交流交流。

这个系列的所有文章我都放在我的博客上了:


源码地址:

原文地址:

转载地址:http://oiizo.baihongyu.com/

你可能感兴趣的文章
Spring <context:annotation-config/> 说明
查看>>
lua
查看>>
Java排序算法(四):Shell排序
查看>>
poj_3468 伸展树
查看>>
Linux ag命令
查看>>
mysql实用教程的数据构造
查看>>
Web API应用架构在Winform混合框架中的应用(5)--系统级别字典和公司级别字典并存的处理方式...
查看>>
【BZOJ】3495: PA2010 Riddle
查看>>
windows执行命令来运行loadrunner录制好的脚本(收藏)
查看>>
Linux automake命令
查看>>
Linux使用jstat命令查看jvm的GC情况
查看>>
CareerCup All in One 题目汇总
查看>>
xmind 使用备忘
查看>>
Atitit.java jar hell解决方案-----Djava.ext.dirs in ide envi..
查看>>
Java_一致性哈希算法与Java实现
查看>>
某学院软件工程复试回忆总结
查看>>
英译中批量翻译
查看>>
比较sql server两个数据库
查看>>
《Lua程序设计》第7章 迭代器与泛型for 学习笔记
查看>>
效率问题
查看>>