SpringBoot 常用註解的原理和使用

2022-11-01 12:00:29

@AutoConfiguration

讀取所有jar包下的 /META-INF/spring.factories 並追加到一個 LinkedMultiValueMap 中。每一個url中記錄的檔案路徑如下:

file:/C:/Users/wangchao/apache-maven-3.5.0/repo/com/baomidou/mybatis-plus-boot-starter/3.5.1/mybatis-plus-boot-starter-3.5.1.jar!/META-INF/spring.factories

按照如下路徑檢視

// 1. @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    
}

// 2. AutoConfigurationImportSelector.class#selectImports()
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}

// 3. AutoConfigurationImportSelector.class#getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}


// 4. AutoConfigurationImportSelector.class#getCandidateConfigurations()
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;
}

// 5. org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

最終使用 loadSpringFactories(@Nullable ClassLoader classLoader) 方法讀取所有組態檔。

// 6. org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories()
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
			    // 讀取所有jar包下的 /META-INF/spring.factories
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

tomcat的自動設定內建於springboot的autoconfiguration中。參考tomcat的自動設定 https://www.cnblogs.com/zhaokejin/p/15626392.html

mybatis-plus的設定沒有被springboot包括。因此mybatis-stater中包含一個包mybatis-spring-boot-autoconfigure,這其中設定了需要自動設定的類。

因此我們也可以在自己的專案下新建 /META-INF/spring.factories ,並設定自動設定類。

@Import

@Import 用於匯入設定類或需要前置載入的類。被匯入的類會註冊為Bean,可直接作為Bean被參照。它的 value 屬性可以支援三種型別:

  • @Configuration 修飾的設定類、或普通類(4.2版本之後可以)。
  • ImportSelector 介面的實現。
  • ImportBeanDefinitionRegistrar 介面的實現。

@Import 的設定

@Configuration
@Import(value = {TestA.class, TestB.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class ConfigurationTest {

}

匯入一個普通類

package com.example.ssmpdemo.entity;

public class TestA {
    public void fun(){
        System.out.println("testA");
    }
}

匯入一個設定類

package com.example.ssmpdemo.entity;

import org.springframework.context.annotation.Configuration;

@Configuration
public class TestB {
    public void fun(){
        System.out.println("testB");
    }
}

通過實現 ImportSelector 介面

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.ssmpdemo.entity.TestC"};
    }
}

通過重寫 ImportBeanDefinitionRegistrarregisterBeanDefinitions 方法。

import com.example.ssmpdemo.entity.TestD;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition root = new RootBeanDefinition(TestD.class);
        registry.registerBeanDefinition("testD", root);
    }
}

@ConfigurationProperties

  • 支援常見的下劃線、中劃線和駝峰的轉換。支援物件引導。比如:user.friend.name 代表的是user物件中的friend物件中的name
  • 需要有set()方法
  • 只新增 @ConfigurationProperties(prefix = "xxx") 並不會生效,需要配合 @Configuration 讓容器識別到。
  • @EnableConfigurationProperties(value = ConfigData.class ) 會將value中指定的類註冊為Bean,可直接用 @AutoWired 參照。
  1. 定義一個類用來記錄所有欄位,並使用@ConfigurationProperties(prefix = "xxx")將資料注入到ConfigData中。
package com.example.ssmpdemo.entity;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 用來記錄Configuration的資料
 * @author wangc
 */
@Data
@ConfigurationProperties(value = "spring.datasource.druid")
public class ConfigData {
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}


# 對應的yml檔案
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:5506/ssmpdemo?serverTimezone=UTC
      username: root
      password: xxxx
  1. 使用@EnableConfigurationProperties(JDBCProperties.class)ConfigData 註冊為Bean,並提供給ConfigurationTest使用 。可將ConfigData作為引數注入到建構函式和普通函數中。

  2. 可以用以下方式參照被@ConfigurationProperties(value = "spring.datasource.druid")修飾的ConfigData

  • 可以直接把 ConfigData 當成Bean使用
        /**
         * 可直接被注入
         */
        @Autowired
        private ConfigData configData;
    
  • 可以用建構函式傳入進來
    @Data
    @Configuration
    @EnableConfigurationProperties(value = ConfigData.class )
    public class ConfigurationTest {
        private ConfigData configData2;
        /**
         * 作為建構函式的引數注入
         * @param data
         */
        ConfigurationTest(ConfigData data){
            this.configData2 = data;
        }
    
  • 也可以作為@Bean的方法函數的引數。只有當前類(ConfigurationTest)才可
        /**
         * 直接作為函數的引數
         * @param data
         * @return
         */
        @Bean(name = "configData2")
        HashMap<String, String> getBean(ConfigData data){
            return new HashMap<>(0);
        }
    
  • 可以省略ConfigData直接將欄位注入到返回結果中。
    @Bean
    @ConfigurationProperties(value = "spring.datasource.druid")
    HashMap<String, String> getBean2(ConfigData data){
        // 會自動為hashMap賦值,或使用set方法為物件賦值
        return new HashMap<>();
    }
    

EnableConfigurationProperties註解的內部如下,它匯入了一個實現了 ImportBeanDefinitionRegistrar 介面的類。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerInfrastructureBeans(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		// 獲得@EnableConfigurationProperties的value指向的物件,並註冊。
		getTypes(metadata).forEach(beanRegistrar::register);
	}