Spring Boot 前世今生和整體架構分析

依託於Servlet的Spring Boot

    spring boot是目前java微服務廣泛使用的Web框架,本身內部的核心模塊是嵌入的tomcat和spring mvc。

Spring Boot 前世今生和整體架構分析-D-Chat_20200615210602.png

    spring mvc設計上遵循Servlet標準,是在標準Servlet接口的基礎上實現後續的請求處理和應答。一個Servlet本身並不能獨立運行,需要依託外部Servlet容器加載到應用中才能工作。tomcat就是servlet容器中的佼佼者,和spring mvc一起,最終構成了spring boot的核心基礎。tomcat和spring mvc的組合雖然已經能夠實現整個系統的運作,但是這種模式下spring mvc應用在開發過程中面臨繁瑣的配置問題。這時spring boot的出現解決了這個問題,同時內置tomcat+spring mvc的模式又恰恰迎合了微服務的潮流。

Spring Boot 前世今生和整體架構分析-tomcat_dependency.png
spring boot項目中tomcat核心模塊依賴
Spring Boot 前世今生和整體架構分析-spring_mvc_dependency.png
spring boot項目中spring mvc核心模塊依賴

歷史悠久的Servlet標準

    1997年,Sun公司聯合其他公司一起,發佈了在Java業界影響深遠的J2EE企業開發標準。這個標準中就包含了著名的JDBC、JSP、XML等等規範,Java Servlet也是這其中之一。

    Servlet標準自從發佈後持續迭代,目前已經進入3.0時代。Servlet 3.1接口標準如下:

public interface Servlet {
    //初始化Servlet。ServletConfig Servlet配置
    public void init(ServletConfig config) throws ServletException;   

    //獲取Servlet配置
    public ServletConfig getServletConfig();             

    //獲取Servlet信息.作者版權等
    public String getServletInfo();   

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    //銷燬
    public void destroy();               
}

熟悉java的開發者都知道,接口並不能真正的運作,而是爲實現的子類提供的一個參考規範,同時實現這一個規範的子類也能夠作爲一個組件被使用到所有同類接口規範的環境中。Servlet規範中,除了初始化配置init方法、獲取配置和附帶信息getServletConfig與getServletInfo方法,以及銷燬回收資源的destroy方法之外,最重要的是service方法。service方法是Servlet中用來處理傳入請求、處理傳出Response的地方,作爲web服務的核心,是需要重點關注的點。傳入的ServletRequest、ServletResponse參數是Servlet組件處理的起點,ServletRequest接口設定了網絡協議、遠程主機地址、設定了ContentType、定了Key、Attribute的參數結構、輸入流等,ServletResponse接口設定了ContentType、Length、本地化配置、輸出流、Buffer等。

    你可能會奇怪Servlet接口標準過於寬泛,和我們目前實際使用的Http協議的形式不太一致。實際上,Servlet標準中也包含了HttpServlet的標準。

Spring Boot 前世今生和整體架構分析-D-Chat_20200616094953.png

HttpServlet實現的service方法特異化地針對Http協議的請求。一方面表現在傳參轉化爲繼承ServletRequest、ServletResponse的HttpServletRequest、HttpServletResponse,它們新增了Header、Conkie、Path、URL、應答Status和狀態枚舉(例,404、502)等。

Spring Boot 前世今生和整體架構分析-D-Chat_20200620140538.png

另一方面,service方法的實現中,會根據http請求的類型,將請求分派到doGet、doPost、doHead、doPut、doTrace、doOption方法中,分別對應Http協議中的GET、POST、HEAD等類型。

    Servlet標準爲Servlet和Servlet容器各自需要完成的工作劃定了邊界。這一點在後續對tomcat和spring mvc介紹中會有更清楚的解釋。

Servlet容器實例:Tomcat

