細說SpringBoot的自動裝配原理

1.什麼是SpringBoot?

  對於spring框架,我們接觸得比較多的應該是spring mvc、和spring。而spring的核心在於IOC(控制反轉對於spring框架來說,就是由spring來負責控制對象的生命週期和對象間的關係)和DI(依賴注入IoC的一個重點是在系統運行中,動態的向某個對象提供它所需要的其他對象。這一點是通過DI(Dependency Injection,依賴注入)來實現的。比如對象A需要操作數據庫,以前我們總是要在A中自己編寫代碼來獲得一個Connection對象,有了 spring我們就只需要告訴spring,A中需要一個Connection,至於這個Connection怎麼構造,何時構造,A不需要知道)。而這些框架在使用的過程中會需要配置大量的xml,或者需要做很多繁瑣的配置。

  springboot框架是爲了能夠幫助使用spring框架的開發者快速高效的構建一個基於spirng框架以及spring生態體系的應用解決方案。它是對“約定優於配置”這個理念下的一個最佳實踐。因此它是一個服務於框架的框架,服務的範圍是簡化配置文件。

2.初步認識Spring Boot

  我們可以使用 https://start.spring.io

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

  爲了讓大家看到效果,我們使用spring mvc來構建一個web應用,而springboot幫我們簡化了非常多的邏輯使得我們非常容易去構建一個web項目。

  springboot提供了spring-boot-starter-web自動裝配模塊

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

  在當前項目下運行mvn spring-boot:run 或者直接運行main方法就可以啓動一個使用了嵌入式tomcat服務請求的web應用,但是我們沒有提供任何服務web請求的controller,所以訪問任何路徑都會返回一個springboot默認的錯誤頁面(whitelabel error page)

  所以,我們可以創建一個Controller來實現請求

@RestController
public class HelloController {

    @GetMapping("/say")
    public String sayHello(){
        return "hello Mic";
    }
}

  訪問http://localhost:8080/say 就可以獲得一個請求結果。 這樣就完成了一個非常簡單的web應用。 springboot是一個約定優於配置的產物,所以在快速構建web應用的背後,其實有很多的約定。

  1. 項目結構層面,靜態文件和頁面模版的存放位置變成了src/main/resources對應的子目錄下
  2. 自動嵌入tomcat作爲web容器對外提供http服務,默認使用8080端口監聽
  3. 自動裝配springmvc必要的組件

3.Spring Boot四大核心

EnableAutoConfiguration 自動裝配
Starter組件, 開箱即用
Actuator 監控
Spring Boot Cli 爲Spring Cloud 提供了Spring Boot 命令行功能

  那今天主要給大家講講Enable*這個註解

4.Enable* 註解的作用

  Enable是啓用的意思,相當於開啓某一個功能

  • EnableScheduling
  • EnableHystrix
  • EnableAsync
  • EnableAutoConfiguration
  • EnableWebMvc

  仍然是在spring3.1版本中,提供了一系列的@Enable開頭的註解,Enable主機應該是在JavaConfig框架上更進一步的完善,是的用戶在使用spring相關的框架是,避免配置大量的代碼從而降低使用的難度

  比如常見的一些Enable註解:EnableWebMvc,(這個註解引入了MVC框架在Spring 應用中需要用到的所有bean);

  比如說@EnableScheduling,開啓計劃任務的支持;

  找到EnableAutoConfiguration,我們可以看到每一個涉及到Enable開頭的註解,都會帶有一個@Import的註解。

5.深入分析Spring Boot中的自動裝配

  在Spring Boot中,不得不說的一個點是自動裝配,它是starter的基礎,也是Spring Boot的核心, 那什麼叫自動裝配?或者什麼叫裝配呢?

  回顧一下Spring Framework,它最核心的功能是IOC和AOP, IoC容器的主要功能是可以管理對象的生命週期。也就是bean的管理。我們把Bean對象託管到Spring Ioc容器的這個過程稱爲裝配,那什麼是自動裝配呢?我們慢慢來介紹

  首先大家看下這張圖,我們先不解釋。等把今天的內容講完,我們再回頭來通過這張圖來總結~

在這裏插入圖片描述

  自動裝配在SpringBoot是基於EnableAutoConfiguration來實現的。那麼在深入分析EnableAutoConfiguration之前,我們來看一下傳統意義上的裝配方式。

5.1簡單分析@Configuration

  Configuration這個註解大家應該有用過,它是JavaConfig形式的基於Spring IOC容器的配置類使用的一種註解。因爲SpringBoot本質上就是一個spring應用,所以通過這個註解來加載IOC容器的配置是很正常的。所以在啓動類裏面標註了@Configuration,意味着它其實也是一個IoC容器的配置類。

public class DemoClass {

    public void say(){
        System.out.println("sya hello ... ");
    }
}
@Configuration
@Import(UserClass.class)
public class DemoConfiguration {

