一個項目老是要有一個啓動的地方,當項目部署在tomcat中的時候,常常就會用tomcat的startup.sh(startup.bat)
的啓動腳原本啓動web項目css
而在spring-boot的web項目中基本會有相似於這樣子的啓動代碼:java
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
這個方法實際上會調用spring-boot的SpringApplication
類的一個run方法:git
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // 1.加載環境變量、參數等 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.加載環境變量、參數等 2.加載Bean(IOC、AOP)等。3.若是得到的ApplicationContext
爲ServletWebServerApplicationContext
,那麼在refresh()
以後會啓動服務器,默認的就是tomcat服務器。github
我以爲spring-boot啓動器算是spring-boot中相對來講代碼清晰易懂的,同時也很是容易瞭解到整個spring-boot的流程結構,建議你們可以去看一下。web
瞭解到spring-boot的啓動器的做用和原理以後,咱們能夠開始實現doodle的啓動器了。spring
根據剛纔提到的,啓動器要作如下幾件事apache
在com.zbw包下建立類Configuration
用於保存一些全局變量,目前這個類只保存瞭如今實現的功能所需的變量。tomcat
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/"; /** * 靜態文件目錄 */ @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 = ""; }
在上一章文章從零開始實現一個簡易的Java MVC框架(七)--實現MVC已經在pom.xml文件中引入了tomcat-embed
依賴,因此這裏就不用引用了。服務器
先在com.zbw.mvc下建立一個包server,而後再server包下建立一個接口Server
mvc
package com.zbw.mvc.server; /** * 服務器 interface */ public interface Server { /** * 啓動服務器 */ void startServer() throws Exception; /** * 中止服務器 */ void stopServer() throws Exception; }
由於服務器有不少種,雖然如今只用tomcat,可是爲了方便擴展和修改,就先建立一個通用的server接口,每一個服務器都要實現這個接口。
接下來就建立TomcatServer
類,這個類實現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和本身實現的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和本身實現的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,前者用於處理靜態資源如css、js文件等,後者用於處理jsp。若是有安裝tomcat能夠去tomcat目錄下的conf文件夾裏有個web.xml文件,裏面有幾行就是配置defaultServlet
和jspServlet
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
而dispatcherServlet就是從零開始實現一個簡易的Java MVC框架(七)--實現MVC這一節中實現的分發器。這三個servlet都設置了LoadOnStartup,當這個值大於等於0時就會隨tomcat啓動也實例化。
在com.zbw包下建立一個類做爲啓動器類,就是相似於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以前執行 new Aop().doAop(); new Ioc().doIoc(); server = new TomcatServer(configuration); server.startServer(); } catch (Exception e) { log.error("Doodle 啓動失敗", e); } } }
這個類中有三個啓動方法都會調用Doodle@start()
方法,在這個方法裏作了三件事:
configuration
中的配置這裏的執行是有順序要求的,特別是Aop必需要在Ioc以前執行,否則注入到類中的屬性都是沒被代理的。
在以前寫mvc的時候有一處有個硬編碼,如今有了啓動器和全局配置,能夠把以前的硬編碼修改了
對在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("轉發請求失敗", e); // TODO: 異常統一處理,400等... }
如今doodle框架已經完成其功能了,咱們能夠簡單的建立一個Controller來感覺一下這個框架。
在com包下建立sample包,而後在com.sample包下建立啓動類APP
package com.sample; import com.zbw.Doodle; public class App { public static void main(String[] args) { Doodle.run(App.class); } }
而後再建立一個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方法,就能啓動服務了。
- 從零開始實現一個簡易的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