深入理解springboot的自動注入

良哥說技術發表於2022-06-25

一、開篇

  在平時的開發過程中用的最多的莫屬springboot了,都知道springboot中有自動注入的功能,在面試過程中也會問到自動注入,你知道自動注入是怎麼回事嗎,springboot是如何做到自動注入的,自動注入背後的原理是什麼,今天來分析下springboot的自動注入,希望這篇文章可以解除大家心中的疑惑。

二、詳述

2.1、什麼是自動注入

  天天將自動注入,你真正明白自動注入是怎麼回事嗎?舉個例子來說,我們要在springboot中使用mybatis,之前的做法是什麼?

  1、引入依賴;

  2、在配置檔案中配置配置類;

  3、寫mybatis的配置檔案或註解;

  在springboot中這個步驟就減少了,減少的是第二步,不用再寫一堆配置類了,步驟簡化為:

  1、引入依賴;

  2、寫mybatis的配置檔案或註解;

  也就是說無需再搞配置類了,就比如之前的”SqlSessionFactoryBean“,現在不用配置了,springboot為我們做了這些工作,現在看springboot引入mybatis需要加入的依賴,

<!--mybatis的依賴 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>

        </dependency>

        <!--mysql的驅動程式-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

  我們加入mybatis和資料庫的驅動依賴,因為mybatis要使用資料庫連線,所以這裡少不了mysql的資料庫驅動。重點看mybatis的這個依賴和之前的是不一樣的,這個是”mybatis-spring-boot-starter“,再看這個依賴中都有哪些jar,

  除了常見的mybatis及mybatis-spring還有一個mybatis-spring-boot-autoconfigure,這個就是今天的主角。

2.2、springboot讀取spring.facotries檔案(可跳過該節)

  前邊說到今天的主角是”mybatis-spring-boot-autoconfigure“,其實還有很多這樣的依賴,大多數第三方自己實現的都會有這樣一個依賴比如,前邊自己實現的starter中就有這樣一個”customer-spring-boot-autoconfigurer“,還有很多都是springboot自己實現的,所以無需這樣的依賴。

  要想知道springboot是如何進行自動注入的,唯一的方式是debug,現在開始debug之旅吧。

2.2.1、SpringApplication構造方法

  springboot的啟動很簡單,就是下面這樣一行程式碼

SpringApplication.run(BootServer.class);

  要跟著這樣一行程式碼走下去,追蹤到了這樣一句,

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

  可以看的會new一個SpringApplication的例項,然後再呼叫其run方法,先看下new方法做了什麼,最終呼叫的是下面的構造方法,

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
         //設定初始化器,很重要
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         //設定監聽器,很重要
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

  我在上面 做了註釋,重點看註釋部分的程式碼;

2.2.2、setInitializers()方法

  該方法從方法名上看是要設定初始化器,其中getSpringFactoriesInstances(ApplicationContextInitializer.class)是重點。其方法定義如下,

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
        //SpringFactoriesLoader.loadFactoryNames是重點
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

  看SpringFactoriesLoader.loadFactoryNames方法,

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
         //loadSpringFactories(classLoader)方法是重點
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

  把斷點放在loadSpringFactroies方法內,

  從上面的debug結果可以看到使用AppClassLoader讀取”FACTORIES_RESOURCE_LOCATION“處的資源,AppClassLoader大家都很熟悉,就說應用類載入器,常量”FACTORIES_RESOURCE_LOCATION“指的是,

/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  jar下的”META-INF/spring.factories“檔案,也就是說要讀取專案中jar包中的”META-INF/spring.factories“檔案的內容,我在spring-boot-2.3.3.RELEASE.jar中找到這樣一個檔案,僅截個圖,詳細內容可以自己檢視,

  可以看到是一些列的鍵值對,我們看下loadSpringFactories方法最後的返回值,

  這個返回值是,專案中所有jar下META-INF/spring.factories檔案中的鍵值對組成的map。回到loadFactoryNames方法處

  該方法需要的是key為”org.springframework.context.ApplicationContextInitializer“的value,該value的值有這樣7個

