在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。java
优化的目标是1.去除DispatcherServlet
请求分发器中的http逻辑代码;2.将ControllerHandler
和ResultRender
中代码按功能细分出来,使其各司其职。git
DispatcherServlet
先在com.zbw.mvc包下建立两个包handler和render,分别用于放ControllerHandler
和ResultRender
拆分出来的功能类。github
再在这两个包下建立两个接口,以便后面的功能类都按这个接口规范。web
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
,这个类是整个请求的执行链,用于存储整个请求须要保存的一些属性和串联整个请求。spring
在com.zbw.mvc下建立这个类json
package com.zbw.mvc; import ... /** * http请求处理链 */ @Data @Slf4j public class RequestHandlerChain { /** * Handler迭代器 */ private Iterator<Handler> 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<Handler> 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
。tomcat
doHandlerChain()
方法就会迭代执行handlerIt
中的Handler的handle()
方法,而且会根据每一个Handler返回的值来判断是否继续往下执行下一个Handler。服务器
doRender()
方法用于调用Render中的render()
方法。mvc
DispatcherServlet
接下来就能够修改DispatcherServlet
请求转发器了。app
package com.zbw.mvc; import ... /** * DispatcherServlet 全部http请求都由此Servlet转发 */ @Slf4j public class DispatcherServlet extends HttpServlet { /** * 请求执行链 */ private final List<Handler> 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的实现类。这些实现类只实现了简单的一些http请求的功能,你们能够本身根据状况开发更多的实现类。
首先是PreRequestHandler
,用于预处理http的一些信息,好比设置http编码,处理请求url,打印一些信息等。
package com.zbw.mvc.handler; import ... /** * 请求预处理 */ @Slf4j public 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
,用于处理静态资源,当碰到资源是静态资源时就直接转发请求到Tomcat默认的servlet去。
package com.zbw.mvc.handler; import ... /** * 普通url请求执行 * 主要处理静态资源 */ @Slf4j public 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()); } }
而后是处理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
,这个和从零开始实现一个简易的Java MVC框架(七)--实现MVC中的ControllerHandler功能同样,用于处理请求中数据和controller对应的关系。
package com.zbw.mvc.handler; import ... /** * Controller请求处理 */ @Slf4j public class ControllerHandler implements Handler { /** * 请求信息和controller信息关系map */ private Map<PathInfo, ControllerInfo> pathControllerMap = new ConcurrentHashMap<>(); /** * bean容器 */ private BeanContainer beanContainer; public ControllerHandler() { beanContainer = BeanContainer.getInstance(); Set<Class<?>> 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<String, String> requestParams = getRequestParams(request); List<Object> 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<Class<?>> 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<String, Class<?>> 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<String, Class<?>> getMethodParams(Method method) { Map<String, Class<?>> 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<String, String> getRequestParams(HttpServletRequest request) { Map<String, String> 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<Object> instantiateMethodArgs(Map<String, Class<?>> methodParams, Map<String, String> 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()); } }
TomcatServer
的多余代码刚才实现的几个HANDLER还须要初始化,就在DispatcherServlet
的init()
方法中初始化。注意初始化的顺序会决定其在RequestHandlerChain
执行链中执行的前后。
... @Slf4j public 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()); } ... }
而后去除TomcatServer
中JspServlet
和DefaultServlet
两个servlet的初始化,由于已经在 JspHandler
和SimpleUrlHandler
中初始化了这两个servlet。
... @Slf4j public 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,设置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); } }
这个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); } }
这个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); } }
这个Render返回json数据,当Handler请求发现返回数据为json格式时,就用这个Render
package com.zbw.mvc.render; import ... /** * 渲染json */ @Slf4j public 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(); } } }
这个Render跳转到页面,将ModelAndView
中的信息存到HttpServletRequest中并跳转到对应页面
package com.zbw.mvc.render; import ... /** * 渲染页面 */ @Slf4j public 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<String, Object> model = mv.getModel(); model.forEach(req::setAttribute); req.getRequestDispatcher(Doodle.getConfiguration().getViewPath() + path).forward(req, resp); } }
至此,MVC的优化完成了,同时整个doodle框架的代码也算是完成了。
虽然doodle早已完成,可是讲解的文章托托延延到如今才完成。
在刚完成doodle时感受整个框架已经成型了,可是在写这个系列文章的过程当中才真正发现欠缺的还有很是很是多,甚至以为把它称为框架都有些抬举它了呢。
只能说在实现它而后再写这个系列的文章以后对spring的崇拜之心更加深了,其被javaer普遍使用和拜读果真是有缘由的。
另外也感谢你们阅读这个系列的文章,若是对你们有所帮助的话能够去给个人项目加个star,有什么问题和建议也能够提出来交流交流。
这个系列的全部文章我都放在个人博客上了:http://zzzzbw.cn/
- 从零开始实现一个简易的Java MVC框架(一)--前言
- 从零开始实现一个简易的Java MVC框架(二)--实现Bean容器
- 从零开始实现一个简易的Java MVC框架(三)--实现IOC
- 从零开始实现一个简易的Java MVC框架(四)--实现AOP
- 从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点
- 从零开始实现一个简易的Java MVC框架(六)--增强AOP功能
- 从零开始实现一个简易的Java MVC框架(七)--实现MVC
- 从零开始实现一个简易的Java MVC框架(八)--制做Starter
- 从零开始实现一个简易的Java MVC框架(九)--优化MVC代码
源码地址:doodle