微服務 SpringBoot 2.0(三):啟動剖析之@SpringBootApplication


我原以為就一個注解,背后竟然還有3個 —— Java面試必修

引言

前面兩章我們先后認識了SpringBoot和它的極簡配置,為新手入門的學習降低了門檻,會基本的使用后,接下來我們將進一步認識SpringBoot,它為何能做到服務秒開,就來跟隨我一起分析SpringBoot運行啟動的原理吧。

啟動原理分2章講解,本章講解@SpringBootApplication注解部分,若需了解SpringApplication.run方法部分請點擊此處

運行啟動

工具

  • SpringBoot版本:2.0.4
  • 開發工具:IDEA 2018
  • Maven:3.3 9
  • JDK:1.8

首先我們看一段啟動代碼

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

 

這不就是個啟動類嘛?從字面理解我都知道Spring啟動的入口,有啥好看的。可別小瞧了這幾行代碼

開始推理

從上面代碼來看,@SpringBootApplication 和 SpringApplication.run長得很相似,比較詭異,所以我們從這兩個開始分析,首先先看注解

@SpringBootApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

 

不要被那么多元注解聲明所嚇到,仔細查看可以看出其中有3個重要的注解

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

所以我覺得,這3個才是真正的幕后主使,@SpringBootApplication只是他們找來擋槍口的,他們合體應該等價於@SpringBootApplication,如下代碼正常運行

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

 

哈哈,經過查詢資料發現,Spring Boot 1.2版之前,真的是由這3個注解出面,之后呢,才隱居幕后,由小嘍嘍在前面擋槍


@SpringBootConfiguration

首先我們來分析這個,點開看到源碼之后我慌了,這特么什么都沒有,唯一的疑點在@Configuration。
好吧,還挺會偽裝,我們就分析@Configuration這個注解吧

  1. 用之前spring的思維,我推斷這個應該是解決當前Class的XML配置問題,凡是經過該注解修飾的,均被實例化到Spring應用程序上下文中,由spring統一管理生命周期,我說IoC大家應該熟悉了吧。
  2. 舉個例子
    傳統的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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="true">
    <!--bean定義-->
  <bean id="userService" class="..UserServiceImpl">
    ...
  </bean>
</beans>

 

使用@Configuration之后的寫法

@Configuration
public class SpringConfiguration{
    @Bean
    public UserService userService(){
        return new UserServiceImpl();
    }
}

 

任何一個標注了@Configuration的Java類定義都是一個JavaConfig配置類。
任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。

  1. 表達依賴注入關系層面
<bean id="userService" class="..UserServiceImpl">
    <propery name ="dependencyService" ref="dependencyService" />
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"></bean>
@Configuration
public class SpringConfiguration{
    @Bean
    public UserService userService(){
        return new UserServiceImpl(dependencyService());
    }
    
    @Bean
    public DependencyService dependencyService(){
        return new DependencyServiceImpl();
    }
}

 

如果一個bean的定義依賴其他bean,則直接調用對應的JavaConfig類中依賴bean的創建方法即可,如上方的dependencyService()


@ComponentScan

這個注解在Spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。

我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。

以前xml中是這么定義的,如下

 <context:component-scan base-package="com.platform.fox.html.**.controller"/>
 <context:component-scan base-package="com.platform.fox.html.**.repository"/>
 <context:component-scan base-package="com.platform.fox.html.**.service"/>
 <context:component-scan base-package="com.platform.fox.html.**.wshandler"/>

 

注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages


@EnableAutoConfiguration

Enable,通常來說我們認為它一定是在開啟或者支持什么功能,比如:@EnableScheduling@EnableCaching,所以他們要做的事情應該都是相似的,根據源碼判斷,簡單概括一下就是,借助@Import的支持,收集和注冊特定場景相關的bean定義。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

 

我理解就是要開船了,EnableAutoConfigurationImportSelector根據名單把水手,舵手、安檢員都統一叫過來各就各位。幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。就像一管理員一樣

借助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!

EnableAutoConfigurationImportSelector中自動配置的關鍵方法如下

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //獲取符合條件的帶有Configuration的注解示例類
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
}

 

SpringFactoriesLoader
所以真正的工作者是SpringFactoriesLoaderSpringFactoriesLoader屬於Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置

public abstract class SpringFactoriesLoader {
    //...
    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        ...
    }


    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        ....
    }
}

 

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應的一組@Configuration類

 
spring.factories

上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內容,可以很好地說明問題。我從中隨機挑取一個類查看源代碼

 
WebMvcAutoConfiguration

所以,其底層真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件,並將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個並加載到IoC容器。

@SpringBootApplication注解說完了,接下來我們來分析SpringApplication.run方法


作者有話說:喜歡的話就請關注Java面試必修 ,請自備水,更多干、干、干貨等着你


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM