從本博文開始,正式開啓Spring及SpringBoot源碼分析之旅。這多是一個漫長的過程,由於本人以前閱讀源碼都是很片面的,對Spring源碼沒有一個系統的認識。從本文開始我會持續更新,爭取在系列文章更完以後,也能讓本身對Spring源碼有一個系統的認識。html
在此立下一個flag,但願本身可以堅持下去。若是有幸讓您能從系列文章中學到丁點的知識,還請評論,關注,或推薦。若有錯誤還請在評論區指出,一塊兒討論共同成長。java
隨着使用 Spring 進行開發的我的和企業愈來愈多,Spring 也慢慢從一個單一簡潔的小框架變成一個大而全的開源軟件,Spring 的邊界不斷的進行擴充,到了後來 Spring 幾乎能夠作任何事情了,市面上主流的開源軟件、中間件都有 Spring 對應組件支持,人們在享用 Spring 的這種便利以後,也遇到了一些問題。Spring 每集成一個開源軟件,就須要增長一些基礎配置,慢慢的隨着人們開發的項目愈來愈龐大,每每須要集成不少開源軟件,所以後期使用 Spirng 開發大型項目須要引入不少配置文件,太多的配置很是難以理解,並容易配置出錯,到了後來人們甚至稱 Spring 爲配置地獄。web
Spring 彷佛也意識到了這些問題,急需有這麼一套軟件能夠解決這些問題,這個時候微服務的概念也慢慢興起,快速開發微小獨立的應用變得更爲急迫,Spring 恰好處在這麼一個交叉點上,於 2013 年初開始的 Spring Boot 項目的研發,2014年4月,Spring Boot 1.0.0 發佈。spring
Spring Boot 誕生之初,就受到開源社區的持續關注,陸續有一些我的和企業嘗試着使用了 Spring Boot,並迅速喜歡上了這款開源軟件。直到2016年,在國內 Spring Boot 才被正真使用了起來,期間不少研究 Spring Boot 的開發者在網上寫了大量關於 Spring Boot 的文章,同時有一些公司在企業內部進行了小規模的使用,並將使用經驗分享了出來。從2016年到2018年,使用 Spring Boot 的企業和我的開發者愈來愈多。2018年SpringBoot2.0的發佈,更是將SpringBoot的熱度推向了一個史無前例的高度。apache
(1)spring1.0時代tomcat
Spring的誕生大大促進了JAVA的發展。也下降了企業java應用開發的技術和時間成本。app
(2)spring2.0時代
對spring1.0在繁雜的xml配置文件上作了必定的優化,讓配置看起來愈來愈簡單,可是並沒語徹底解決xml冗餘的問題。框架
(3)spring3.0時代
可使用spring提供的java註解來取代曾經xml配置上的問題,彷佛咱們曾經忘記了發生什麼,spring變得史無前例的簡單。Spring3.0奠基了SpringBoot自動裝配的基礎。3.0提供的java註解使得咱們能夠經過註解的方式來配置spring容器。省去了使用相似於spring-context.xml的配置文件。webapp
同年,Servlet3.0規範的誕生爲SpringBoot完全去掉xml(web.xml)奠基了了理論基礎(對於servlet3.0來講,web.xml再也不是必需品。可是Servlet3.0規範仍是建議保留web.xml)。ide
(4)spring4.0時代
4.0 時代咱們甚至連xml配置文件都不須要了徹底使用java源碼級別的配置與spring提供的註解就能快速的開發spring應用程序,但仍然沒法改變Java Web應用程序的運行模式,咱們仍然須要將war部署到Web Server 上,才能對外提供服務。
4.0開始全面支持java8.0
同年,Servlet3.1規範誕生(tomcat8開始採用Servlet3.1規範)。
分析SpringBoot如何省去web.xml還得從Servlet3.0的規範提及。Servlet3.0規範規定以下(摘自穆茂強 張開濤翻譯的Servlet3.1規範,3.0和3.1在這一點上只有一些細節上的變換,在此不作過多介紹):
ServletContainerInitializer類經過jar services API查找。對於每個應用,應用啓動時,由容器建立一個ServletContainerInitializer 實例。 框架提供的ServletContainerInitializer實現必須綁定在 jar 包 的META-INF/services 目錄中的一個叫作 javax.servlet.ServletContainerInitializer 的文件,根據 jar services API,指定 ServletContainerInitializer 的實現。除 ServletContainerInitializer 外,咱們還有一個註解@HandlesTypes。在 ServletContainerInitializer 實現上的@HandlesTypes註解用於表示感興趣的一些類,它們可能指定了 HandlesTypes 的 value 中的註解(類型、方法或自動級別的註解),或者是其類型的超類繼承/實現了這些類之一。不管是否設置了 metadata-complete,@HandlesTypes 註解將應用。當檢測一個應用的類看是否它們匹配 ServletContainerInitializer 的 HandlesTypes 指定的條件時,若是應用的一個或多個可選的 JAR 包缺失,容器可能遇到類裝載問題。因爲容器不能決定是否這些類型的類裝載失敗將阻止應用正常工做,它必須忽略它們,同時也提供一個將記錄它們的配置選項。若是ServletContainerInitializer 實現沒有@HandlesTypes 註解,或若是沒有匹配任何指定的@HandlesType,那麼它會爲每一個應用使用 null 值的集合調用一次。這將容許 initializer 基於應用中可用的資源決定是否須要初始化 Servlet/Filter。在任何 Servlet Listener 的事件被觸發以前,當應用正在啓動時,ServletContainerInitializer 的 onStartup 方法將被調用。ServletContainerInitializer’s 的onStartup 獲得一個類的 Set,其或者繼承/實現 initializer 表示感興趣的類,或者它是使用指定在@HandlesTypes 註解中的任意類註解的。
這個規範如何理解呢?
簡單來講,當實現了Servlet3.0規範的容器(好比tomcat7及以上版本)啓動時,經過SPI擴展機制自動掃描全部已添加的jar包下的META-INF/services/javax.servlet.ServletContainerInitializer中指定的全路徑的類,並實例化該類,而後回調META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer的實現類的onStartup方法。 若是該類存在@HandlesTypes註解,而且在@HandlesTypes註解中指定了咱們感興趣的類,全部實現了這個類的onStartup方法將會被調用。
再直白一點來講,存在web.xml的時候,Servlet容器會根據web.xml中的配置初始化咱們的jar包(也能夠說web.xml是咱們的jar包和Servlet聯繫的中介)。而在Servlet3.0容器初始化時會調用jar包META-INF/services/javax.servlet.ServletContainerInitializer中指定的類的實現(javax.servlet.ServletContainerInitializer中的實現替代了web.xml的做用,而所謂的在@HandlesTypes註解中指定的感興趣的類,能夠理解爲具體實現了web.xml的功能,固然也能夠有其餘的用途)。
上一節中咱們介紹了SpringBoot誕生的技術基礎和Servlet3.0規範。這一章節,咱們經過Spring源碼來分析,Spring是如何實現省去web.xml的。
以下圖所示,在org.springframework:spring-web工程下,META-INF/services/javax.servlet.ServletContainerInitializer文件中,指定了將會被Servlet容器啓動時回調的類。
查看 SpringServletContainerInitializer 類的源碼,發現確實如如上文所說,實現了 ServletContainerInitializer ,而且也在 @HandlesTypes 註解中指定了,感興趣的類 WebApplicationInitializer
能夠看到onStartup方法上有一大段註釋,翻譯一下大體意思:
servlet 3.0+容器啓動時將自動掃描類路徑以查找實現Spring的webapplicationinitializer接口的全部實現,將其放進一個Set集合中,提供給 SpringServletContainerInitializer onStartup的第一個參數(翻譯結束)。
在Servlet容器初始化的時候會調用 SpringServletContainerInitializer 的onStartup方法,繼續看onStartup方法的代碼邏輯,在該onStartup方法中利用逐個調用webapplicationinitializer全部實現類中的onStartup方法。
1 @HandlesTypes(WebApplicationInitializer.class) 2 public class SpringServletContainerInitializer implements ServletContainerInitializer { 3 4 /** 5 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} 6 * implementations present on the application classpath. 7 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, 8 * Servlet 3.0+ containers will automatically scan the classpath for implementations 9 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all 10 * such types to the {@code webAppInitializerClasses} parameter of this method. 11 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath, 12 * this method is effectively a no-op. An INFO-level log message will be issued notifying 13 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that 14 * no {@code WebApplicationInitializer} implementations were found. 15 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected, 16 * they will be instantiated (and <em>sorted</em> if the @{@link 17 * org.springframework.core.annotation.Order @Order} annotation is present or 18 * the {@link org.springframework.core.Ordered Ordered} interface has been 19 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} 20 * method will be invoked on each instance, delegating the {@code ServletContext} such 21 * that each instance may register and configure servlets such as Spring's 22 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener}, 23 * or any other Servlet API componentry such as filters. 24 * @param webAppInitializerClasses all implementations of 25 * {@link WebApplicationInitializer} found on the application classpath 26 * @param servletContext the servlet context to be initialized 27 * @see WebApplicationInitializer#onStartup(ServletContext) 28 * @see AnnotationAwareOrderComparator 29 */ 30 @Override 31 public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) 32 throws ServletException { 33 34 List<WebApplicationInitializer> initializers = new LinkedList<>(); 35 36 if (webAppInitializerClasses != null) { 37 for (Class<?> waiClass : webAppInitializerClasses) { 38 // Be defensive: Some servlet containers provide us with invalid classes, 39 // no matter what @HandlesTypes says... 40 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && 41 WebApplicationInitializer.class.isAssignableFrom(waiClass)) { 42 try { 43 initializers.add((WebApplicationInitializer) 44 ReflectionUtils.accessibleConstructor(waiClass).newInstance()); 45 } 46 catch (Throwable ex) { 47 throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); 48 } 49 } 50 } 51 } 52 53 if (initializers.isEmpty()) { 54 servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); 55 return; 56 } 57 58 servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); 59 AnnotationAwareOrderComparator.sort(initializers); 60 for (WebApplicationInitializer initializer : initializers) { 61 initializer.onStartup(servletContext); 62 } 63 } 64 65 }
查看 WebApplicationInitializer 接口,這個接口也就是上文中所說的Servlet3.0規範中 @HandlesTypes(WebApplicationInitializer.class) 註解中所指定的感興趣的類。
截取一段很重要的註釋。這段註釋告訴咱們實現該接口的類主要須要實現的功能就是web.xml中配置文件中配置的內容。
1 /* 2 * <servlet> 3 * <servlet-name>dispatcher</servlet-name> 4 * <servlet-class> 5 * org.springframework.web.servlet.DispatcherServlet 6 * </servlet-class> 7 * <init-param> 8 * <param-name>contextConfigLocation</param-name> 9 * <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value> 10 * </init-param> 11 * <load-on-startup>1</load-on-startup> 12 * </servlet> 13 * 14 * <servlet-mapping> 15 * <servlet-name>dispatcher</servlet-name> 16 * <url-pattern>/</url-pattern> 17 * </servlet-mapping>}</pre> 18 * 19 */ 20 public interface WebApplicationInitializer { 21 void onStartup(ServletContext servletContext) throws ServletException; 22 }
查看SpringBoot SpringBootServletInitializer 源碼,該類在spring-boot依賴包中。
仔細看下面的標藍的代碼。不難發現這正是Servlet容器(tomcat)如何找到SpringBoot並啓動它的。
1 package org.springframework.boot.web.support; 2 3 import javax.servlet.Filter; 4 import javax.servlet.Servlet; 5 import javax.servlet.ServletContext; 6 import javax.servlet.ServletContextEvent; 7 import javax.servlet.ServletException; 8 9 import org.apache.commons.logging.Log; 10 import org.apache.commons.logging.LogFactory; 11 12 import org.springframework.boot.SpringApplication; 13 import org.springframework.boot.builder.ParentContextApplicationContextInitializer; 14 import org.springframework.boot.builder.SpringApplicationBuilder; 15 import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; 16 import org.springframework.boot.web.servlet.ServletContextInitializer; 17 import org.springframework.context.ApplicationContext; 18 import org.springframework.context.annotation.Configuration; 19 import org.springframework.core.annotation.AnnotationUtils; 20 import org.springframework.util.Assert; 21 import org.springframework.web.WebApplicationInitializer; 22 import org.springframework.web.context.ContextLoaderListener; 23 import org.springframework.web.context.WebApplicationContext; 24 import org.springframework.web.context.support.StandardServletEnvironment; 25 26 /** 27 * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} 28 * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and 29 * {@link ServletContextInitializer} beans from the application context to the servlet 30 * container. 31 * <p> 32 * To configure the application either override the 33 * {@link #configure(SpringApplicationBuilder)} method (calling 34 * {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a 35 * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in 36 * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you 37 * might also want to add an {@code @Ordered} annotation to configure a specific startup 38 * order. 39 * <p> 40 * Note that a WebApplicationInitializer is only needed if you are building a war file and 41 * deploying it. If you prefer to run an embedded container then you won't need this at 42 * all. 43 * 44 * @author Dave Syer 45 * @author Phillip Webb 46 * @author Andy Wilkinson 47 * @since 1.4.0 48 * @see #configure(SpringApplicationBuilder) 49 */ 50 public abstract class SpringBootServletInitializer implements WebApplicationInitializer { 51 52 protected Log logger; // Don't initialize early 53 54 private boolean registerErrorPageFilter = true; 55 56 /** 57 * Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if 58 * error page mappings should be handled via the Servlet container and not Spring 59 * Boot. 60 * @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered. 61 */ 62 protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) { 63 this.registerErrorPageFilter = registerErrorPageFilter; 64 } 65 66 @Override 67 public void onStartup(ServletContext servletContext) throws ServletException { 68 // Logger initialization is deferred in case a ordered 69 // LogServletContextInitializer is being used 70 this.logger = LogFactory.getLog(getClass()); 71 WebApplicationContext rootAppContext = createRootApplicationContext( 72 servletContext); 73 if (rootAppContext != null) { 74 servletContext.addListener(new ContextLoaderListener(rootAppContext) { 75 @Override 76 public void contextInitialized(ServletContextEvent event) { 77 // no-op because the application context is already initialized 78 } 79 }); 80 } 81 else { 82 this.logger.debug("No ContextLoaderListener registered, as " 83 + "createRootApplicationContext() did not " 84 + "return an application context"); 85 } 86 } 87 88 protected WebApplicationContext createRootApplicationContext( 89 ServletContext servletContext) { 90 SpringApplicationBuilder builder = createSpringApplicationBuilder(); 91 StandardServletEnvironment environment = new StandardServletEnvironment(); 92 environment.initPropertySources(servletContext, null); 93 builder.environment(environment); 94 builder.main(getClass()); 95 ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); 96 if (parent != null) { 97 this.logger.info("Root context already created (using as parent)."); 98 servletContext.setAttribute( 99 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); 100 builder.initializers(new ParentContextApplicationContextInitializer(parent)); 101 } 102 builder.initializers( 103 new ServletContextApplicationContextInitializer(servletContext)); 104 builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); 105 builder = configure(builder); 106 SpringApplication application = builder.build(); 107 if (application.getSources().isEmpty() && AnnotationUtils 108 .findAnnotation(getClass(), Configuration.class) != null) { 109 application.getSources().add(getClass()); 110 } 111 Assert.state(!application.getSources().isEmpty(), 112 "No SpringApplication sources have been defined. Either override the " 113 + "configure method or add an @Configuration annotation"); 114 // Ensure error pages are registered 115 if (this.registerErrorPageFilter) { 116 application.getSources().add(ErrorPageFilterConfiguration.class); 117 } 118 return run(application); 119 } 120 121 /** 122 * Returns the {@code SpringApplicationBuilder} that is used to configure and create 123 * the {@link SpringApplication}. The default implementation returns a new 124 * {@code SpringApplicationBuilder} in its default state. 125 * @return the {@code SpringApplicationBuilder}. 126 * @since 1.3.0 127 */ 128 protected SpringApplicationBuilder createSpringApplicationBuilder() { 129 return new SpringApplicationBuilder(); 130 } 131 132 /** 133 * Called to run a fully configured {@link SpringApplication}. 134 * @param application the application to run 135 * @return the {@link WebApplicationContext} 136 */ 137 protected WebApplicationContext run(SpringApplication application) { 138 return (WebApplicationContext) application.run(); 139 } 140 141 private ApplicationContext getExistingRootWebApplicationContext( 142 ServletContext servletContext) { 143 Object context = servletContext.getAttribute( 144 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 145 if (context instanceof ApplicationContext) { 146 return (ApplicationContext) context; 147 } 148 return null; 149 } 150 151 /** 152 * Configure the application. Normally all you would need to do is to add sources 153 * (e.g. config classes) because other settings have sensible defaults. You might 154 * choose (for instance) to add default command line arguments, or set an active 155 * Spring profile. 156 * @param builder a builder for the application context 157 * @return the application builder 158 * @see SpringApplicationBuilder 159 */ 160 protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 161 return builder; 162 } 163 164 }
查看Spring 5.0.14官方文檔:https://docs.spring.io/spring/docs/5.0.14.RELEASE/spring-framework-reference/web.html#spring-web
文檔中給出在傳統的springMVC中在web.xml中的配置內容
1 <web-app> 2 <!-- 初始化Spring上下文 --> 3 <listener> 4 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 5 </listener> 6 <!-- 指定Spring的配置文件 --> 7 <context-param> 8 <param-name>contextConfigLocation</param-name> 9 <param-value>/WEB-INF/app-context.xml</param-value> 10 </context-param> 11 <!-- 初始化DispatcherServlet --> 12 <servlet> 13 <servlet-name>app</servlet-name> 14 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15 <init-param> 16 <param-name>contextConfigLocation</param-name> 17 <param-value></param-value> 18 </init-param> 19 <load-on-startup>1</load-on-startup> 20 </servlet> 21 <servlet-mapping> 22 <servlet-name>app</servlet-name> 23 <url-pattern>/app/*</url-pattern> 24 </servlet-mapping> 25 </web-app>
文檔中提供了一個如何使用基於java代碼的方式配置Servlet容器example
1 public class MyWebApplicationInitializer implements WebApplicationInitializer { 2 3 @Override 4 public void onStartup(ServletContext servletCxt) { 5 6 // Load Spring web application configuration 7 //經過註解的方式初始化Spring的上下文 8 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); 9 //註冊spring的配置類(替代傳統項目中xml的configuration) 10 ac.register(AppConfig.class); 11 ac.refresh(); 12 13 // Create and register the DispatcherServlet 14 //基於java代碼的方式初始化DispatcherServlet 15 DispatcherServlet servlet = new DispatcherServlet(ac); 16 ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); 17 registration.setLoadOnStartup(1); 18 registration.addMapping("/app/*"); 19 } 20 }
對比官方文檔給出的example,不難發現上面這段java代碼就是SpringBoot省去web.xml的具體實現方法。上面 MyWebApplicationInitializer 正是 WebApplicationInitializer ( @HandlesTypes(WebApplicationInitializer.class) ) 接口的實現。
官方文檔提供的 MyWebApplicationInitializer 類正是SpringBoot不依賴與web.xml的關鍵代碼。
SpringBoot中具體實現web.xml中配置的代碼沒有官方文檔中的example這麼簡單,SpringBoot中具體初始化 DispatcherServlet 的類是 DispatcherServletAutoConfiguration 。感興趣的話能夠斷點調試一下。
以上章節介紹了SpringBoot誕生的歷史背景,每個新技術的誕生,都是場景驅動的。而後介紹了SpringBoot能作到不依賴web.xml的技術條件。最後經過源碼分析了SpringBoot中具體的實現。
下一篇博文將利用本文講到的知識基於Spring springframework內置tomcat簡單模擬SpringBoot的基本功能。簡單說就是實現一個簡易版的SpringBoot。
本文是筆者查閱大量資料,閱讀大量Spring源碼總結出來的,原創不易,轉載請註明出處。
若有錯誤請在評論區留言指正。
參考文獻:
https://blog.csdn.net/adingyb/article/details/80707471
https://www.jianshu.com/p/c6f4df3d720c