一、Spring的注解驱动历程
Spring 1.0
具备ioc
<bean> ...
Spring 2.5
有了 @Controller @service @Repository @Component 然后配置Component-scan 的路径就可以了,减少了bean的配置
Spring 3.0 ---- 无配置化实现bean的装配(去xml化)
用@Configuration 取代application.xml <bean>...
@Configuration 要配置一个bean可以 @bean来做
@import
spring4.X
@conditionl:条件满足才装配
spring5.X
@indexed 它可以为Spring的模式注解添加索引
二、springboot的自动装配
1.疑惑点:导入对应的jar包(这个是官方的jar) 例如
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
我们就直接可以使用
@Autowired RedisTemplate redisTemplate;
为什么可以呢?我们还没有手动加载到ioc?
2.前言知识: 我们知道要引入bean有很多种方式,例如静态:xml/@Configuration、 动态的 @import(value = {Cat.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})可以实现ImportSelector接口(MyImportSelector是该接口的实现类)动态导入bean或configuration
public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.fjj.project.entity.Company", "com.fjj.project.entity.Member"}; --- 这里可返回的classname都会被装进ioc } }
3.原理:我们只要把的bean放到以上selectImports方法就可以装到ioc了,那么我们怎么知道将要被引入的jar的bean放在哪里呢?
springboot约定去所有要引入的jar的MATE-INF/spring.factories 都写上
org.springframework.boot.autoconfigure.EnableAutoConfiguration=要加载的classname的路径
然后springboot启动的时候会去读取,接下来我们去查看源码->
步骤1:
在启动类中
@SpringBootApplication public class QuartzApplication{ public static void main(String[] args) { SpringApplication.run(QuartzApplication.class, args); } } 其中@SpringBootApplication里面包含@EnableAutoConfiguration,在这个配置文件里面有一个 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... 步骤2: 所以我们进入AutoConfigurationImportSelector这个类,果不其然也有你实现DeferredImportSelector 也就是间接实现了ImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); --加载配置spring.factories,详细可以查看以下getCandidateConfigurations方法
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
.....
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;
}
}
扩展点: 以上很类似于spi的机制: 在第三方jar中 1).首先在MATE-INF/services创建名称为interface的文件 2).里面写着实现类的class路径
按照这种思路,我们刚刚导入的包应该有spring.factories文件,并且里面有对应的org.springframework.boot.autoconfigure.EnableAutoConfiguration=XXX才对吧,实际上没有
what? 刚刚分析的源码没卵用吗?
其实springboot把jar分为两种:
1)官方包 spring-boot-starter-xxx (这种springboot直接给你配置好了,如下图)
2)第三方包 xxx-spring-boot-starter
第三方还是老老实实的写,例如我导入
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency>
这个想直接使用bean,肯定会有一个文件
按照这种思路,官方的jar自己准备了那么多,那假如对应的jar没有引进来怎么办?加载岂不是报错了?
其实不会,因为spring4.x引入了@conditional,也就是说满足条件才会加载,以RedisAutoConfiguration为例子
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure.data.redis; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
扩展点 条件装配两种方式: 1.@conditional 2.添加spring-autoconfigure-metadata-properties文件 格式:被加载的classname路径 +.ConditionalOnClass= 需要存在才生效的classname的路径