    @Bean
    public DemoClass getDemoClass(){
        return new DemoClass();
    }
}
public class DemoConfigurationMain {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
        DemoClass bean = ac.getBean(DemoClass.class);
        bean.say();
    }
}

  這種形式就是通過註解的方式來實現IoC容器,傳統意義上的spring應用都是基於xml形式來配置bean的依賴關係。然後通過spring容器在啓動的時候,把bean進行初始化並且,如果bean之間存在依賴關係,則分析這些已經在IoC容器中的bean根據依賴關係進行組裝。

  直到Java5中,引入了Annotations這個特性,Spring框架也緊隨大流並且推出了基於Java代碼和Annotation元信息的依賴關係綁定描述的方式。也就是JavaConfig。

  從spring3開始,spring就支持了兩種bean的配置方式,一種是基於xml文件方式、另一種就是JavaConfig

Configuration的本質

  如果大家細心一點就會發現Configuration註解的本質是一個Component註解,這個註解會被AnnotationConfigApplicationContext加載,而AnnotationConfigApplicationContext是ApplicationContext的一個具體實現,表示根據配置註解啓動應用上下文。
  因此我們在Main方法中通過AnnotationConfigApplicationContext去加載JavaConfig後,可以得到Ioc容器中的bean的實例
  JavaConfig的方式在前面的代碼中已經演示過了,任何一個標註了@Configuration的Java類定義都是一個JavaConfig配置類。而在這個配置類中,任何標註了@Bean的方法,它的返回值都會作爲Bean定義註冊到Spring的IOC容器,方法名默認成爲這個bean的id

5.2簡單分析@ComponentScan

  ComponentScan這個註解是大家接觸得最多的了,相當於xml配置文件中的<context:component-scan>。 它的主要作用就是掃描指定路徑下的標識了需要裝配的類,自動裝配到spring的Ioc容器中。

  標識需要裝配的類的形式主要是:@Component、@Repository、@Service、@Controller這類的註解標識的類。

public static void main(String[] args) {
    ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    System.out.println("********************************");
    for (String beanName:beanDefinitionNames
         ) {
        System.out.println(beanName);
    }
}

5.3 Import註解

  import 註解是什麼意思呢? 聯想到xml形式下有一個<import resource/> 形式的註解,就明白它的作用了。import就是把多個分來的容器配置合併在一個配置中。在JavaConfig中所表達的意義是一樣的。爲了能更好的理解後面講的EnableAutoConfiguration,我們詳細的和大家來介紹下import註解的使用

方式一:直接填class數組

  我們先在不同的兩個package下創建對應的bean。
  比如:
在這裏插入圖片描述
  那麼DemoConfigurationMain中執行的代碼應該是加載不到demo2中的UserClass的
在這裏插入圖片描述
  這時我們可以通過@import來直接加載
在這裏插入圖片描述

或者
在這裏插入圖片描述

方式二:ImportSelector方式【重點】

  第一種方式如果導入的是一個配置類,那麼該配置類中的所有的都會加載,如果想要更加的靈活,動態的去加載的話可以通過Import接口的第二種使用方式,也就是ImportSelector這種方式,我們來看看怎麼使用。
LogService

public class LogService {
}

CacheService

public class CacheService {
}

GpDefineImportSelector

