前言
使用過springboot的同學應該已經知道拱镐,springboot通過默認配置了很多框架的使用方式幫我們大大簡化了項目初始搭建以及開發(fā)過程。本文的目的就是一步步分析springboot的啟動過程湿酸,分析springboot是如何幫我們簡化這個過程的。
如果想學習Java工程化递胧、高性能及分布式囚痴、深入淺出。微服務族展、Spring森缠,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135仪缸,群里有阿里大牛直播講解技術贵涵,以及Java大型互聯(lián)網技術的視頻免費分享給大家。
springboot幫我們做了什么
通常搭建一個基于spring的web應用恰画,我們需要做以下工作:
1宾茂、pom文件中引入相關jar包,包括spring拴还、springmvc跨晴、redis、mybaits片林、log4j端盆、mysql-connector-java 等等相關jar ...
2怀骤、配置web.xml,Listener配置焕妙、Filter配置蒋伦、Servlet配置、log4j配置焚鹊、error配置 ...
3痕届、配置數據庫連接、配置spring事務
4末患、配置視圖解析器
5研叫、開啟注解、自動掃描功能
6阻塑、配置完成后部署tomcat蓝撇、啟動調試
......
搭個初始項目不一會就一個小時甚至半天過去了。而用springboot后陈莽,一切都變得很簡便快速渤昌。下來我們來一步步分析springboot的起步依賴與自動配置這兩個核心原理。
回到頂部
起步依賴
在springboot中我們只需要引入下面簡單的幾步就可以完成一個ssm后臺項目的初始搭建走搁。
1独柑、引入jar
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
com.alibaba
druid
1.0.31
如果想學習Java工程化、高性能及分布式私植、深入淺出忌栅。微服務、Spring曲稼,MyBatis索绪,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術贫悄,以及Java大型互聯(lián)網技術的視頻免費分享給大家瑞驱。
spring-boot-starter-web包自動幫我們引入了web模塊開發(fā)需要的相關jar包,
mybatis-spring-boot-starter幫我們引入了dao開發(fā)相關的jar包窄坦。
spring-boot-starter-xxx是官方提供的starter唤反,xxx-spring-boot-starter是第三方提供的starter。
如下截圖:
可以看出在這個mybatis-spring-boot-starter 中鸭津,并沒有任何源碼彤侍,只有一個pom文件,它的作用就是幫我們引入了相關jar包逆趋。
2盏阶、配置數據源
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機制幫我們完成了項目起步所需要的的相關jar包。那問題又來了闻书,傳統(tǒng)的spring應用中不是要在application.xml中配置很多bean的嗎般哼,比如dataSource的配置吴汪,transactionManager的配置 ... springboot是如何幫我們完成這些bean的配置的?下面我們來分析這個過程
回到頂部
自動配置
基于java代碼的bean配置
以mybatis為例蒸眠,在上面的截圖中,我們發(fā)下mybatis-spring-boot-starter這個包幫我們引入了mybatis-spring-boot-autoconfigure這個包杆融,如下圖:
里面有MybatisAutoConfiguration這個類楞卡,打開這個類看看有什么東西。
熟悉@Configuration&脾歇、@Bean這兩個bean的同學或許已經知道了蒋腮。這兩個注解一起使用就可以創(chuàng)建一個基于java代碼的配置類,可以用來替代相應的xml配置文件藕各。
@Configuration注解的類可以看作是能生產讓Spring IoC容器管理的Bean實例的工廠池摧。
@Bean注解告訴Spring,一個帶有@Bean的注解方法將返回一個對象激况,該對象應該被注冊到spring容器中作彤。
傳統(tǒng)的基于xml的bean配置方法如下:
相當于用基于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這個類,自動幫我們生成了SqlSessionFactory這些Mybatis的重要實例并交給spring容器管理乌逐,從而完成bean的自動注冊竭讳。
自動配置條件依賴
從MybatisAutoConfiguration這個類中使用的注解可以看出,要完成自動配置是有依賴條件的浙踢。
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
//....
}
如果想學習Java工程化绢慢、高性能及分布式、深入淺出洛波。微服務胰舆、Spring,MyBatis蹬挤,Netty源碼分析的朋友可以加我的Java高級交流:854630135缚窿,群里有阿里大牛直播講解技術,以及Java大型互聯(lián)網技術的視頻免費分享給大家闻伶。
這些是springboot特有的滨攻,常見的條件依賴注解有:
@ConditionalOnBean,僅在當前上下文中存在某個bean時蓝翰,才會實例化這個Bean光绕。
@ConditionalOnClass,某個class位于類路徑上畜份,才會實例化這個Bean诞帐。
@ConditionalOnExpression,當表達式為true的時候爆雹,才會實例化這個Bean停蕉。
@ConditionalOnMissingBean愕鼓,僅在當前上下文中不存在某個bean時,才會實例化這個Bean慧起。
@ConditionalOnMissingClass菇晃,某個class在類路徑上不存在的時候,才會實例化這個Bean蚓挤。
@ConditionalOnNotWebApplication磺送,不是web應用時才會實例化這個Bean。
@AutoConfigureAfter灿意,在某個bean完成自動配置后實例化這個bean估灿。
@AutoConfigureBefore,在某個bean完成自動配置前實例化這個bean缤剧。
所以要完成Mybatis的自動配置馅袁,需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個類荒辕,需要存在DataSource這個bean且這個bean完成自動注冊汗销。
進入DataSourceAutoConfiguration這個類,可以看到這個類屬于這個包:
org.springframework.boot.autoconfigure.jdbc
這個包又屬于spring-boot-autoconfigure-2.0.4.RELEASE.jar這個包兄纺,自動配置這個包幫們引入了jdbc大溜、kafka、logging估脆、mail钦奋、mongo等包。很多包需要我們引入相應jar后自動配置才生效疙赠。
bean參數獲取
到此我們已經知道了bean的配置過程付材,但是還沒有看到springboot是如何讀取yml或者properites配置文件的的屬性來創(chuàng)建數據源的?
在DataSourceAutoConfiguration類里面圃阳,我們注意到使用了EnableConfigurationProperties這個注解。
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
...
}
DataSourceProperties中封裝了數據源的各個屬性捍岳,且使用了注解ConfigurationProperties指定了配置文件的前綴富寿。
@ConfigurationProperties(
prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName;
private Class type;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
...
}
@EnableConfigurationProperties與@ConfigurationProperties這兩個注解有什么用呢?我們先看一個例子:
@Component
@ConfigurationProperties(prefix="spring.datasource")
public class PropertiesBean {
private String url;
private String username;
private String password;
//省略getter锣夹、setter...
@Override
public String toString() {
return "PropertiesBean{" +
"url='" + url + ''' +
", username='" + username + ''' +
", password='" + password + ''' +
'}';
}
}
@SpringBootApplication
@MapperScan("com.itpsc.mapper*")
@EnableConfigurationProperties
public class SpringbootMybatisDemoApplication {
public static void main(String[] args) {
//SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
ConfigurableApplicationContext context = SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
//獲取yml配置轉換后的bean
System.out.println("----------------------"+context.getBean(PropertiesBean.class));
context.close();
}
}
如果想學習Java工程化页徐、高性能及分布式、深入淺出银萍。微服務变勇、Spring,MyBatis贴唇,Netty源碼分析的朋友可以加我的Java高級交流:854630135搀绣,群里有阿里大牛直播講解技術飞袋,以及Java大型互聯(lián)網技術的視頻免費分享給大家。
運行結果:
從運行結果可以看出@ConfigurationProperties與@EnableConfigurationPropertie的作用就是:
@ConfigurationProperties注解的作用是把yml或者properties配置文件轉化為bean链患。
@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效巧鸭。如果只配置@ConfigurationProperties注解,在spring容器中是獲取不到y(tǒng)ml或者properties配置文件轉化的bean的锣险。
通過這種方式蹄皱,把yml或者properties配置參數轉化為bean,這些bean又是如何被發(fā)現與加載的芯肤?
bean發(fā)現
springboot默認掃描啟動類所在的包下的主類與子類的所有組件,但并沒有包括依賴包的中的類压鉴,那么依賴包中的bean是如何被發(fā)現和加載的崖咨?
我們通常在啟動類中加@SpringBootApplication這個注解,點進去看
@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 {
...
}
實際上重要的只有三個Annotation:
@Configuration(@SpringBootConfiguration里面還是應用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
@Configuration的作用上面我們已經知道了油吭,被注解的類將成為一個bean配置類击蹲。
@ComponentScan的作用就是自動掃描并加載符合條件的組件,比如@Component和@Repository等婉宰,最終將這些bean定義加載到spring容器中歌豺。
@EnableAutoConfiguration 這個注解的功能很重要,借助@Import的支持心包,收集和注冊依賴包中相關的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這兩個注解蟹腾。@AutoConfigurationPackage的作用就是自動配置的包痕惋,@Import導入需要自動配置的組件。
進入@AutoConfigurationPackage娃殖,發(fā)現也是引入了@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 determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
new AutoConfigurationPackages.PackageImport(metadata)
這兩句代碼的作用就是加載啟動類所在的包下的主類與子類的所有組件注冊到spring容器值戳,這就是前文所說的springboot默認掃描啟動類所在的包下的主類與子類的所有組件。
那問題又來了炉爆,要搜集并注冊到spring容器的那些beans來自哪里堕虹?
進入 AutoConfigurationImportSelector類,
如果想學習Java工程化芬首、高性能及分布式赴捞、深入淺出。微服務衩辟、Spring螟炫,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135艺晴,群里有阿里大牛直播講解技術昼钻,以及Java大型互聯(lián)網技術的視頻免費分享給大家掸屡。
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 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方法調用loadSpringFactories方法從所有的jar包中讀取META-INF/spring.factories文件信息。
private static Map> 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這個jar中spring.factories文件部分內容然评,其中有一個key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定義了需要自動配置的bean仅财,通過讀取這個配置獲取一組@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,
每個xxxAutoConfiguration都是一個基于java的bean配置類碗淌。實際上盏求,這些xxxAutoConfiguratio不是所有都會被加載,會根據xxxAutoConfiguration上的@ConditionalOnClass等條件判斷是否加載亿眠。
private static T instantiateFactory(String instanceClassName, Class 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);
}
}
如果想學習Java工程化碎罚、高性能及分布式、深入淺出纳像。微服務荆烈、Spring,MyBatis竟趾,Netty源碼分析的朋友可以加我的Java高級交流:854630135憔购,群里有阿里大牛直播講解技術,以及Java大型互聯(lián)網技術的視頻免費分享給大家岔帽。
如上代碼段玫鸟,通過反射機制將spring.factories中@Configuration類實例化為對應的java實列。到此我們已經知道怎么發(fā)現要自動配置的bean了犀勒,最后一步就是怎么樣將這些bean加載到spring容器屎飘。
bean加載
如果要讓一個普通類交給Spring容器管理,通常有以下方法:
1账蓉、使用 @Configuration與@Bean 注解
2枚碗、使用@Controller @Service @Repository @Component 注解標注該類,然后啟用@ComponentScan自動掃描
3铸本、使用@Import 方法
springboot中使用了@Import 方法
@EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解肮雨,AutoConfigurationImportSelector實現了DeferredImportSelector接口,
DeferredImportSelector接口繼承了ImportSelector接口箱玷,ImportSelector接口只有一個selectImports方法怨规。
public class AutoConfigurationImportSelector implements DeferredImportSelector{
...
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);
}
}
...
}
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class getImportGroup() {
return null;
}
public interface Group {...}
}
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
我們先通過一個簡單例子看看@Import注解是如何將bean導入到spring容器的。
1锡足、新建一個bean
public class User {
private Long id;
private String name;
private String password;
private String phone;
...
}
如果想學習Java工程化波丰、高性能及分布式、深入淺出舶得。微服務掰烟、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135纫骑,群里有阿里大牛直播講解技術蝎亚,以及Java大型互聯(lián)網技術的視頻免費分享給大家。
2先馆、創(chuàng)建一個ItpscSelector類繼承ImportSelector接口并實現selectImports方法
public class ItpscSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.itpsc.entity.User"};
}
}
3发框、創(chuàng)建ImportConfig類,使用@Configuration煤墙、@Import(ItpscSelector.class)注解梅惯。
@Configuration
@Import(ItpscSelector.class)
public class ImportConfig {
}
4、從容器獲取bean
@RunWith(SpringRunner.class)
@SpringBootTest
public class ImportSelectorTests {
@Test
public void testSelectImport() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ImportConfig.class);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
運行結果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
com.itpsc.entity.User
很直觀仿野,selectImports方法返回一組bean铣减,@EnableAutoConfiguration注解借助@Import注解將這組bean注入到spring容器中,springboot正式通過這種機制來完成bean的注入的脚作。
回到頂部
總結
我們可以將自動配置的關鍵幾步以及相應的注解總結如下:
1徙歼、@Configuration&與@Bean->基于java代碼的bean配置
2、@Conditional->設置自動配置條件依賴
3鳖枕、@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉換為bean。
4桨螺、@EnableAutoConfiguration宾符、@AutoConfigurationPackage 與@Import->實現bean發(fā)現與加載。
如果想學習Java工程化灭翔、高性能及分布式魏烫、深入淺出。微服務肝箱、Spring哄褒,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135煌张,群里有阿里大牛直播講解技術呐赡,以及Java大型互聯(lián)網技術的視頻免費分享給大家。
加群直通車:854630135
課堂直通車:點一下就好啦