摘要:前言在從零開始實現一個簡易的框架七實現中實現了框架的的功能,不過最后指出代碼的邏輯不是很好,在這一章節就將這一部分代碼進行優化。
前言
在從零開始實現一個簡易的Java MVC框架(七)--實現MVC中實現了doodle框架的MVC的功能,不過最后指出代碼的邏輯不是很好,在這一章節就將這一部分代碼進行優化。
優化的目標是1.去除DispatcherServlet請求分發器中的http邏輯代碼;2.將ControllerHandler和ResultRender中代碼按功能細分出來,使其各司其職。
修改DispatcherServlet 創建接口先在com.zbw.mvc包下創建兩個包handler和render,分別用于放ControllerHandler和ResultRender拆分出來的功能類。
再在這兩個包下創建兩個接口,以便后面的功能類都按這個接口規范。
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 @Slf4j public class RequestHandlerChain { /** * Handler迭代器 */ private IteratorhandlerIt; /** * 請求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轉發 */ @Slf4j public class DispatcherServlet extends HttpServlet { /** * 請求執行鏈 */ private final ListHANDLER = 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 ... /** * 請求預處理 */ @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
接下來是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()); } }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,這個和從零開始實現一個簡易的Java MVC框架(七)--實現MVC中的ControllerHandler功能一樣,用于處理請求中數據和controller對應的關系。
package com.zbw.mvc.handler; import ... /** * Controller請求處理 */ @Slf4j public class ControllerHandler implements Handler { /** * 請求信息和controller信息關系map */ private Map初始化HANDLER列表和去除TomcatServer的多余代碼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
剛才實現的幾個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也只是實現基本的功能 ,大家可以自己根據情況開發更多。
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 */ @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(); } } }ViewRender
這個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結語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
原文地址:從零開始實現一個簡易的Java MVC框架(九)--優化MVC代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76715.html
摘要:不過仔細了解了一段時候發現,其實他的原理是很簡單的,所以想要自己也動手實現一個功能類似的框架。原文地址從零開始實現一個簡易的框架 前言 最近在看spring-boot框架的源碼,看了源碼之后更是讓我感受到了spring-boot功能的強大。而且使用了很多的設計模式,讓人在看的時候覺得有點難以下手。 不過仔細了解了一段時候發現,其實他的原理是很簡單的,所以想要自己也動手實現一個功能類似的...
摘要:接下來就可以把這個切點類加入到我們之前實現的功能中了。實現的切點功能首先改裝注解,把之前改成來存儲表達式。測試用例在上一篇文章從零開始實現一個簡易的框架四實現中的測試用例的基礎上修改測試用例。 前言 在上一節從零開始實現一個簡易的Java MVC框架(四)--實現AOP中我們實現了AOP的功能,已經可以生成對應的代理類了,但是對于代理對象的選擇只能通過指定的類,這樣確實不方便也不合理。...
摘要:服務器相關配置啟動類資源目錄目錄靜態文件目錄端口號目錄目錄實現內嵌服務器在上一章文章從零開始實現一個簡易的框架七實現已經在文件中引入了依賴,所以這里就不用引用了。 spring-boot的Starter 一個項目總是要有一個啟動的地方,當項目部署在tomcat中的時候,經常就會用tomcat的startup.sh(startup.bat)的啟動腳本來啟動web項目 而在spring-b...
摘要:在前面的文章中實現的功能時,目標類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。改裝原有功能現在要改裝原來的的實現代碼,讓的功能加入到框架中為了讓切面能夠排序,先添加一個注解,用于標記排序。 前言 在前面從零開始實現一個簡易的Java MVC框架(四)--實現AOP和從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點這兩節文章...
摘要:容器實際上就是存放所有的地方,即以及相關信息對應其實體的容器,為什么稱之為呢,因為在中,定義信息和實例的東西叫。了解到這個以后接下來就可以開始編寫容器了,在包下創建一個類叫。獲取容器實例至此,這個容器就完成了。 項目準備 首先確保你擁有以下環境或者工具 idea java 8 maven 3.3.X lombok插件 然后我們創建一個maven工程,編寫pom.xml引入一些需要的...
閱讀 1583·2021-10-18 13:35
閱讀 2362·2021-10-09 09:44
閱讀 816·2021-10-08 10:05
閱讀 2718·2021-09-26 09:47
閱讀 3564·2021-09-22 15:22
閱讀 432·2019-08-29 12:24
閱讀 1999·2019-08-29 11:06
閱讀 2857·2019-08-26 12:23