tomcat需要做什麼

    根據Servlet標準,Servlet容器需要做些什麼呢?最簡單直接能想到的就是,需要處理套接字Socket的接入,以完成對TCP消息的接收和發送,tomcat的核心能力也就是在這裏。

    相對複雜的一點是Tomcat需要考慮的更多。首先,tomcat最初是可以作爲一個獨立的服務器部署的,不同的實現Servlet接口的應用都可以被打包整war包,熱部署到tomcat指定目錄下,實現服務的運行。所以,tomcat需要考慮同一個服務器下多個站點Host、多個Servlet應用運行的問題。具體而言,靜態上就涉及多站點配置、多服務配置、Servlet管理和加載、連接管理和配置,動態上涉及socket接入、消息解包和封包、符合Servlet標準的請求request和應答response的填充、請求應答的服務分流等等。雖然到現在來看,微服務下框架根本不需要這麼複雜,多站點、多服務現在已經不是嵌入的tomcat需要考慮的問題。
Spring Boot 前世今生和整體架構分析-tomcat_work.png

tomcat如何做到的
啓動和配置策略
Spring Boot 前世今生和整體架構分析-tomcat + spring mvc原理(二).png

    由於考慮了多站點Host和多Servlet的情況,Tomcat的整體架構還是比較複雜的詳參。其中,最上層的Server代表了Tomcat服務自身,Connecter用來處理連接管理和請求解包、應答封包,每個Service可持有多個Connector和一個Engine。Container是Engine、Host、Context、Wrapper的統一標準。其中,Engine用來管理多個Host,每個Host代表一個虛擬的站點,Context代表一套應用服務的上下文,每個Wrapper包裹了一個Servlet,在啓動或者檢測到新war包時,使用反射進行Servlet的加載。

    靜態的這種架構在啓動時採用的方式是,所有這些容器都繼承了Lifecycle接口,用來實現生命週期的管理。詳參

public interface Lifecycle {    
    ......
    //容器初始化   
    public void init() throws LifecycleException;  
    //容器啓動         
    public void start() throws LifecycleException;  
    //容器停止       
    public void stop() throws LifecycleException;
    //容器銷燬        
    public void destroy() throws LifecycleException;       

}

父容器在初始化時調用子容器的init方法,在啓動時調用子容器的start方法…這樣層層調用,最終實現所有容器的啓動和停止。Lifecycle也定義了生命週期的事件監聽機制,在初始化之前後、啓動前後、停止前後、銷燬前後,都能夠通過實現監聽器添加自己的代碼邏輯。

    最初的tomcat以及spring mvc的開發,最被人詬病的就是它們的配置問題。這裏先看一下獨立運行的tomcat服務器的配置。在tomcat安裝目錄下可以找到一個conf/server.xml文件,裏面層層定義了不同容器的配置參數。

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost"  appBase="webapps"
      </Host>
    </Engine>
  </Service>
</Server>
消息處理策略:

    前面講到Connector是用來處理連接管理的,所有消息接收處理的起點就是Connector。
Spring Boot 前世今生和整體架構分析-tomcat + spring mvc 原理(三):網絡請求的監控與處理-Connector structure.png
如上圖是Connector內部的結構。不同的ProtocolHandler接口的實現代表不同的協議處理,現在最主要和常見的是Http協議處理的ProtocolHandler。其內部的EndPoint主要用來處理底層TCP消息的接收和發送,Acceptor用來監聽接收連接,Poller(圖中未展示)用來處理Acceptor接收的消息,這兩個部分都是使用多線程異步處理。之後接收到的消息被傳送給了Handler,Handler用來適配不同協議的Processor。Http協議的Processor處理器就能夠將TCP消息轉化爲Http的形式。最後在由Adapter完成特定格式的請求request、應答response的”裝箱“,傳遞到Engine(Container的最上層實現)中。詳參