public class GpDefineImportSelector implements ImportSelector {
    /**
     * AnnotationMetadata 註解元數據
     * @param annotationMetadata
     * @return
     *      要被IOC容器加載的bean信息
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 我們可以基於註解元數據信息來動態的返回要加載的bean信息
        annotationMetadata
                .getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
        .forEach((k,v)->{
            System.out.println(annotationMetadata.getClassName());
            System.out.println("---> " + k+":" + String.valueOf(v));
        });

        return new String[]{CacheService.class.getName()};
    }
}

EnableDefineService

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(GpDefineImportSelector.class)
public @interface EnableDefineService {

    String[] packages() default "";

}

EnableDemoTest

@EnableDefineService()
@SpringBootApplication
public class EnableDemoTest {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(EnableDemoTest.class);//SpringApplication.run(EnableDemoTest.class,args);
        System.out.println(ac.getBean(CacheService.class));
        System.out.println(ac.getBean(LogService.class));
    }
}

方式三:ImportBeanDefinitionRegistrar方式

  這種方式和第二種方式很相似,同樣的要實現 ImportBeanDefinitionRegistrar 接口

public class GpImportDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 指定bean的定義信息
        RootBeanDefinition beanDefinition = new RootBeanDefinition(CacheService.class);
        RootBeanDefinition beanDefinition1 = new RootBeanDefinition(LogService.class);
        // 註冊一個bean
        beanDefinitionRegistry.registerBeanDefinition("cacheService1111",beanDefinition);
        beanDefinitionRegistry.registerBeanDefinition("cacheService2222",beanDefinition1);

    }
}

在這裏插入圖片描述
在這裏插入圖片描述

5.4 深入分析EnableAutoConfiguration原理

  瞭解了ImportSelectorImportBeanDefinitionRegistrar後,對於EnableAutoConfiguration的理解就容易一些了
  它會通過import導入第三方提供的bean的配置類:AutoConfigurationImportSelector
在這裏插入圖片描述
AutoConfigurationImportSelector

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        try {
// 加載META-INF/spring-autoconfigure-metadata.properties 下的元數據信息

            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 獲取候選加載的配置信息 META-INF/spring.factories
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重複的配置信息
            configurations = this.removeDuplicates(configurations);
// 排序
            configurations = this.sort(configurations, autoConfigurationMetadata);
            // 獲取 註解中配置的 exclusion 信息
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 檢查
            this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的信息
            configurations.removeAll(exclusions);
// 過濾,檢查候選配置類上的註解@ConditionalOnClass,如果要求的類不存在,則這個候選類會被過濾不被加載
            configurations = this.filter(configurations, autoConfigurationMetadata);
// 廣播事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 返回要被加載的類數組
return (String[])configurations.toArray(new String[configurations.size()]);
        } catch (IOException var6) {
            throw new IllegalStateException(var6);
        }
    }
}

  本質上來說,其實EnableAutoConfiguration會幫助springboot應用把所有符合@Configuration配置都加載到當前SpringBoot創建的IoC容器,而這裏面藉助了Spring框架提供的一個工具類SpringFactoriesLoader的支持。以及用到了Spring提供的條件註解@Conditional,選擇性的針對需要加載的bean進行條件過濾

5.5 SpringFactoriesLoader

  爲了給大家補一下基礎,我在這裏簡單分析一下SpringFactoriesLoader這個工具類的使用。它其實和java中的SPI機制的原理是一樣的,不過它比SPI更好的點在於不會一次性加載所有的類,而是根據key進行加載。

  首先,SpringFactoriesLoader的作用是從classpath/META-INF/spring.factories文件中,根據key來加載對應的類到spring IoC容器中。接下來帶大家實踐一下
  通過mybatis整合來看
  引入mybatis的依賴

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>

在這裏插入圖片描述

  查看 MybatisAutoConfiguration 裏面的源碼,發現在其中加載了SqlSessionFactory等信息。

通過實際案例來實現

  創建一個新的maven項目,引入相關的依賴

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.6.RELEASE</version>
    </dependency>
</dependencies>

創建一個bean類

public class GpCoreService {

    public void crudService(){
        System.out.println("gupao core service run ...");
    }
}

以及對應的配置類

@Configuration
public class GpConfiguration {

    @Bean
    public GpCoreService gpCoreService(){
        return new GpCoreService();
    }

}

然後打包
在這裏插入圖片描述
在另一個項目中導入該jar包

<dependency>
    <groupId>org.gupao.edu</groupId>
    <artifactId>Gp-Core</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

  通過下面代碼獲取依賴包中的屬性

  運行結果會報錯,原因是GuPaoCore並沒有被Spring的IoC容器所加載,也就是沒有被EnableAutoConfiguration導入

public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
    String[] beanDefinitionNames = run.getBeanDefinitionNames();
    System.out.println(run.getBean(GpCoreService.class));
}

解決方案

  在GuPao-Core項目resources下新建文件夾META-INF,在文件夾下面新建spring.factories文件,文件中配置,key爲自定配置類EnableAutoConfiguration的全路徑,value是配置類的全路徑

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupao.edu.GpConfiguration

  重新打包,重新運行SpringBootStudyApplication這個類。
  可以發現,我們編寫的那個類,就被加載進來了。

5.6深入理解條件過濾

  在分析AutoConfigurationImportSelector的源碼時,會先掃描spring-autoconfiguration-metadata.properties文件,最後在掃描spring.factories對應的類時,會結合前面的元數據進行過濾,爲什麼要過濾呢? 原因是很多的@Configuration其實是依託於其他的框架來加載的,如果當前的classpath環境下沒有相關聯的依賴,則意味着這些類沒必要進行加載,所以,通過這種條件過濾可以有效的減少@configuration類的數量從而降低SpringBoot的啓動時間。
  修改Gupao-Core

  在META-INF/增加配置文件,spring-autoconfigure-metadata.properties。

com.gupao.edu.GpConfiguration.ConditionalOnClass=com.gupao.edu.service.GpTestService

在這裏插入圖片描述

格式:自動配置的類全名.條件=值

上面這段代碼的意思就是,如果當前的classpath下存在TestClass,則會對GuPaoConfig這個Configuration進行加載
演示過程(spring-boot)

1.沿用前面spring-boot工程的測試案例,直接運行main方法,發現原本能夠被加載的GuPaoCore,發現在ioc容器中找不到了。
2.在當前工程中指定的包com.gupaoedu下創建一個TestClass以後,再運行上面這段代碼,程序能夠正常執行

好了~到此如果跟着一塊動手的話相信你對於SpringBoot的自動裝配應該有了不一樣的理解,有問題的歡迎留言交流!

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