在從零開始實現一個簡易的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