Spring Boot 前世今生和整體架構分析-tomcat + spring mvc 原理(四):tomcat網絡請求的監控與處理-pipeline.png

    上面說到過不同層次Container的作用,它們的設計是爲了Tomcat多站點、多服務的實現,在微服務時代已經幾乎喪失了作用。這裏我就簡單介紹它們請求的傳送原理,對內部細節不做過多解釋。Engine、Host、Context、Wrapper都實現了Container接口,同時是繼承了ContainerBase類。ContainerBase類持有了Pipeline子類實例,Pipeline設計採用了責任鏈的形式,鏈的節點是一個valve,Pipeline負責依次調用每個節點的invoke方法處理請求。對於Engine、Host、Context、Wrapper這種父子容器的形式,每種容器都有一個默認實現的StandardXXXValve,這個節點該層次所有節點調用之後再被調用,然後傳入到子容器Pipeline處理,就這樣一層一層,直到請求被傳到WrapperPipeline中。StandardWrapperValve能夠獲取加載的Servlet,再經過一系列Filter處理之後詳參,最終請求和應答被傳入Servlet的service方法中。tomcat處理終結,Servlet開始工作。詳參

spring mvc:那個DispatcherServlet

Servlet的實現
Spring Boot 前世今生和整體架構分析-DispatcherServlet結構.png

    spring mvc本質是一個Servlet,而DispatcherServlet是spring mvc的核心。上圖顯示了DispatcherServlet的繼承結構,未被框起來的部分是上文已經介紹過的Servlet標準,紅框內是spring提供的獲取和設置環境、上下文Context的接口,藍框內是spring mvc下Servlet實現,HttpServletBean和FrameServlet主要負責獲取上下文、環境配置,DispatcherServlet用來處理請求分發和後續的請求、應答流程。詳參

spring mvc請求的處理

    從tomcat傳入到Servlet service方法的request和response,最終最主要的處理邏輯是調用DispatcherServlet的doDispatch方法。詳參

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception;
Spring Boot 前世今生和整體架構分析-DispatcherServlet_request_handle.png

    doDispatch方法中會使用spring mvc中定義的各種組件來處理整個流程,有些組件目前仍然在項目中發光發熱,有些組件主要應用於前後端不分離的時期,目前已經退出了歷史舞臺,但是在spring mvc的源碼中還能看到他們的身影。

    http請求在doDispatch方法中,最先會被MultipartResolver解析,這個組件主要用來判斷是否是文件上傳請求,如果是,會進行緩存和其他預備處理。然後HandlerMapping詳參會通過請求的類型、url等獲取處理這個請求的Handler(Interceptor和Controller是不同類型的Handler)詳參。HandlerAdaper詳參用來使用Handler處理請求,因爲這之前會有參數解析等預處理,解析好的參數被填充到Controller的相應處理方法中詳參,這之後就是編寫的業務代碼的領域。只直到Controller方法處理完畢後返回,HandlerAdapter又會對返回結果進行處理。

    如果是之前的前後端不分離架構,比如JSP開發的前端加spring mvc的形式,後續還會視圖解析、視圖渲染相關的工作詳參。目前大多使用RESTful服務,是直接發送json或者xml數據給獨立的前端,前端自行完成渲染和展示,比如現在流行的JavaScript。所以像視圖解析器(ViewResolver)這樣的組件,已經不能發揮作用了。

spring mvc的配置

    像上文提到的tomcat配置一樣,spring mvc配置也採用xml的方式。每個開發的spring mvc應用中都需要配置一個servlet.xml文件,如下是示例內容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    <!--視圖解析-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--掃描-->
    <context:component-scan base-package="com.demo.controller"/>
</beans>

    spring mvc的配置中現在增加了自動掃描bean的設置,但是如果有些特殊配置,還是需要人工手動添加,比較繁瑣。

spring boot:一站式配置

    如何解決tomcat和spring mvc中令人頭疼的配置問題,spring boot提供了一套解決方案。利用spring 4對條件化配置的支持,spring boot實現了合理推測應用所需的bean並自動化配置的機制。

