SpringBoot自動裝配
在沒有使用SpringBoot之前,使用ssm時配置redis需要在XML中配置埠號,地址,賬號密碼,連線池等等,而使用了SpringBoot後只需要在application.yml或application.properties中配置資訊,然後在pom檔案新增一個Redis的start就可以用了
SpringBootApplication註解
什麼是自動裝配,也就是說幫你把需要的類自動新增到Spring容器中
只要是一個SpringBoot專案肯定有這樣一個類
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(TokenApplication.class, args);
}
}
而自動裝配是在@SpringBootApplication
這個註解中實現的,點進去首先能看到這個註解上還有三個類
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
其中SpringBootConfiguration上還有一個註解@Configuration
也就是說明其實SpringBootApplication也是一個配置類
@ComponentScan用於掃描需要被Spring管理的類,這也就是為什麼寫的類需要在SpringBoot啟動類同級或在同級下的子包中
@EnableAutoConfiguration點進去發現它上面有一個特殊的註解@Import(AutoConfigurationImportSelector.class)
@Import註解的作用是將指定類新增到Spring容器中成為一個Bean
而在AutoConfigurationSelector類中有自動裝配的實現
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
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);
}
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
//獲取註解的名稱作為key
String name = getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
+ " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}
其中我們關注與返回值相關的程式碼,也就是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;
}
繼續檢視loadFactoryNames方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//從META-INF/spring.factories中獲取配置檔案
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
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);
}
}
其中需要解釋一下的是:MultiValueMap[介面],它是一個特殊的Map,由SpringBoot自己寫的,檢視它的實現類LinkedMultiValueMap
private final Map<K, List<V>> targetMap;
通常我們使用的Map是一個<K,V>結構的,而它這個Map是一個<K,V V V ...>結構的
首先SpringBoot會從快取中嘗試獲取(其實也就是個map,不過有點特殊),如果獲取不到,那就一次將全部內容讀取出來,然後以K V V V...的形式存放到類中
那麼META-INF/spring.factories這個檔案在哪呢?
它裡面的內容是這樣的
以類的全限定名作為Key,其他類的全限定名作為Value
那到現在還是一頭霧水,讀到了這個有什麼用呢?我們拿常見的Redis來看看
點進RedisAutoConfiguration看看
發現裡面全是報錯,因為我還沒有匯入Redis的start,當我在pom檔案中新增redis的依賴後
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
發現不報錯了,使用RedisTemplate存取值也可以了,這裡就不再演示存取值
上面這個類主要看類上面的註解,主要的兩個為:ConditionalOnClass
和EnableConfigurationProperties
ConditionalOnClass
@ConditionalOnClass
的作用是當前專案的classpath中存在某個類在會例項化這個類為bean,Spring還提供了其他類似的註解
那麼毫無疑問pom中匯入的那個依賴中肯定有一個類或介面叫做RedisOperations
,點進去檢視它的包路徑
package org.springframework.data.redis.core;
我們去匯入的包中找一找
EnableConfigurationProperties
@EnableConfigurationProperties
註解是使@ConfigurationProperties
註解的類生效,點進註解上的類
@ConfigurationProperties
註解的作用是可以將引數的配置設定在application配置檔案中,我們在application配置檔案中配置的引數都配置類中的欄位,要不然這些引數那來的?
那麼現在SpringBoot自動裝配的大致流程就完成了
- 讀取META-INF/spring.factories檔案
- 將掃描出的類進行判斷
- 如果符合類上的
@ConditionalOnxxxx
註解就將類新增到Spring容器中
如何自定義一個Start
現在知道了SpringBoot是如何自動裝配的,掃描MEAT-INF下spring.factories檔案,key為:EnableAutoConfiguration,為什麼key為EnableAutoConfiguration呢?在上面的程式碼中,掃描的以@EnableAutoConfiguration註解獲取名稱作為key
首先建立一個Maven專案,匯入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.0.0.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
之後在resources資料夾下建立META-INF/spring.factories檔案
然後建立配置類 當程式碼沒有寫完時@ConfigurationProperties會報錯:沒有開啟這個配置類,可以暫時不管
建立服務類
建立自動注入的配置類
最後在spring.factories中新增自動裝配類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
\com.jame.UserServiceAutoConfiguration //這裡替換成自己的類全路徑
整個專案結構如下
最後將maven專案 clean install 打包到當前maven倉庫中
在target輸出資料夾下開啟cmd 直接在路徑框中輸入cmd直接開啟當前位置
輸入命令
mvn install:install-file -Dfile=start-user-1.0-SNAPSHOT.jar -DgroupId=com.jame -DartifactId=user-spring-boot-start -Dversion=1.0 -Dpackaging=jar
- -Dfile 檔名
- -DgroupId 就是groupId
- -DartifactId 專案名稱,可以不和檔名一樣
- -Dversion 版本號
- -Dpackaging打包方式
完成後新建個SpringBoot專案來測試
匯入剛才打包的專案
配置引數
建立一個Controller來測試
這裡使用@Autowired idea可能會提示錯誤,說找不到對應的型別,這個是idea的問題
如果不想看著難受可以設定:Setting->Editor->inspections->Spring Core->Core->AutoWring for bean class 將Error設定為Waring
最後訪問測試: