Spring Boot 2 原理
springboot幫我們做了什么
通常搭建一個(gè)基于spring的web應(yīng)用蛮瞄,我們需要做以下工作:
pom文件中引入相關(guān)jar包庇茫,包括spring蕊玷、springmvc痘儡、redis爽航、mybaits横浑、log4j、mysql-connector-java 等等相關(guān)jar ...
配置web.xml腻暮,Listener配置彤守、Filter配置、Servlet配置哭靖、log4j配置具垫、error配置 ...
配置數(shù)據(jù)庫(kù)連接、配置spring事務(wù)
配置視圖解析器
開(kāi)啟注解试幽、自動(dòng)掃描功能
配置完成后部署tomcat筝蚕、啟動(dòng)調(diào)試
......
- 搭個(gè)初始項(xiàng)目不一會(huì)就一個(gè)小時(shí)甚至半天過(guò)去了。而用springboot后铺坞,一切都變得很簡(jiǎn)便快速起宽。下來(lái)我們來(lái)一步步分析springboot的起步依賴與自動(dòng)配置這兩個(gè)核心原理。
起步依賴
1. 引入jar
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--mybatis 開(kāi)發(fā)包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!--springboot web模塊支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid 的數(shù)據(jù)源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
spring-boot-starter-web包自動(dòng)幫我們引入了web模塊開(kāi)發(fā)需要的相關(guān)jar包济榨,
mybatis-spring-boot-starter幫我們引入了dao開(kāi)發(fā)相關(guān)的jar包坯沪。
spring-boot-starter-xxx是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter擒滑。
- 可以看出在這個(gè)mybatis-spring-boot-starter 中腐晾,并沒(méi)有任何源碼叉弦,只有一個(gè)pom文件,它的作用就是幫我們引入了相關(guān)jar包藻糖。
2. 配置數(shù)據(jù)源
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
dbcp2:
min-idle: 5
initial-size: 5
max-total: 5
max-wait-millis: 200
- stater機(jī)制幫我們完成了項(xiàng)目起步所需要的的相關(guān)jar包淹冰。那問(wèn)題又來(lái)了,傳統(tǒng)的spring應(yīng)用中不是要在application.xml中配置很多bean的嗎巨柒,比如dataSource的配置榄棵,transactionManager的配置 ... springboot是如何幫我們完成這些bean的配置的?下面我們來(lái)分析這個(gè)過(guò)程
自動(dòng)配置
基于java代碼的bean配置
- 以mybatis為例潘拱,在上面的截圖中,我們發(fā)下mybatis-spring-boot-starter這個(gè)包幫我們引入了mybatis-spring-boot-autoconfigure這個(gè)包拧略,如下圖:
熟悉@Configuration&@Bean這兩個(gè)bean的同學(xué)或許已經(jīng)知道了芦岂。這兩個(gè)注解一起使用就可以創(chuàng)建一個(gè)基于java代碼的配置類,可以用來(lái)替代相應(yīng)的xml配置文件垫蛆。
@Configuration注解的類可以看作是能生產(chǎn)讓Spring IoC容器管理的Bean實(shí)例的工廠禽最。
@Bean注解告訴Spring,一個(gè)帶有@Bean的注解方法將返回一個(gè)對(duì)象袱饭,該對(duì)象應(yīng)該被注冊(cè)到spring容器中川无。
傳統(tǒng)的基于xml的bean配置方法如下:
<beans>
<bean id = "car" class="com.itpsc.Car">
<property name="wheel" ref = "wheel"></property>
</bean>
<bean id = "wheel" class="com.itpsc.Wheel"></bean>
</beans>
- 相當(dāng)于用基于java代碼的配置方式:
@Configuration
public class Conf {
@Bean
public Car car() {
Car car = new Car();
car.setWheel(wheel());
return car;
}
@Bean
public Wheel wheel() {
return new Wheel();
}
}
- 所以上面的MybatisAutoConfiguration這個(gè)類,自動(dòng)幫我們生成了SqlSessionFactory這些Mybatis的重要實(shí)例并交給spring容器管理虑乖,從而完成bean的自動(dòng)注冊(cè)懦趋。
自動(dòng)配置條件依賴
- 從MybatisAutoConfiguration這個(gè)類中使用的注解可以看出,要完成自動(dòng)配置是有依賴條件的疹味。
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
//....
}
這些是springboot特有的仅叫,常見(jiàn)的條件依賴注解有:
@ConditionalOnBean,僅在當(dāng)前上下文中存在某個(gè)bean時(shí)糙捺,才會(huì)實(shí)例化這個(gè)Bean诫咱。
@ConditionalOnClass,某個(gè)class位于類路徑上洪灯,才會(huì)實(shí)例化這個(gè)Bean坎缭。
@ConditionalOnExpression,當(dāng)表達(dá)式為true的時(shí)候签钩,才會(huì)實(shí)例化這個(gè)Bean掏呼。
@ConditionalOnMissingBean,僅在當(dāng)前上下文中不存在某個(gè)bean時(shí)铅檩,才會(huì)實(shí)例化這個(gè)Bean哄尔。
@ConditionalOnMissingClass,某個(gè)class在類路徑上不存在的時(shí)候柠并,才會(huì)實(shí)例化這個(gè)Bean岭接。
@ConditionalOnNotWebApplication富拗,不是web應(yīng)用時(shí)才會(huì)實(shí)例化這個(gè)Bean。
@AutoConfigureAfter鸣戴,在某個(gè)bean完成自動(dòng)配置后實(shí)例化這個(gè)bean啃沪。
@AutoConfigureBefore,在某個(gè)bean完成自動(dòng)配置前實(shí)例化這個(gè)bean窄锅。
所以要完成Mybatis的自動(dòng)配置创千,需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個(gè)類入偷,需要存在DataSource這個(gè)bean且這個(gè)bean完成自動(dòng)注冊(cè)追驴。
進(jìn)入DataSourceAutoConfiguration這個(gè)類,可以看到這個(gè)類屬于這個(gè)包:
org.springframework.boot.autoconfigure.jdbc
這個(gè)包又屬于spring-boot-autoconfigure-2.0.3.RELEASE.jar這個(gè)包疏之,自動(dòng)配置這個(gè)包幫們引入了jdbc殿雪、kafka、logging锋爪、mail丙曙、mongo等包。很多包需要我們引入相應(yīng)jar后自動(dòng)配置才生效其骄。
bean參數(shù)獲取
到此我們已經(jīng)知道了bean的配置過(guò)程亏镰,但是還沒(méi)有看到springboot是如何讀取yml或者properites配置文件的的屬性來(lái)創(chuàng)建數(shù)據(jù)源的?
在DataSourceAutoConfiguration類里面拯爽,我們注意到使用了EnableConfigurationProperties這個(gè)注解索抓。
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
...
}
- DataSourceProperties中封裝了數(shù)據(jù)源的各個(gè)屬性,且使用了注解ConfigurationProperties指定了配置文件的前綴毯炮。
@ConfigurationProperties(
prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
...
}
@EnableConfigurationProperties與@ConfigurationProperties這兩個(gè)注解有什么用呢纸兔?
@ConfigurationProperties注解的作用是把yml或者properties配置文件轉(zhuǎn)化為bean。
@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效否副。如果只配置@ConfigurationProperties注解汉矿,在spring容器中是獲取不到y(tǒng)ml或者properties配置文件轉(zhuǎn)化的bean的。
bean發(fā)現(xiàn)
springboot默認(rèn)掃描啟動(dòng)類所在的包下的主類與子類的所有組件备禀,但并沒(méi)有包括依賴包的中的類洲拇,那么依賴包中的bean是如何被發(fā)現(xiàn)和加載的?
我們通常在啟動(dòng)類中加@SpringBootApplication這個(gè)注解曲尸,點(diǎn)進(jìn)去看
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}
實(shí)際上重要的只有三個(gè)Annotation:
@Configuration(@SpringBootConfiguration里面還是應(yīng)用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
@Configuration的作用上面我們已經(jīng)知道了赋续,被注解的類將成為一個(gè)bean配置類。
@ComponentScan的作用就是自動(dòng)掃描并加載符合條件的組件另患,比如@Component和@Repository等纽乱,最終將這些bean定義加載到spring容器中。
@EnableAutoConfiguration 這個(gè)注解的功能很重要昆箕,借助@Import的支持鸦列,收集和注冊(cè)依賴包中相關(guān)的bean定義租冠。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
如上源碼,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import這兩個(gè)注解薯嗤。@AutoConfigurationPackage的作用就是自動(dòng)配置的包顽爹,@Import導(dǎo)入需要自動(dòng)配置的組件。
進(jìn)入@AutoConfigurationPackage骆姐,發(fā)現(xiàn)也是引入了@Import注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, new String[]{(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()});
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
new AutoConfigurationPackages.PackageImport(metadata)
這兩句代碼的作用就是加載啟動(dòng)類所在的包下的主類與子類的所有組件注冊(cè)到spring容器镜粤,這就是前文所說(shuō)的springboot默認(rèn)掃描啟動(dòng)類所在的包下的主類與子類的所有組件。
那問(wèn)題又來(lái)了玻褪,要搜集并注冊(cè)到spring容器的那些beans來(lái)自哪里肉渴?
進(jìn)入 AutoConfigurationImportSelector類
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final String[] NO_IMPORTS = new String[0];
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if(!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
...
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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方法調(diào)用loadSpringFactories方法從所有的jar包中讀取META-INF/spring.factories文件信息。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap)cache.get(classLoader);
if(result != null) {
return result;
} else {
try {
Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result1 = new LinkedMultiValueMap();
while(ex.hasMoreElements()) {
URL url = (URL)ex.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry entry = (Entry)var6.next();
List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result1.addAll((String)entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result1);
return result1;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
}
}
}
- 下面是spring-boot-autoconfigure這個(gè)jar中spring.factories文件部分內(nèi)容带射,其中有一個(gè)key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定義了需要自動(dòng)配置的bean同规,通過(guò)讀取這個(gè)配置獲取一組@Configuration類。
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# 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,\
- 每個(gè)xxxAutoConfiguration都是一個(gè)基于java的bean配置類庸诱。實(shí)際上,這些xxxAutoConfiguratio不是所有都會(huì)被加載晤揣,會(huì)根據(jù)xxxAutoConfiguration上的@ConditionalOnClass等條件判斷是否加載桥爽。
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class ex = ClassUtils.forName(instanceClassName, classLoader);
if(!factoryClass.isAssignableFrom(ex)) {
throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
} else {
return ReflectionUtils.accessibleConstructor(ex, new Class[0]).newInstance(new Object[0]);
}
} catch (Throwable var4) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
}
}
- 如上代碼段,通過(guò)反射機(jī)制將spring.factories中@Configuration類實(shí)例化為對(duì)應(yīng)的java實(shí)列昧识。到此我們已經(jīng)知道怎么發(fā)現(xiàn)要自動(dòng)配置的bean了钠四,最后一步就是怎么樣將這些bean加載到spring容器。
bean加載
- 如果要讓一個(gè)普通類交給Spring容器管理跪楞,通常有以下方法:
使用 @Configuration與@Bean 注解
使用@Controller @Service @Repository @Component 注解標(biāo)注該類缀去,然后啟用@ComponentScan自動(dòng)掃描
使用@Import 方法
springboot中使用了@Import 方法
@EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector實(shí)現(xiàn)了DeferredImportSelector接口甸祭,
DeferredImportSelector接口繼承了ImportSelector接口缕碎,ImportSelector接口只有一個(gè)selectImports方法。
總結(jié)
- 我們可以將自動(dòng)配置的關(guān)鍵幾步以及相應(yīng)的注解總結(jié)如下:
@Configuration&與@Bean->基于java代碼的bean配置
@Conditional->設(shè)置自動(dòng)配置條件依賴
@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉(zhuǎn)換為bean池户。
@EnableAutoConfiguration咏雌、@AutoConfigurationPackage 與@Import->實(shí)現(xiàn)bean發(fā)現(xiàn)與加載。