這樣我們把setInitializers方法就分析完了,其主要就是從jar包中的META-INF/spring.factories檔案中獲取org.springframework.context.ApplicationContextInitializer對應的值。下面看setListeners方法

2.2.3、setListeners()方法

  該方法和setInitializers方法是類似的,

  重點是其引數不一樣,該方法的引數是ApplicationListener.class,也就是要找出org.springframework.context.ApplicationListener在spring.factories中的配置,

  本人核實過這些的確是從spring.factories檔案中讀取的,和其內容是一致的。

寫到這裡其實和自動注入沒有關係,如果說有關係的話是,這裡認識了一個關鍵的類”SpringFactoriesLoader“,該類的作用就是讀取jar包中META-INF/spring.facotries檔案的內容。在後邊的自動注入中還會出現該類的影子。繼續向前。

2.3、自動注入的原理

2.3.1、@SpringBootApplication註解  

在啟動springboot程式的時候在程式的入口都會有寫上@SpringBootApplication的註解,

package com.my.template;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * 啟動類
 * @date 2022/6/3 21:32
 */
@SpringBootApplication
public class BootServer {
    public static void main(String[] args) {
        try {
            SpringApplication.run(BootServer.class);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  看下該註解的定義,

  在該註解上還有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三個註解,今天重點看@EnableAutoConfiguration註解。

2.3.2、@EnableAutoConfiguration註解

  該註解便是自動注入的核心註解,

  重點是該註解上的下面這句話,

@Import(AutoConfigurationImportSelector.class)

  看下AutoConfigurationImportSelector類,該類中有這樣一個方法,和自動注入是相關的,

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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類又出現了,還是很熟悉的loadFactoryNames方法,這次的方法引數是getSpringFactoriesLoaderFactoryClass()方法,

/**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

  所以SpringFactoriesLoader.loadFactoryNames是要從META-INF/spring.factories中獲取key為”org.springframework.boot.autoconfigure.EnableAutoConfiguration“的value,這裡可以看到有很多,從中還可以找到我自定義的和myatis的。

也就是說要把這些配置類加到spring的容器中。現在有個問題這些配置都會生效嗎?

2.3.3、這些配置類都會生效嗎?

  上面說到自動配置會載入很多的配置類,但是這些類都會生效嗎?答案是不會的,只會在特定情況下生效,以MybatisAutoConfiguration為例,

  可以看的該類上有很多註解,

  @ConditionalOnClass,當類路徑中存在某個類標識該註解的類才會生效,也就是隻有存在SqlSessionFactory、SqlSessionFactoryBean才會解析MybatisAutoConfiguration類。換句話說,要有mybatis、mybatis-spring的jar包。

  @ConditionaleOnSigleCanidate,需要一個單例bean

  @EnableConfigurationProperties  讀取配置檔案,也就是application.properites

  @AutoConfigureAfter  自動配置在某個類之後

現在我們知道了一個XXAutoConfiguration類是否會生效還要看其上面的註解是怎麼定義的。

三、總結

  本文主要分析了springboot的自動注入原理,

  1、註解@SpringBootApplication中含有三個註解,其中@EnabelAutoConfiguration和自動配置有關;

  2、@EnableAutoConfiguration會讀取所有jar下META-INF/spring.factories檔案的內容,獲取”org.springframework.boot.autoconfigure.EnableAutoConfiguration“的配置,把這些配置注入到容器;

  3、@EnableAutoConfiguration注入的類是否生效,需要看其上面的註解,主要配合@ConditionaleXXX註解使用;

歡迎轉發、關注。

推薦閱讀

我的第一個springboot starter

springboot引入mybatis遇到的坑

springboot多環境下如何進行動態配置

做了這些年開發,今天第一次梳理了這三種常用的變數

 深入理解springboot的自動注入

相關文章