文章目录
自动装配是SpringBoot的核心思想,也是实现开箱即用的原理
SpringBoot有两个重要概念:
(1)约定大于配置
(2)自动装配
其中,自动装配是SpringBoot的核心思想,也是实现开箱即用的原理,引入依赖,按约定配置,即可使用。比如我们需要使用redis,只需要Redis的starter依赖,按约定配置Redis地址和密码即可使用。
需要注意的是:starter需要按照SpringBootStarter的规则来实现。
- 无论是官方还是第三方提供的xxx-starter的jar包(我们通过pom文件引入的starter依赖),每个starter的META-INF目录下都会有一个 spring.factories 文件,SpringBoot启动时会通过@Import注解指定的选择器读取该文件配置的自动配置类中。
- SpringBoot应用启动时,会通过@Import(AutoConfigurationImportSelector.class)注解中配置的自动装配选择器AutoConfigurationImportSelector的importSelect方法到META-INF目录下的spring.factories中加装需要装配的配置类的完全限定名,然后根据配置类完全限定名到对应的starter中获取实际想要的Bean对象(自动配配置类中通过@Bean等注解会创建对象,而且starter可以读取自己的yml等配置文件)。
- SpringBoot主要依靠SpringFactoriesLoader扫描Classpath下的所有spring.factories文件。
自定义SpringBoot的starter-----了解自定义,可以更好的理解自动配置类的装配过程
@SpringBootApplication
@SpringBootApplication是一个组合注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
(1)@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
@SpringBootConfiguration用于指定Springboot的启动类也是一个配置类,该配置类中的@Bean注解标注的方法也会被加载进Spring容器。
因为@SpringBootConfiguration底层也是@Configuration,@Configuration注解可以达到在Spring中使用xml配置文件的beans标签的作用。@Bean就等同于xml配置文件中的bean标签
@SpringBootConfiguration和@Configuration的唯一区别:@SpringBootConfiguration在自动配置中会被SpringBoot识别。
(2) @ComponentScan
@ComponentScan作用是自动扫描并加载符合条件的组件(比如@Service、@Component和@Repository等),最终将它们的bean定义加载到IoC容器中
(3)@EnableAutoConfiguration 这个是SpringBoot的核心注解,自动装配主要依靠这个注解。
@EnableAutoConfiguration(@Enablexxxx开头的注解,都是SpringBoot用于自动装配的注解)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
SpringBoot中有许多@Enablexxxx开头的注解,@EnableAutoConfiguration注解与这些注解的实现方式是相同的,都是借助@Import注解,将所有符合自动配置条件的bean定义加载到IoC容器
- @EnableScheduling是通过@Import将Spring调度框架相关的bean定* 义都加载到IoC容器。
- @EnableZuulProxy是通过@Import将zuul相关的bean定义加载到IoC容器。
需要自动配置的类分为两类:
(1)用户自定义的类,即启动类所在包及其包下@Configuration、@Bean、@Component等标注的类,这类需要自动配置的类是通过@AutoConfigurationPackage注解完成的,将需要自动配置类对应的BeanDefiniton实例注册到ConcurrentHashMap容器中去,通过三级缓存完成Bean的创建。
(2)引入第三方jar包中的类,主要通过@Import(AutoConfigurationImportSelector.class)注解,AutoConfigurationImportSelector在importSelect方法中通过SpringFactoriesLoader类加载META_INF目录下的spring.factories文件找到自动配置类的位置,但并不是spring,factories文件中的所有配置都会被加载,自动配置类会通过@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnBean、@ConditionalOnMissingBean注解等判断条件是否成立,成立才会加载配置类。
@AutoConfigurationPackage
@AutoConfigurationPackage也是一个组合注解,里面也包含了注解@Import
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@Import
@Import注解的作用:导入指定配置对象到IOC容器
@Import导入配置有三种方式:
(1)导入普通类
@Import(value = {UserBean.class})
public class UserConfig {
}
public class Demo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
UserBean user =(UserBean) applicationContext.getBean(UserBean.class.getName());
System.out.println(UserBean.class.getName());
}
}
public class UserBean {
public UserBean() {
System.out.println("========UserBean无参构造器==========");
}
@Bean
public User user(){
return new User();
}
}
(2)导入ImportBeanDefinitionRegistrar接口的实现类,该接口实现类中覆写的方法创建了普通类的BeanDefinition对象
@Import(value = {CustomImportBeanDefinitionRegistrar.class})
public class UserConfig {
}
public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(UserBean.class);
beanDefinitionRegistry.registerBeanDefinition(UserBean.class.getName(), rootBeanDefinition);
}
}
public class Demo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
UserBean user =(UserBean) applicationContext.getBean(UserBean.class.getName());
System.out.println(UserBean.class.getName());
}
}
public class UserBean {
public UserBean() {
System.out.println("========UserBean无参构造器==========");
}
@Bean
public User user(){
return new User();
}
}
(3)导入ImportSelector接口的实现类型,该接口实现类中覆写的方法中指定了该普通类的完全限定名【这种方法可以导入多个配置类】
@Import(value = {UserImportSelector.class})
public class UserConfig {
}
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 指定配置类的完全限定名,可以有多个,这里存在硬编码
return new String[]{"top.onething.demo.config.UserBean"};
}
}
public class Demo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
UserBean user =(UserBean) applicationContext.getBean(UserBean.class.getName());
System.out.println(UserBean.class.getName());
}
}
public class UserBean {
public UserBean() {
System.out.println("========UserBean无参构造器==========");
}
@Bean
public User user(){
return new User();
}
}
导入ImportSelector接口的实现类这种方式,在上述代码中存在硬编码指定实现类路径的问题。为解决这个问题,SpringBoot默认把需要自动装配的配置类放到了classpath路径下META-INF目录的spring.factories文件中
SpringFactoriesLoader
- SpringFactoriesLoader工厂加载机制是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件,这个Properties格式的文件中的key是接口、注解、或抽象类的全名,value是以逗号 “ , “ 分隔的实现类,使用SpringFactoriesLoader来实现相应的实现类注入Spirng容器中。
- SpringFactoryiesLoader会读取META-INF/spring.factories下的EnableAutoConfiguration的配置,紧接着在进行排除【去重,不同key下面可能有相同的类】与过滤【spring.factories中有redis、rabbitmq、kafka等需要的配置类,SpringFactoryiesLoader会全部加装,加装后根据pom文件的依赖过滤掉不需要的配置类】,进而得到需要装配的类
BeanDefinition
BeanDefinition:Bean的描述对象,包括Bean对象的字节码文件、是否懒加载、作用域等 BeanDefinition是接口,AbstractBeanDefinition其抽象实现类
BeanDefinition描述对象的三个重要属性
(1)class:
(2)autowireMode
#spring的自动装配的方式
//不自动注入(默认是这种模型)
AbstractBeanDefinition.AUTOWIRE_NO
//根据类型自动装配(但是这个类型是根据set***方法进行装配的)
AbstractBeanDefinition.AUTOWIRE_BY_TYPE
//根据名称自动装配
AbstractBeanDefinition.AUTOWIRE_BY_NAME
//根据构造函数自动装配
AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
(3)constructorArumentValues
通过设置构造器参数来设置spring需要调用哪一个构造器,它会根据参数进行推断
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) defaultListableBeanFactory.getBeanDefinition("name");
//修改构造参数
ConstructorArgumentValues constructorArgumentValues1 = new ConstructorArgumentValues();
constructorArgumentValues1.addIndexedArgumentValue(0, "第1个参数");
constructorArgumentValues1.addIndexedArgumentValue(1, "第2个参数");
beanDefinition.setConstructorArgumentValues(constructorArgumentValues1);
//修改注入模型
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
获取Bean的描述对象----》BeanFactoryPostProcessor接口【Bean工厂的后置处理器,这就是修改Bean的描述对象】-----》getBean 根据Bean的描述对象创建Bean对象----->BeanPostProcessor接口【Bean对象后置处理器:这是修改的Bean实列对象】----->Bean实例化对象
-
应用启动后加载字节码文件,通过Spring的包扫描@Controller、@Service、@Dao、@Component、@Configuration、@Bean等注解标注的类或方法,生成他们各自的Bean描述对象,并这些Bean的描述对象存储到BeanDefinitionMap容器中,其中key为Bean的名称、value为Bean的描述对象
-
BeanFactory的实现类具有属性BeanDefinitionMap
从Spring容器中获取Bean对象
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
BeanDefinition bd = (BeanDefinition)this.beanDefinitionMap.get(beanName);
if (bd == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("No bean named '" + beanName + "' found in " + this);
}
throw new NoSuchBeanDefinitionException(beanName);
} else {
return bd;
}
}
SpringIO容器是一个单例缓存池,是Bean描述对象的集合
//根据配置类创建容器,容器中的元素即配置类生成的Bea对象
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(@Configuration注解标注的配置类.class);
//根据配置类所在包名创建容器,容器中的元素即配置类生成的Bea对象
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("@Configuration注解标注的配置类所在包名");
Object obj = applicationContext.getBean("bean名称");
总结
- SpringBoot是怎么自动装配的
SpringBoot所有自动配置类都是在启动的时候进行扫描并加载,通过spring.factories可以找到自动配置类的路径,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。 - 自动装配的什么对象
配置类的Bean定义对象BeanDenifition,加载后放入beanDenifitionMap中,这时候还是半成品,最后还需要经过处理才能成为最终的Bean对象