1. 基础介绍
1.1 什么是Spring?
我们可以理解为一个容器,一个Map,这个Map里面有我们所有配置的Bean,这样我们就不用每次new Bean,而是由Spring帮我们实例化.
1.2 什么是SpringBoot
基于Spring的脚手架框架,我们以前使用Spring的时候,无论整合MyBatis,Redis,全部需要自己实例化并且配置,并且配置许多的XML,但是有了SpringBoot之后,我们只需要引入对应的依赖,配置文件配置好,他就自动帮我们实例化了对应的Bean,我们就可以直接使用.
1.3 什么是Beandefinition
Beandefinition中文是Bean定义,就是描述Bean的bean,你就把他当成一个pojo,里面有很多属性,例如我的类名,是否懒加载,是否自动注入等等.Spring就会根据beandefinition去把我们的Bean实例化.Spring的Bean和我们普通的Bean不同之处在于,Spring的Bean多了很多属性,是被包装过的.
例如我们下图我的User的Beandefinition,他有众多属性,是否懒加载,是否单例等等,这个Beandefinition就是描述我User的类,后面Spring会根据类名反射创建我们的Bean,而且我们的bean是拥有众多属性的Bean.
1.4 Spring引入Bean的方式
1.@Component,@Controller,@Service等等
2.@Bean
3.@Import
@Import,我们比较少使用,但是他却是springboot中重要的存在,首先他的使用很简单,例如下图,直接导入我们想要的Bean就可以了,他相比我们的@Component,优势在于我@Import可以任意导入一个不在我Springboot扫描(@ComponentScan)的路径下的类和JAR里面的类,而且可以批量导入,这一点是@Component做不到的,所以我们在引入第三方的Bean基本都有@Import
@Import({Parent.class,
// 这是Spring-code包里面的Bean,我随便找的一个
AntPathMatcher.class})
4.@Import结合ImportSelector和DeferredImportSelector
使用@Import的时候,它的类可以是实现了ImportSelector或者DeferredImportSelector接口的类。 Spring容器会实例化这个实现类,并且执行其selectImports方法例如@Import(MyImportSelector.class)这样就可以往我们Spring容器注入MyImportSelector,并且执行selectImports()方法,这个方法会返回一连串的类名,让spring去注入返回的类.
注意:
DeferredImportSelector特殊一点,他会判断有没有实现里面的getImportGroup()方法,如果这个方法返回空则直接执行selectImports()方法,否则需要返回一个实现了DeferredImportSelector.Group的类,并且会执行返回类的Group.process()方法和Group.selectImports()方法,这里不懂没关系只需要下面讲的源码里面,调用了process()方法
public class MyImportSelector implements ImportSelector
//虽然不能@Autowired,但是实现了这些接口是可以感知到的,下面看源码会发现,Spring会给他注入进去
// 这样我们就可以根据特定的条件,来决定某些Bean能注入,有些Bean不能注入了
//,BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware
{
// 备注:这里各种@Autowired的注入都是不生效的,都是null
// 了解Spring容器刷新过程的时候就知道,这个时候还没有开始解析@Autowired,所以肯定是不生效的
@Autowired
private HelloService helloService;
/**
* 容器在会在特定的时机,帮我们调用这个方法,向容器里注入Bean信息
*
* @param importingClassMetadata 包含配置类上面所有的注解信息,以及该配置类本身
* 若有需要,可以根据这些其它注解信息,来判断哪些Bean应该注册进去,哪些不需要
* @return 返回String数组,注意:都必须是类的全类名,才会被注册进去的(若你返回的全类名不存在该类,容器会抛错)
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("this MyImportSelector...");
//return new String[]{"com.fsx.bean.Child"};
// 一般建议这么玩 用字符串写死的方式只是某些特殊场合(比如这个类不一定存在之类的。。。)
return new String[]{Child.class.getName()};
}
}
1.5 当我们在Springboot中看到了EnableXXXXX,的时候他一定是在里面用了@Import导入了某一种自动配置的类,例如下图的事务管理器.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
...
}
1.6 Spring创建Bean的简易过程
1.首先扫描我们的注解,获取哪一些Bean是我们需要的
2.把这些需要的Bean转化为我们的Beandefinition,放进我们BeandefinitionMap
3.Spring根据Beandefinition通过反射创建我们的Bean.
2. springboot自动配置原理
有了之前前置知识之后,我们快速来浏览一下Springboot自动配置原理
2.1 首先我们找到启动类的@SpringBootApplication,发现里面有一个@EnableAutoConfiguration
2.2 看到EnableXXX就知道里面肯定用了@Import加载一些自动配置Bean
2.3 上面看到@Import导入了AutoConfigurationImportSelector并且实现了DeferredImportSelector,我们知道他会调用selectImports()方法,实际上DeferredImportSelector实现了getImportGroup方法,如果没实现这个方法则直接调用selectImports(),但是实现了,所以该方法返回了自定义Group类,是Group类的process调用的getAutoConfigurationEntry方法,这里不懂没关系我们只需要知道他调用了getAutoConfigurationEntry(),然后这个方法里面调用了GetCandidateConfigurations();
GetCandidateConfigurations()方法会读取jar包所有的META_INF/spring.factories文件,这个文件里面全部都是自动配置类AutoConfiguration,那么就会将得到所有需要自动配置的类名,然后通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤,因为不是所有的自动配置类都需要注入到Spring容器.
AutoConfigurationImportSelector:
如果没找到任何spring-factories,就会报错,有就返回spring-factories里面配置的所有类名.
AutoConfigurationGroup:
getAutoConfigurationEntry()
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从META-INF/spring.factories中获得候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 排重
configurations = removeDuplicates(configurations);
//根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 根据:@EnableAutoConfiguration.exclude
// @EnableAutoConfiguration.excludeName
// spring.autoconfigure.exclude 进行排除
checkExcludedClasses(configurations, exclusions);
// exclusions 也排除
configurations.removeAll(exclusions);
// 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 这个方法是调用实现了AutoConfigurationImportListener 的bean.. 分别把候选的配置名单,和排除的配置名单传进去做扩展
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
2.4 例如sping.factories中有一个RedisAutoConfiguration,我2.3的@Import会把这个配置类加载到Spring容器当中
这个配置类里面有个@Bean redisTemplate,我们用springboot的时候,直接写一下redis的配置,直接注入redisTemplate即可使用,因为他已经帮我注入进去容器了,不需要自己再@Bean去创建.
- 那么RedisAutoConfiguration@ConditionalOnClass(RedisOperations.class)是什么意思?
如果我的类路径下有RedisOperations.class,Spring你就帮我实例化RedisAutoConfiguration吧,RedisOperations.class 这个类在spring-data-redis这个包里面,意思就是我只要pom引入了spring-data-redis,你就帮我实例化RedisAutoConfiguration这个Bean吧.
- 那么redisTemplate上面的@ConditionOnMissingBean是什么意思呢?
如果我的项目不需要他的自动配置了,我要用自己的定制化,@Bean去注入RedisTemplate,这个时候ConditionOnMissingBean这个注解就会判断,我的容器里面有自己定制化的RedisTemplate,不需要再用自动配置的RedisTemplate.因为spring中有两个同名的bean会报错.
- 如果我们使用Spring默认的RedisTemplate,那么他的默认配置在哪呢?
答案就在注解@EnableConfigurationProperties(RedisProperties.class)的RedisProperties里面,如果我不做任何redis的配置,默认连接127.0.0.1:6379
RedisAutoConfiguration:
RedisProperties:
2.4 总结
任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:会获取到自动配置,MybatisPlusLanguageDriverAutoConfiguration和MybatisPlusAutoConfiguration
但是自动配置是否生效就看我满不满足@Conditional注解,例如下面mybatisPlus,我类路径下都没有,SqlSessionFactory这个类,我有可能数据库的包都没引进来,我初始化mybatis干嘛呢?如果满足所有条件会将自动配置类实例,并且类里面带@Bean的也会实例化到IOC容器中,我直接就可以使用.
3 @Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
@Conditional扩展注解作用 |
(判断是否满足当前指定条件) |
@ConditionalOnJava |
系统的java版本是否符合要求 |
@ConditionalOnBean |
容器中存在指定Bean; |
@ConditionalOnMissingBean |
容器中不存在指定Bean; |
@ConditionalOnExpression |
满足SpEL表达式指定 |
@ConditionalOnClass |
系统中有指定的类 |
@ConditionalOnMissingClass |
系统中没有指定的类 |
@ConditionalOnSingleCandidate |
容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty |
系统中指定的属性是否有指定的值 |
@ConditionalOnResource |
类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication |
当前是web环境 |
@ConditionalOnNotWebApplication |
当前不是web环境 |
@ConditionalOnJndi |
JNDI存在指定项 |
3.1 我们怎么知道哪些自动配置类生效;
我们可以通过设置配置文件中:启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
3.2 以HttpEncodingAutoConfiguration(Http编码自动配置)**为例说明自动配置原理;
该注解如下:
- @Configuration:表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件。
- @ConditionalOnWebApplication:Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效。
- @ConditionalOnClass:判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器。
- @ConditionalOnProperty:判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的。
- @EnableConfigurationProperties({ServerProperties.class}):将配置文件中对应的值和 ServerProperties绑定起来;并把 ServerProperties加入到 IOC 容器中。并注册ConfigurationPropertiesBindingPostProcessor用于将@ConfigurationProperties的类和配置进行绑定
ServerProperties通过 @ConfigurationProperties 注解将配置文件与自身属性绑定。
对于@ConfigurationProperties注解小伙伴们应该知道吧,我们如何获取全局配置文件的属性中用到,它的作用就是把全局配置文件中的值绑定到实体类JavaBean上面(将配置文件中的值与ServerProperites绑定起来),而@EnableConfigurationProperties主要是把以绑定值JavaBean加入到spring容器中.我们在application.properties 声明spring.application.name 是通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上,然后再通过@EnableConfigurationProperties注解导入到Spring容器中.
4. 为什么我们需要用DeferredImportSelector
4.1 延迟特性
beandefintion的注册顺序:
- 1.先@ComponentScan的:@Component,@Service,@Controller
- 2.@lmport的:@Component,@Service,@Controller
- 3.@Configuration -->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口的
- 4.@lmport进来的@Configuration --->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口
- 5.@lmport进来的实现了DeffredlmportSelector接口的@Configuration --->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口
总结一下就是@Import导入进来的实现DeferredImportSelector,通过他的selectImport加载的类的解析是靠后的.
假如我通过selectImport导入进来的类比@Component导入的优先会发生什么问题?
如下图如果我先加载selectImport的RedisTemplate,那我的注解ConditonOnBean肯定不会生效,因为现在我的容器里面根本没有RedisTemplate.
如果我先加载我们自定义的RedisTemplate,那我的ConditonOnBean就会判断,我的容器里面已经有我自己定义的RedisTemplate了,我自动配置的RedisTemplate你就不需要生效了.
4.2 分组特性
如上图,我的selectImport导入的类如果进行排序,只会影响我当前分组的排序,不会影响容器里面其他Bean的排序,例如下图我的DeferredImportSelector.Group里面有一个RedisTemplate和,sqlSessionFactory,他们如果加了@Order注解去排序,只会在我这个组里面排序,不会影响其他.
5.最后附上一张自动配置原理的图
总结:
首先通过import导入一个DeferredImportSelector,因为保证顺序,他是最后加载的,为了保证我们自动配置类是放在最后,才能定制我们自己的类.他会去加载我们所有jar包当中的Spring.factories文件,并且获得里面配置的所有类名List,并且排序,过滤后返回给我们Spring,这样我们Spring就能管理这些Bean.也提现了Spring的扩展性,我们可以利用这个去写我们自己的starter,这种模式类似SPI.