功能分析
傳統(tǒng)的Spring項目會有很多的配置文件妻献,比如我們要使用Redis,一般除了對應(yīng)的依賴的jar包我們還需要在application.xml里面配置JedisConnectionFactory乏矾、JedisPoolConfig屋剑、RedisTemplate。但是如果使用SpringBoot的話诗眨,系統(tǒng)會根據(jù)pom.xml里面的jar包唉匾,自動生成這些類并且注入到IOC容器當中。
- 傳統(tǒng)Spring項目中需要配置
<bean id="jedisConnectionFactory" class="...JedisConnectionFactory"></bean>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"></bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"></bean>
- 而使用SpringBoot的話匠楚,除了pom.xml引入相應(yīng)的jar包外巍膘,只需要在application.properties配置對應(yīng)的屬性值即可
概述
自動裝配的過程:
- 通過各種注解+繼承,引入包含自動裝配核心方法的類
- SpringApplication.run(Application.class, args)在運行時芋簿,調(diào)用自動裝配方法
- 自動裝配方法會讀取spring-boot-autoconfigure.jar里面的spring.factories配置文件峡懈,配置文件中有所有自動裝配類的配置類的類名
- 生成對應(yīng)功能的Configuration類,這些功能配置類要生效的話与斤,會去classpath中找是否有該類的依賴類(也就是pom.xml必須有對應(yīng)功能的jar包才行)
- 配置類里再通過判斷生成最后的功能類肪康,并且配置類里面注入了默認屬性值類,功能類可以引用并賦默認值撩穿。生成功能類的原則是自定義優(yōu)先磷支,沒有自定義時才會使用自動裝配類。
綜上所述食寡,要想自動裝配一個類需要滿足2個條件:
- spring.factories里面有這個類的配置類(一個配置類可以創(chuàng)建多個圍繞該功能的依賴類)
- pom.xml里面需要有對應(yīng)的jar包
自動裝配的結(jié)果:
- 根據(jù)各種判斷和依賴雾狈,最終生成了業(yè)務(wù)需要的類并且注入到IOC容器當中了
- 自動裝配生成的類賦予了一些默認的屬性值
注解引用線路圖
復(fù)合注解+@import加載了對應(yīng)的類進來,然后在程序啟動方法里面抵皱,間接調(diào)用自動加載類的方法
@SpringBootApplication -->@EnableAutoConfiguration -->@Import(EnableAutoConfigurationImportSelector.class)
-->extends AutoConfigurationImportSelector -->selectImports() -->getExcludeAutoConfigurationsProperty()
通過注解引用善榛,最終在SpringApplication.run()方法的時候,會調(diào)用selectImports()呻畸,最終加載自動裝配
private List<String> getExcludeAutoConfigurationsProperty() {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(this.environment, "spring.autoconfigure.");
}
Redis自動裝配示例
- 從spring-boot-autoconfigure.jar/META-INF/spring.factories中獲取120多個默認功能配置類移盆,其中包括redis的功能配置類RedisAutoConfiguration的全限定名
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
- RedisAutoConfiguration配置類生效的一個條件是@ConditionalOnClass :JedisConnection.class, RedisOperations.class, Jedis.class,所以會去classpath下去查找對應(yīng)的class文件
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
}
- 如果pom.xml有對應(yīng)的jar包,就能匹配到對應(yīng)依賴class:JedisConnection.class, RedisOperations.class, Jedis.class
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 匹配成功,這個功能配置類才會生效伤为,同時會注入默認的屬性配置類@EnableConfigurationProperties(RedisProperties.class)
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
- Redis功能配置里面會根據(jù)條件生成最終的JedisConnectionFactory味滞、RedisTemplate,條件就是IOC環(huán)境里面,沒有用戶自定義的@ConditionalOnMissingBean(RedisConnectionFactory.class)钮呀、RedisTemplate
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- 最終創(chuàng)建好的默認裝配類剑鞍,會通過功能配置類里面的 @Bean注解,注入到IOC當中
核心注解
@SpringBootApplication整合了3個注解:SpringBootConfiguration爽醋、EnableAutoConfiguration蚁署、ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}
- @SpringBootConfiguration:實際上就是@Configuration,表明這是一個IOC容器的配置類蚂四,相當于說明該bean是一個spring中的xml文件光戈。
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
}
- @ComponentScan:指定了Spring中的指定MVC環(huán)境掃描包和Spring IOC的掃描包哪痰,掃描路徑就是該類所在的所有包。SpringBoot的注解掃描所有的同路徑下的類久妆,@Controller類歸位MVC類晌杰,其它類為Spring的類
常規(guī)mvc配置指定包
<context:component-scan base-package="com.test.Action" />
Spring也要指定Spring的注解類的掃描路徑
<context:component-scan base-package="com.test" />
- @EnableAutoConfiguration:表示開啟Spring Boot自動配置功能,Spring Boot會根據(jù)應(yīng)用的依賴筷弦、自定義的bean肋演、classpath下有沒有某個類等等因素來猜測你需要的bean,然后注冊到IOC容器中烂琴。
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
- @Conditional注解表示在滿足某種條件后才初始化一個bean或者啟用某些配置爹殊。自定義編寫條件類,實現(xiàn)Condition接口奸绷,并覆蓋它的matches()方法,比如MyService類依賴于JdbcTemplateCondition.class個條件類梗夸,而JdbcTemplateCondition.class調(diào)節(jié)類的滿足條件是在classpath下面可以加載JdbcTemplate這個類。
@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
......
}
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
- @ConditionalOnClass:表示只要在classpath下找得到對應(yīng)的class文件号醉,該配置類反症、或者方法才會生效。
比如classpath中有Billy.class畔派,這個配置類才生效惰帽,也就是Fighter這個Bean才會注入到IOC容器中
@Configuration
@ConditionalOnClass({Billy.class})
public class VanConfig {
@Bean
public Fighter billy(){
return new Billy();
}
}
- @ConditionalOnBean:表示在IOC環(huán)境下,containsBean為true的時候父虑,才通過该酗。
例如下面,即使classpath下存在Test.class士嚎,但是Test.class沒有注入到IOC中呜魄,也會報錯。HelloService注入失敗莱衩。
@Bean
@ConditionalOnBean(Test.class)
public HelloService test(){
return new HelloService();
}
- @ConditionalOnMissingBean 這個是個很厲害的注解爵嗅,實現(xiàn)默認配置時自定義優(yōu)先。如果上下文(IOC環(huán)境)中已經(jīng)有這個Bean了就忽略笨蚁,沒有這個Bean的話睹晒,才執(zhí)行返回默認自動裝配Bean。
比如我們應(yīng)用要依賴Animal接口括细,如果我們手動注入一個animal伪很,那么就以注入的bean為準,如果未注入奋单,則會被@ConditionalOnMissingBean檢測到锉试,就使用默認的AutoConfigAnimal作為bean。
@RestController
public class MyRun {
@Autowired
private Animal animal;
@RequestMapping("/auto/home")
public String home(){
return animal.eat();
}
}
@Component("animal")
public class Human implements Animal{
public String eat() {
return "eat rice";
}
}
@Configuration
public class TestConfig {
@Bean
@ConditionalOnMissingBean(Animal.class)
public Animal test(){
return new AutoConfigAnimal();
}
}
public class AutoConfigAnimal implements Animal{
public String eat() {
return "eat anything";
}
}
依賴的注解(Redis示例)
@SpringBootApplication:sb項目應(yīng)用啟動類的注解览濒,其實是3個注解的組合:@SpringBootConfiguration呆盖、@EnableAutoConfiguration拖云、@ComponentScan,其中在自動裝配中起作用的是第二個
@EnableAutoConfiguration:表示SB應(yīng)用啟動自動裝配的功能(包括加載對應(yīng)的Bean到IOC容器中应又,且根據(jù)默認配置對屬性賦值)
@Import(EnableAutoConfigurationImportSelector.class):這個注解比較厲害宙项,可以把沒有注冊到IOC中的Bean強行注冊到IOC中,表示啟動自動配置功能需要引入EnableAutoConfigurationImportSelector.class才行
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }):表示讓RedisAutoConfiguration配置類起作用的話,必須有包含這些類的jar包才行
@EnableConfigurationProperties(RedisProperties.class):表示默認引用RedisProperties.class里面的配置
@ConditionalOnMissingBean(RedisConnectionFactory.class):表示如果用戶沒有自定義注入RedisConnectionFactory.class類株扛,才會使用默認的JedisConnectionFactory
代碼邏輯:
自動裝配的過程
通過各種注解實現(xiàn)了類與類之間的依賴關(guān)系尤筐,容器在啟動的時候Application.run,會調(diào)用EnableAutoConfigurationImportSelector.class的selectImports方法(其實是其父類的方法)
selectImports方法最終會調(diào)用SpringFactoriesLoader.loadFactoryNames方法來獲取一個全面的常用BeanConfiguration列表
loadFactoryNames方法會讀取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories)席里,獲取到所有的Spring相關(guān)的Bean的全限定名ClassName,大概120多個
selectImports方法繼續(xù)調(diào)用filter(configurations, autoConfigurationMetadata);這個時候會根據(jù)這些BeanConfiguration里面的條件拢驾,來一一篩選奖磁,最關(guān)鍵的是
@ConditionalOnClass,這個條件注解會去classpath下查找繁疤,jar包里面是否有這個條件依賴類咖为,所以必須有了相應(yīng)的jar包,才有這些依賴類稠腊,這個時候這些功能配置類才會生效功能配置類生效后躁染,會獲取到依賴的默認屬性值類,里面有一些該功能的默認屬性值
功能配置類里面配置了最終的功能Bean架忌,這個時候會通過@ConditionalOnMissingBean先判斷用戶是否自定義了吞彤,如果用戶沒有自定義,就創(chuàng)建一個默認的功能類叹放,并且注入到IOC中
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
spring.factories 文件:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer