2021-12-17积累
27、描述BeanDefinition的加载过程
这个问的不多,但是属于Spring源码里面比较难的知识点。调用链很深很复杂。
BeanDefinition用来描述Bean的生产信息,决定Bean以什么方式去生产。定义态的bean。
以javaConfig+@Bean的方式配置bean的形式为例:
1、new AnntationConfigApplicationContext 了一个Spring容器
2、解析配置类
3、注册成BeanDefinition,统一放到一个map里面
4、根据BeanDefinition信息,通过BeanFactory的getBean方法去生产bean。
【解析配置类的步骤】:
1、读取配置:
使用的API:BeanDefinitionReader
2、解析:这些加了注解@Bean,@Import,@ComponentScan的类
使用的API:ConfigurationClassParser配置类的解析器。会调用doProcess方法进行真正的解析。
以解析@ComponentScan为例讲解是怎么扫描的?
扫描API: ClassPathBeanDefinitonScanner里面的方法doScan()帮我们进行扫描。
扫描方法:根据包路径找到所有的.class文件,判断这个类是否加了标准的@Component注解。判断是不是接口,抽象类。如果都符合的话,就会注册成BeanDefinition。
28、如何在Spring所有Bean创建完之后做扩展?
哪里才算所有的bean创建完了。
首先是所有的配置bean会注册成BeanDefinition
然后根据BeanDefinition进行循环调用一个一个的getBean进行生产。
循环完所有的BeanDefiniton,通过BeanFactory的getBean方法生成所有的Bean
这个循环结束之后,所有的Bean就创建完了。
源码:
new ApplicationContext()里面有refresh()体现IOC容器加载。
refresh方法里面会调用finishBeanFactoryInitialization方法。
在finishBeanFactoryInitialization方法里面循环所有的BeanDefinition,一个一个getBean创建 。这个循环结束之后,那么所有的bean就创建完了。
【注意】:
扩展点1:
等所有的bean创建完之后,会再次循环所有的bean,看有哪些bean是否实现了SmartInitializingSingleton接口,如果实现了这个接口,就会继续调用afterSingletonsInstantiated方法。
扩展点2:
所有的bean创建完之后都会调用的扩展接口。
refresh方法里面会调用finishBeanFactoryInitialization方法的后面就是调用finishRefresh方法。
在finishRefresh方法里面会调用ContextRefreshedEvent事件,发布一个ContextRefreshed事件。
创建一个监听器来监听这个事件,就可以完成这个扩展。
比如:创建监听器RefreshedEventListener,使用注解@EventListener来监听这个事件。同样会在所有bean创建完之后调用。
class RefreshedEventListener{
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event){
}
}
IOC的容器加载体现在refresh方法里面。
refreshed说明IOC容器已经加载完了。
这两个扩展接口就可以实现所有的bean创建完之后的扩展。
29、如何在所有BeanDefinition注册完之后做扩展?
考察你是是否知道BeanDefinition。
在第11道面试题里面就讲到了这个。
BeanDefinition是在IOC容器加载的时候,会将配置的Bean,先注册成定义的临时形态。临时形态就叫做BeanDefinition,也叫做定义态。BeanDefinition主要是去读取我们配置的定义信息。比如:class,lazy,scope,autowired,id这些配置会被读取到对象BeanDefinition里面。
会在IOC容器加载的时候调用invokeBeanFactoryPostProcessor方法进行注册。
refresh方法是体现IOC容器的关键方法,里面会调用invokeBeanFactoryPostProcessor方法-bean工厂的后置处理器帮我们注册所有的Bean Definition。注册完之后调用BeanFactoryPostProcessor接口,帮我们进行扩展。
我们只要实现这样的接口BeanFactoryPostProcessor,那么Spring‘就会在注册完之后帮我们进行扩展
@Component
class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
//下面这个方法是在所有的BeanDefinition注册完之后才会调用的。
@override
public void postProcessBeanFactory(ConfigurationListableBeanFactory beanFactory){
}
}
总结:Bean工厂的后置处理器可以在所有的BeanDefinition注册完成之后进行扩展。
扩展什么呢?
比如拿到Person这个bean的定义信息之后,本来它写在xml的时候是默认单例的,现在拿到它的定义信息之后,可以在postProcessBeanFactory方法里面修改为多例。
@Component
class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
//下面这个方法是在所有的BeanDefinition注册完之后才会调用的。
@override
public void postProcessBeanFactory(ConfigurationListableBeanFactory beanFactory){
BeanDefinition personDefinition = beanFactory.getBeanDefinition("person");
personDefinition.setScope("prototype");
}
}
30、Bean的生产顺序是由什么决定的?
BeanDefinition的注册顺序是有什么决定的?
Bean在生产之前有个临时状态:BeanDefinition;存储着bean的描述信息。由BeanDefinition决定着Bean的生产顺序。会按照BeanDefinition的注册顺序来决定Bean的生产顺序。因为所有的BeanDefinition存储在List集合里面,list是有序的。
【Bean的创建顺序是由Bean Definition的注册顺序来决定的,当然依赖关系也会影响Bean的创建顺序】。比如B依赖A,那么,A先生产之后再生产B。
【问:BeanDefinition的注册顺序是由谁来决定的呢?】
BeanDefinition的注册顺序主要是由注解或者配置的解析顺序来决定的。
注册BeanDefinition就是在invokeBeanFactoryPostProcessor这个方法里面执行的,这个方法调用链非常深。
注解的解析顺序:(从上到下)
@Configuration
@Component
@Import直接导入类
@Bean
@Import导入一个实现接口:ImportBeanDefinitionRegistrar
BeanDefinitionRegistryPostProcessor是最后解析的(毕竟叫:后置处理器)
测试这几个顺序:@Configuration @Component @Import直接导入类 @Bean
@Componenet
class User{
}
@Component
class UserImport{
}
@Configuration
@ComponentScan
@Import({UserImport.class})
class MainConfig{
@Bean
public User userBean(){
return new User();
}
}
测试类:
psvm{
context = new ApplicationContext();
for(String beanDefinitionName : context.getBeanDefinitionNames){
sout(beanDefinitionName);
}
}
输出结果:
mainConfig:这个是第一位,通过@Configuration注解的
user:这个是第2位,通过@Component注解的
userImport:这个是第3位,通过@Import直接导入一个类
UserBean:这个是第4位,通过Bean直接导入一个类
31、Spring有哪几种配置方式?
三种配置方式
1、基于XML的;有Spring的配置文件,xxx.xml配置文件里面有bean标签
2、基于注解的配置(Spring2.5之后);在xxx.xml里面有component-scan注解 指定base-package属性,然后在这个扫描包下使用注解@Component
@Autowired进行bean注入
3、基于java的配置(Spring3.0之后);
基于JavaConfig 的配置方式。
使用@Configuration来代替Spring的配置文件,使用@Bean代替配置文件里面的bean标签。
32、JavaConfig是如何代替Spring.xml的?
应用层面:
1、new ApplicationContext的区别:
以前XML的方式:Spring是去new了一个ClassPathXmlApplicationContext,参数传入的是xml文件
而avaConfig的方式:new了一个AnnotationConfigApplicationContext。参数传入的是java的配置类。
2、配置文件和配置类以及属性的区别
使用@Configuration标注在javaConfig上面。这个配置类相当于是Spring.xml文件
使用@Bean配置某个节点,相当于xml文件里面的bean标签。其中标签bean里面的很多属性,比如:
scope,lazy都可以使用对应的注解@Scope和@Lazy
3、扫描包的区别:
使用xml,在xml里面写component-scan base-package
使用javaConfig,用@ComponentScan(base-packages="…")注解
4、引入外部属性配置文件:
xml:place-holder resource="…",拿到外部属性配置文件的值,是通过property标签,name=…,value="${}"
javaConfig:使用注解@PropertySource(classpath:…)
拿到外部属性配置文件,通过@Value("${}")
5、需要引入另外一个Spring的配置文件
xml:使用import标签 resource="…"
javaConfig:使用@Import({xx.class})注解,这个注解很灵活