SpringBoot
1. SpringBoot 項目快速搭建
- 創建一個
maven
工程(jar) - 導入
SpringBoot
相關的依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 編寫一個主程序,啓動
SpringBoot
應用
package com.wangzhao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author wangzhao
* @date 2020/6/29 17:43
*/
// @SpringBootApplication 來標註一個主程序類,說明這是一個Spring Boot應用
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldMainApplication.class, args);
}
}
- 編寫
Controller
層
package com.wangzhao.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wangzhao
* @date 2020/6/29 17:47
*/
@RestController
public class HelloController {
@RequestMapping("/hello")
public String sayHello() {
return "Hello World!";
}
}
- 運行主程序測試
6. 項目整體結構
如上,便成功啓動了一個SpringBoot
的項目,可以看到,相比於Spring
而言,SpringBoot
使用非常簡單,快速,並且我們幾乎沒有進行任何配置文件的填寫。
2. HelloWorld 探究
2.1 pom.xml
2.1.1 父項目
Hello World
其所依賴的父項目爲:spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
我們可以看一下spring-boot-starter-parent
做了哪些事情
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
- 使用
UTF-8
格式編碼 - 定義了
Java
編譯版本爲1.8
。
除此之外,spring-boot-starter-parent
其所依賴的父項目爲spring-boot-dependencies
:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<properties>
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.6.4</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.11.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<bitronix.version>2.1.4</bitronix.version>
......
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.1.12.RELEASE</version>
</dependency>
......
</dependencies>
</dependencyManagement>
其相當於SpringBoot
的版本仲裁中心;
他提供了jar
包的版本管理,以及Spring
框架和其他第三方組件jar
包的依賴管理。
2.1.2 導入的依賴
回到我們項目的pom
文件中,導入了該依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
讓我們進入到spring-boot-starter-web
的pom
文件中,可以看到導入的都是web
相關的模塊,並且還內置了一個tomcat
。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.12.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.12.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.12.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.13.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.13.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
spring-boot-starter
:spring-boot
場景啓動器,其幫我們導入了相關場景模塊正常運行所依賴的組件;
Spring Boot
將所有的功能場景都抽取出來,做成一個個的starter
(啓動器),只需要在項目裏面引入這些starter
相關場景的所有依賴都會導入進來。要用什麼功能,就導入什麼場景的啓動器。
2.2 主啓動類
// @SpringBootApplication 來標註一個主程序類,說明這是一個Spring Boot應用
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldMainApplication.class, args);
}
}
@SpringBootApplication
:SpringBoot
應用標註在某個類上說明這個類是SpringBoot
的主配置類,SpringBoot
就應該運行這個類的main()
方法啓動SpringBoot
應用。
2.2.1 @SpringBootApplication
進入到@SpringBootApplication
內,觀察其做了哪些工作:
package org.springframework.boot.autoconfigure;
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 根據class來排除特定的類,使其不能加入spring容器,傳入參數value類型是class類型。
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 根據classname 來排除特定的類,使其不能加入spring容器,傳入參數value類型是class的全類名字符串數組。
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 指定掃描包,參數是包名的字符串數組。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 掃描特定的包,參數類似是Class類型數組。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
可以看到@SpringBootApplication
是一個複合註解,包括@SpringBootConfiguration
,@EnableAutoConfiguration
和@ComponentScan
。
@SpringBootConfiguration
:SpringBoot
的配置類,標註在某個類上,表示這是一個SpringBoot
的配置類。
package org.springframework.boot;
// 配置類的作用等同於配置文件,配置類也是容器中的一個對象
@Configuration
public @interface SpringBootConfiguration {
}
@EnableAutoConfiguration
:開啓自動配置功能,以前由我們需要配置的東西,現在由SpringBoot
幫我們自動配置,這個註解就是Springboot
能實現自動配置的關鍵。@ComponentScan
:這個註解是組件掃描這個是我們最熟悉的註解,即使沒有使用過註解也經常在Spring
的配置文件中使用過<context:component-scan base-package="com.xxx.xxx"/>
, 組件掃描就是掃描指定的包下的類,並加載符合條件的組件。
3. @EnableAutoConfiguration
package org.springframework.boot.autoconfigure;
// 自動配置包
@AutoConfigurationPackage
// Spring的底層註解@Import,給容器中導入一個組件;
// 導入的組件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)
// 告訴SpringBoot開啓自動配置功能,這樣自動配置才能生效。
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 返回不會被導入到 Spring 容器中的類
Class<?>[] exclude() default {};
// 返回不會被導入到 Spring 容器中的類名
String[] excludeName() default {};
}
Spring
中有很多以Enable
開頭的註解,其作用就是藉助@Import
來收集並註冊特定場景相關的Bean
,並加載到IOC
容器。@EnableAutoConfiguration就是藉助@Import來收集所有符合自動配置條件的bean定義,並加載到IoC容器。
@EnableAutoConfiguration
就是藉助@Import
來收集所有符合自動配置條件的Bean
定義,並加載到IOC
容器。
3.1 @AutoConfigurationPackage
@AutoConfigurationPackage
:自動配置包,它也是一個組合註解,其中最重要的註解是@Import(AutoConfigurationPackages.Registrar.class)
,它是Spring
框架的底層註解,它的作用就是給容器中導入某個組件類,例如@Import(AutoConfigurationPackages.Registrar.class)
,它就是將Registrar
這個組件類導入到容器中,可查看Registrar
類中registerBeanDefinitions
方法,這個方法就是導入組件類的具體實現。
package org.springframework.boot.autoconfigure;
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 將註解標註的元信息傳入,獲取到相應的包名
register(registry, new PackageImport(metadata).getPackageName());
}
可以看到AnnotationMetadata
(註解標註注的元信息中包含了使用了哪些註解,相應的註解作用在哪個類上)
我們對new PackageImport(metadata).getPackageName()
進行檢索,看看其結果是什麼?
因此可以得知使用@AutoConfigurationPackage
註解就是將主程序類所在包及所有子包下的組件到掃描到Spring
容器中。
3.1.1 AutoConfigurationImportSelector
@Import({AutoConfigurationImportSelector.class})
:將AutoConfigurationImportSelector
這個類導入到Spring
容器中,AutoConfigurationImportSelector
可以幫助Springboot
應用將所有符合條件的@Configuration
配置都加載到當前SpringBoot
創建並使用的IOC
容器(ApplicationContext
)中。
AutoConfigurationImportSelector
是 ImportSelector
接口的實現類,而 ImportSelector
接口中的selectImports
方法將返回的全限定類名對應的類交給 Spring
容器管理。
其不光實現了ImportSelector
接口,還實現了很多其它的Aware
接口,分別表示在某個時機會被回調。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
我們可以知道,所有的aware
都優先於selectImports
方法執行,也就是說selectImports
方法最後執行,那麼在它執行的時候所有需要的資源都已經獲取到了(AutoConfigurationImportSelector
的四個成員變量)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 獲得自動配置元信息,需要傳入beanClassLoader這個類加載器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
深入研究 loadMetadata 方法
// 文件中爲需要加載的配置類的類路徑
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 讀取spring-boot-autoconfigure-2.1.12.RELEASE.jar包中
// spring-autoconfigure-metadata.properties的信息生成urls枚舉對象
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
//解析urls枚舉對象中的信息封裝成properties對象並加載
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils
.loadProperties(new UrlResource(urls.nextElement())));
}
//根據封裝好的properties對象生成AutoConfigurationMetadata對象返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
深入研究 getAutoConfigurationEntry 方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 將註解元信息封裝成註解屬性對象
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取到配置類的全路徑字符串集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 刪除重複項
configurations = removeDuplicates(configurations);
// 應用 exclusion 屬性
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 應用過濾器 AutoConfigurationImportFilter,
// 對於 spring boot autoconfigure,定義了一個需要被應用的過濾器 :
// org.springframework.boot.autoconfigure.condition.OnClassCondition,
// 此過濾器檢查候選配置類上的註解@ConditionalOnClass,如果要求的類在classpath
// 中不存在,則這個候選配置類會被排除掉
configurations = filter(configurations, autoConfigurationMetadata);
// 現在已經找到所有需要被應用的候選配置類
// 廣播事件 AutoConfigurationImportEvent
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
深入 getCandidateConfigurations 方法
這個方法中有一個重要方法loadFactoryNames
,這個方法是讓SpringFactoryLoader
去加載一些組件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 這個方法需要傳入兩個參數getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
// getSpringFactoriesLoaderFactoryClass()這個方法返回的是EnableAutoConfiguration.class
// getBeanClassLoader()這個方法返回的是beanClassLoader(類加載器)
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;
}
繼續點開loadFactory
方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//獲取出入的鍵
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//如果類加載器不爲null,則加載類路徑下spring.factories文件,將其中設置的配置類的全路徑信息封裝 爲Enumeration類對象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//循環Enumeration類對象,根據相應的節點信息生成Properties對象,通過傳入的鍵獲取值,在將值切割爲一個個小的字符串轉化爲Array,方法result集合中
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
}
}
3.2 自動配置原理
SpringBoot
啓動的時候加載主配置類,開啓了自動配置功能 @EnableAutoConfiguration@EnableAutoConfiguration
作用:
- 利用
EnableAutoConfigurationImportSelector
給容器中導入一些組件 - 可以查看
selectImports()
方法的內容:
List<String> configurations = this.getCandidateConfigurations(annotationMetadata,attributes);
- 上面代碼的作用是獲取候選的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames();
// 掃描所有 jar 包類路徑下 META-INF/spring.factories
// 把掃描到的這些文件的內容包裝成 properties 對象
// 從properties中獲取到 EnableAutoConfiguration.class類(類名對應的值),把他們添加到容器中
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
......
每一個這樣的 xxxAutoConfiguration
類都是容器中的一個組件,都加入到容器之中。用它們來做自動配置。
- 每一個自動配置類進行自動配置功能
- 以
HttpEncodingAutoConfiguration
(Http
編碼自動配置)爲例解釋自動配置原理
// 表示這是一個配置類,和以前編寫的配置文件一樣,也可以給容器中添加組件
@Configuration
// 啓動指定類的ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;
@EnableConfigurationProperties({HttpEncodingProperties.class})
// Spring底層@Conditional註解,根據不同的條件,如果滿足指定的條件,整個配置類裏面的配置就會生效。
// 判斷當前應用是否是web應用,如果是,當前配置類生效。並把HttpEncodingProperties加入到 ioc 容器中
@ConditionalOnWebApplication
// 判斷當前項目有沒有這個CharacterEncodingFilter : SpringMVC中進行亂碼解決的過濾器
@ConditionalOnClass({CharacterEncodingFilter.class})
// 判斷配置文件中是否存在某個配置 spring.http.encoding.enabled 如果不存在,判斷也是成立的
// matchIfMissing = true 表示即使我們配置文件中不配置spring.http.encoding.enabled=true,也是默認生效的
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
// 它已經和SpringBoot配置文件中的值進行映射了
private final HttpEncodingProperties properties;
// 只有一個有參構造器的情況下,參數的值就會從容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //給容器中添加一個組件,這個組件中的某些值需要從properties中獲取
@ConditionalOnMissingBean({CharacterEncodingFilter.class}) //判斷容器中沒有這個組件
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
根據當前不同的條件判斷,決定這個配置類是否生效。
一旦這個配置類生效,這個配置類就會給容器中添加各種組件;這些組件的屬性是從對應的properties
類中獲取的,這些類裏面的每一個屬性又是和配置文件綁定的。
# 我們能配置的屬性都是來源於這個功能的properties類
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
- 所有在配置文件中能配置的屬性都是在
xxxProperties
類中封裝着,配置文件能配置什麼就可以參照某個功能對應的這個屬性類。
// 從配置文件中獲取指定的值和bean的屬性進行綁定
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
3.3 精髓
-
SpringBoot 啓動會加載大量的自動配置類
-
我們看我們需要實現的功能有沒有
SpringBoot
默認寫好的自動配置類 -
我們再來看這個自動配置類中到底配置了哪些組件;(只要我們有我們要用的組件,我們就不需要再來配置了)
-
給容器中自動配置類添加組件的時候,會從
properties
類中獲取某些屬性,我們就可以在配置文件中指定這些屬性的值。
xxxAutoConfiguration
:自動配置類,用於給容器中添加組件從而代替之前我們手動完成大量繁瑣的配置。
xxxProperties
: 封裝了對應自動配置類的默認屬性值,如果我們需要自定義屬性值,只需要根據xxxProperties
尋找相關屬性在配置文件設值即可。