摘要:服務器相關配置啟動類資源目錄目錄靜態(tài)文件目錄端口號目錄目錄實現(xiàn)內嵌服務器在上一章文章從零開始實現(xiàn)一個簡易的框架七實現(xiàn)已經(jīng)在文件中引入了依賴,所以這里就不用引用了。
spring-boot的Starter
一個項目總是要有一個啟動的地方,當項目部署在tomcat中的時候,經(jīng)常就會用tomcat的startup.sh(startup.bat)的啟動腳本來啟動web項目
而在spring-boot的web項目中基本會有類似于這樣子的啟動代碼:
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
這個方法實際上會調用spring-boot的SpringApplication類的一個run方法:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; CollectionexceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // 1.加載環(huán)境變量、參數(shù)等 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); // 2.加載Bean(IOC、AOP)等 context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //會調用一個AbstractApplicationContext@refresh()方法,主要就是在這里加載Bean,方法的最后還會啟動服務器 refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
這段代碼還是比較長的,不過實際上主要就做了兩個事情:1.加載環(huán)境變量、參數(shù)等 2.加載Bean(IOC、AOP)等。3.如果獲得的ApplicationContext為ServletWebServerApplicationContext,那么在refresh()之后會啟動服務器,默認的就是tomcat服務器。
我覺得spring-boot啟動器算是spring-boot中相對來說代碼清晰易懂的,同時也非常容易了解到整個spring-boot的流程結構,建議大家能夠去看一下。
實現(xiàn)Starter了解到spring-boot的啟動器的作用和原理之后,我們可以開始實現(xiàn)doodle的啟動器了。
根據(jù)剛才提到的,啟動器要做以下幾件事
加載一些參數(shù)變量
加載Bean(IOC、AOP)等工作
啟動服務器
Configuration保存變量在com.zbw包下創(chuàng)建類Configuration用于保存一些全局變量,目前這個類只保存了現(xiàn)在實現(xiàn)的功能所需的變量。
package com.zbw; import ... /** * 服務器相關配置 */ @Builder @Getter public class Configuration { /** * 啟動類 */ private Class> bootClass; /** * 資源目錄 */ @Builder.Default private String resourcePath = "src/main/resources/"; /** * jsp目錄 */ @Builder.Default private String viewPath = "/templates/"; /** * 靜態(tài)文件目錄 */ @Builder.Default private String assetPath = "/static/"; /** * 端口號 */ @Builder.Default private int serverPort = 9090; /** * tomcat docBase目錄 */ @Builder.Default private String docBase = ""; /** * tomcat contextPath目錄 */ @Builder.Default private String contextPath = ""; }實現(xiàn)內嵌Tomcat服務器
在上一章文章從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC已經(jīng)在pom.xml文件中引入了tomcat-embed依賴,所以這里就不用引用了。
先在com.zbw.mvc下創(chuàng)建一個包server,然后再server包下創(chuàng)建一個接口Server
package com.zbw.mvc.server; /** * 服務器 interface */ public interface Server { /** * 啟動服務器 */ void startServer() throws Exception; /** * 停止服務器 */ void stopServer() throws Exception; }
因為服務器有很多種,雖然現(xiàn)在只用tomcat,但是為了方便擴展和修改,就先創(chuàng)建一個通用的server接口,每個服務器都要實現(xiàn)這個接口。
接下來就創(chuàng)建TomcatServer類,這個類實現(xiàn)Server
package com.zbw.mvc.server; import ... /** * Tomcat 服務器 */ @Slf4j public class TomcatServer implements Server { private Tomcat tomcat; public TomcatServer() { new TomcatServer(Doodle.getConfiguration()); } 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); // 添加jspServlet,defaultServlet和自己實現(xiàn)的dispatcherServlet tomcat.addServlet("", "jspServlet", new JspServlet()).setLoadOnStartup(3); tomcat.addServlet("", "defaultServlet", new DefaultServlet()).setLoadOnStartup(1); tomcat.addServlet("", "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0); ctx.addServletMappingDecoded("/templates/" + "*", "jspServlet"); ctx.addServletMappingDecoded("/static/" + "*", "defaultServlet"); ctx.addServletMappingDecoded("/*", "dispatcherServlet"); ctx.addServletMappingDecoded("/*", "dispatcherServlet"); } catch (Exception e) { log.error("初始化Tomcat失敗", e); throw new RuntimeException(e); } } @Override public void startServer() throws Exception { tomcat.start(); String address = tomcat.getServer().getAddress(); int port = tomcat.getConnector().getPort(); log.info("local address: http://{}:{}", address, port); tomcat.getServer().await(); } @Override public void stopServer() throws Exception { tomcat.stop(); } private File getRootFolder() { try { File root; String runningJarPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath().replaceAll("", "/"); int lastIndexOf = runningJarPath.lastIndexOf("/target/"); if (lastIndexOf < 0) { root = new File(""); } else { root = new File(runningJarPath.substring(0, lastIndexOf)); } log.info("Tomcat:application resolved root folder: [{}]", root.getAbsolutePath()); return root; } catch (URISyntaxException ex) { throw new RuntimeException(ex); } } }
這個類主要就是配置tomcat,和配置普通的外部tomcat有點類似只是這里是用代碼的方式。注意的是在getRootFolder()方法中獲取的是當前項目目錄下的target文件夾,即idea默認的編譯文件保存的位置,如果修改了編譯文件保存位置,這里也要修改。
特別值得一提的是這部分代碼:
// 添加jspServlet,defaultServlet和自己實現(xiàn)的dispatcherServlet tomcat.addServlet("", "jspServlet", new JspServlet()).setLoadOnStartup(3); tomcat.addServlet("", "defaultServlet", new DefaultServlet()).setLoadOnStartup(1); tomcat.addServlet("", "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0); ctx.addServletMappingDecoded("/templates/" + "*", "jspServlet"); ctx.addServletMappingDecoded("/static/" + "*", "defaultServlet"); ctx.addServletMappingDecoded("/*", "dispatcherServlet"); ctx.addServletMappingDecoded("/*", "dispatcherServlet");
這部分代碼就相當于原來的web.xml配置的文件,而且defaultServlet和jspServlet這兩個servlet是tomcat內置的servlet,前者用于處理靜態(tài)資源如css、js文件等,后者用于處理jsp。如果有安裝tomcat可以去tomcat目錄下的conf文件夾里有個web.xml文件,里面有幾行就是配置defaultServlet和jspServlet
default org.apache.catalina.servlets.DefaultServlet debug 0 listings false 1 jsp org.apache.jasper.servlet.JspServlet fork false xpoweredBy false 3
而dispatcherServlet就是從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC這一節(jié)中實現(xiàn)的分發(fā)器。這三個servlet都設置了LoadOnStartup,當這個值大于等于0時就會隨tomcat啟動也實例化。
實現(xiàn)啟動器類在com.zbw包下創(chuàng)建一個類作為啟動器類,就是類似于SpringApplication這樣的。這里起名叫做Doodle,因為這個框架就叫doodle嘛。
package com.zbw; import ... /** * Doodle Starter */ @NoArgsConstructor(access = AccessLevel.PRIVATE) @Slf4j public final class Doodle { /** * 全局配置 */ @Getter private static Configuration configuration = Configuration.builder().build(); /** * 默認服務器 */ @Getter private static Server server; /** * 啟動 */ public static void run(Class> bootClass) { run(Configuration.builder().bootClass(bootClass).build()); } /** * 啟動 */ public static void run(Class> bootClass, int port) { run(Configuration.builder().bootClass(bootClass).serverPort(port).build()); } /** * 啟動 */ public static void run(Configuration configuration) { new Doodle().start(configuration); } /** * 初始化 */ private void start(Configuration configuration) { try { Doodle.configuration = configuration; String basePackage = configuration.getBootClass().getPackage().getName(); BeanContainer.getInstance().loadBeans(basePackage); //注意Aop必須在Ioc之前執(zhí)行 new Aop().doAop(); new Ioc().doIoc(); server = new TomcatServer(configuration); server.startServer(); } catch (Exception e) { log.error("Doodle 啟動失敗", e); } } }
這個類中有三個啟動方法都會調用Doodle@start()方法,在這個方法里做了三件事:
讀取configuration中的配置
BeanContainer掃描包并加載Bean
執(zhí)行Aop
執(zhí)行Ioc
啟動Tomcat服務器
這里的執(zhí)行是有順序要求的,特別是Aop必須要在Ioc之前執(zhí)行,不然注入到類中的屬性都是沒被代理的。
修改硬編碼在之前寫mvc的時候有一處有個硬編碼,現(xiàn)在有了啟動器和全局配置,可以把之前的硬編碼修改了
對在com.zbw.mvc包下的ResultRender類里的resultResolver()方法,當判斷為跳轉到jsp文件的時候跳轉路徑那一行代碼修改:
try { Doodle.getConfiguration().getResourcePath(); // req.getRequestDispatcher("/templates/" + path).forward(req, resp); req.getRequestDispatcher(Doodle.getConfiguration().getResourcePath() + path).forward(req, resp); } catch (Exception e) { log.error("轉發(fā)請求失敗", e); // TODO: 異常統(tǒng)一處理,400等... }啟動和測試項目
現(xiàn)在doodle框架已經(jīng)完成其功能了,我們可以簡單的創(chuàng)建一個Controller來感受一下這個框架。
在com包下創(chuàng)建sample包,然后在com.sample包下創(chuàng)建啟動類APP
package com.sample; import com.zbw.Doodle; public class App { public static void main(String[] args) { Doodle.run(App.class); } }
然后再創(chuàng)建一個ControllerDoodleController:
package com.sample; import com.zbw.core.annotation.Controller; import com.zbw.mvc.annotation.RequestMapping; import com.zbw.mvc.annotation.ResponseBody; @Controller @RequestMapping public class DoodleController { @RequestMapping @ResponseBody public String hello() { return "hello doodle"; } }
接著再運行App的main方法,就能啟動服務了。
從零開始實現(xiàn)一個簡易的Java MVC框架(一)--前言
從零開始實現(xiàn)一個簡易的Java MVC框架(二)--實現(xiàn)Bean容器
從零開始實現(xiàn)一個簡易的Java MVC框架(三)--實現(xiàn)IOC
從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP
從零開始實現(xiàn)一個簡易的Java MVC框架(五)--引入aspectj實現(xiàn)AOP切點
從零開始實現(xiàn)一個簡易的Java MVC框架(六)--加強AOP功能
從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC
從零開始實現(xiàn)一個簡易的Java MVC框架(八)--制作Starter
從零開始實現(xiàn)一個簡易的Java MVC框架(九)--優(yōu)化MVC代碼
源碼地址:doodle
原文地址:從零開始實現(xiàn)一個簡易的Java MVC框架(八)--制作Starter
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76627.html
摘要:不過仔細了解了一段時候發(fā)現(xiàn),其實他的原理是很簡單的,所以想要自己也動手實現(xiàn)一個功能類似的框架。原文地址從零開始實現(xiàn)一個簡易的框架 前言 最近在看spring-boot框架的源碼,看了源碼之后更是讓我感受到了spring-boot功能的強大。而且使用了很多的設計模式,讓人在看的時候覺得有點難以下手。 不過仔細了解了一段時候發(fā)現(xiàn),其實他的原理是很簡單的,所以想要自己也動手實現(xiàn)一個功能類似的...
摘要:接下來就可以把這個切點類加入到我們之前實現(xiàn)的功能中了。實現(xiàn)的切點功能首先改裝注解,把之前改成來存儲表達式。測試用例在上一篇文章從零開始實現(xiàn)一個簡易的框架四實現(xiàn)中的測試用例的基礎上修改測試用例。 前言 在上一節(jié)從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP中我們實現(xiàn)了AOP的功能,已經(jīng)可以生成對應的代理類了,但是對于代理對象的選擇只能通過指定的類,這樣確實不方便也不合理。...
摘要:在前面的文章中實現(xiàn)的功能時,目標類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。改裝原有功能現(xiàn)在要改裝原來的的實現(xiàn)代碼,讓的功能加入到框架中為了讓切面能夠排序,先添加一個注解,用于標記排序。 前言 在前面從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP和從零開始實現(xiàn)一個簡易的Java MVC框架(五)--引入aspectj實現(xiàn)AOP切點這兩節(jié)文章...
摘要:前言在從零開始實現(xiàn)一個簡易的框架七實現(xiàn)中實現(xiàn)了框架的的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進行優(yōu)化。 前言 在從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC中實現(xiàn)了doodle框架的MVC的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進行優(yōu)化。 優(yōu)化的目標是1.去除DispatcherServlet請求分發(fā)器中的http邏...
摘要:容器實際上就是存放所有的地方,即以及相關信息對應其實體的容器,為什么稱之為呢,因為在中,定義信息和實例的東西叫。了解到這個以后接下來就可以開始編寫容器了,在包下創(chuàng)建一個類叫。獲取容器實例至此,這個容器就完成了。 項目準備 首先確保你擁有以下環(huán)境或者工具 idea java 8 maven 3.3.X lombok插件 然后我們創(chuàng)建一個maven工程,編寫pom.xml引入一些需要的...
閱讀 1302·2021-11-16 11:44
閱讀 3770·2021-10-09 10:01
閱讀 1755·2021-09-24 10:31
閱讀 3848·2021-09-04 16:41
閱讀 2521·2021-08-09 13:45
閱讀 1221·2019-08-30 14:08
閱讀 1785·2019-08-29 18:32
閱讀 1647·2019-08-26 12:12