自動化配置的實現

    spring boot啓動類的註解@SpringBootApplication是一個複合註解,包含了@EnableAutoConfiguration和@ComponentScan這兩個註解。其中@ComponentScan註解比較常見,可以用來掃描基礎包下的所有bean詳參。而@EnableAutoConfiguration詳參註解內部實現了掃描外部依賴包配置文件中的bean,這個配置文件就是spring.factories。

# 初始化工具
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# 應用監聽器
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# 配置監聽器
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# 自動配置的過濾組件
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# 自動配置類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\

以上截取了spring boot默認的spring.factories文件內容的一部分,除了配置了初始化工具、應用監聽器等,最重要的部分是自動配置類,這些類被加載之後,又會注入其他依賴的bean或者設置其他條件,這樣就完成依賴bean的自動化配置。
    任何引入jar包依賴中都可以設置spring.factories文件,比如這裏以mybatis爲例。詳參

Spring Boot 前世今生和整體架構分析-spring boot原理分析(五):依賴包中bean自動配置之Mybatis和自定義包配置-mybatis-autoconfigure.png mybatis-spring-boot-autoconfigure jar中引入了spring.factories文件,這個文件中只有一句配置:
# Auto
Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

就是用來引入MybatisAutoConfiguration。

    MybatisAutoConfiguration的格式和替他AutoConfiguration格式基本一致。

//註解用來實現條件配置注入
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
   ……
  //注入依賴的bean
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    ……
    return factory.getObject();
  }

 //注入依賴的bean
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
      …….
      return new SqlSessionTemplate(sqlSessionFactory);
  }
 //設置其他條件
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
       …….
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ……
        scanner.setAnnotationClass(Mapper.class);   //設置掃描@Mapper
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(packages));
       ….
}
環境上下文管理

    自動化配置實現後,需要對配置的bean和其他環境進行統一管理,這裏就需要引入spring boot中重要的概念——應用上下文ApplicationContext。
詳參
Spring Boot 前世今生和整體架構分析-applicationContext.png

上圖是spring boot ApplicationContext的繼承樹,其中對於Servlet,最終實際發揮作用的是AnnotationConfigServletWebServerApplicationContext。但通過分析其父類,可以知道AnnotationConfigServletWebServerApplicationContext具有哪些能力。

    ApplicationContext接口以上爲整個應用上下文包含的內容奠定了基礎,比如包含了ResourceLoader的加載能力、ApplicationEventPublisher的應用事件的發佈能力、BeanFactory 獲取和註冊bean的能力、EnvironmentCapable獲取環境配置的能力。

    WebApplicationContext接口添加了ServletContext getServletContext()方法。ServletContext的相關內容在spring mvc的原理(七)中,ServletConfig能夠獲取ServletContext的配置,其實代表就是spring mvc的應用本身。
    WebServerApplicationContext的接口裏增加了WebServer getWebServer()的方法,這裏具體代表的就是tomcat服務器本身。
    這樣應用上下文即能夠獲取spring mvc配置,又能獲取內嵌的tomcat服務器,在加載過程中,就能夠啓動整個應用。

    ConfigurableApplicationContext是一個功能性的子接口,爲ApplicationContext提供了setter的方法,所有有繼承改接口的子類都擁有修改配置的能力。

    最後對於AnnotationConfigServletWebServerApplicationContext,除了上述所有子接口和子類的功能之外,還實現了AnnotationConfigRegistry接口。AnnotationConfigRegistry這個接口主要是負責bean註解的註冊和掃描工作,比如@Configuration註解。

    所以spring boot的啓動工作就是完成自動配置化,然後初始化應用上下文,啓動tomcat和加載spring mvc,整個應用就能愉快的跑起來了。詳參

    spring boot的衍生髮展歷程遵循了基本的事物發展規律,始終是依託已有的資源有序有目標的演化。目前依託於spring boot,spring cloud的服務治理項目也發展起來,相信spring boot會像tomcat、spring mvc一樣成爲新事物的基石。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章