spring-ioc-高阶知识
高阶知识重点在于单独介绍 Spring 一些稍微偏底层的知识,不涉及源码的情况下介绍 Spring 的一些核心组件和后处理器
配置源与配置元信息
配置源可以来自于 xml 和 一个 Configuration 类,用于后续spring容器创建bean的时候读取(BeanDefiniton)
bean 的元信息
- 全限定名 className
- 作用域 scope
- 是否延迟加载 lazy
ioc容器 配置的元信息
- beans
- profile
- …
- context
- context:annotation-config/ 开启注解驱动
- context:component-scan/ 开启组件扫描
- context:property-placeholder/ 引入外部的资源文件( properties xml yml 等)
Environment
是在 ApplicationContext
创建之后创建的,随着 ApplicationContext
存在而存在,影响后面 bean 的注册和创建
本身也是一个特殊的 bean
包括
- profile 基于模式的环境
- property 外部化配置
EnvironmentAware
接口回调注入的方式
一些 api
environment.getDefaultProfiles();
environment.getProperty("jdbc.url");
BeanDefinition
bean 的元信息
包含 类信息(全限定类名),属性(作用域,是否是默认的bean),行为(是否延迟加载),
依赖关系(与其他 bean 的依赖关系,父bean,依赖的bean)
BeanDefinition 具有层级性
可以在 IOC 容器初始化阶段被 BeanDefinitionRegistryPostProcessor 构造和注册,
被 BeanFactoryPostProcessor 修改
我们前面了解到,bean 的注册有三种方式,这三种注册方式对应的 BeanDefinition 的解析方式也是不一样的
BeanDefinitionRegistry
持有所有 BeanDefinition
对象,使用一个map管理
并且定义了 BeanDefinition
的增删查方法
支撑其他组件和动态注册Bean(其余组件->xml reader,读取到bean的定义后也要通过这个 registry 来注册)
最终实现的 DefaultListableBeanFactory
, 回顾,ApplicationContext
管理 bean 也是通过组合一个 DefaultListableBeanFactory
来实现的
一些案例
BeanDefinition 的注册
ImportBeanDefinitionRegistrar
注册 BeanDefinition,注意我们这里构建 BeanDefinition 的方式,使用 BeanDefinitionBuilder
public class PersonRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("person",
BeanDefinitionBuilder.genericBeanDefinition(Person.class).addPropertyValue("name", "zhangsan")
.getBeanDefinition());
}
}
我们可以像之前配置依赖注入的时候一样,去指定这里 Person 的bean创建时候的属性
@Configuration
@Import(PersonRegister.class)
public class BeanDefinitionRegistryConfiguration {
}
public class BeanDefinitionRegistryApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
BeanDefinitionRegistryConfiguration.class);
Person person = ctx.getBean(Person.class);
System.out.println(person);
}
}
Person{name='zhangsan'}
我们用 @Import 注解引入这个 ImportBeanDefinitionRegistrar
,然后尝试获取这个BeanDefiniton 对应的 bean
BeanDefinition
信息的移除
BeanFactoryPostProcessor
BeanDefiniton
的后置处理器
@Component
public class RemoveBeanDefinitionPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
// 获取IOC容器中的所有BeanDefinition
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
// 判断BeanDefinition对应的Bean是否为Person类型
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
if (Person.class.getName().equals(beanDefinition.getBeanClassName())) {
// 判断Person的性别是否为male
// 使用xml配置文件对bean进行属性注入,最终取到的类型为TypedStringValue,这一点不需要记住
TypedStringValue sex = (TypedStringValue) beanDefinition.getPropertyValues().get("sex");
if ("male".equals(sex.getValue())) {
// 移除BeanDefinition
registry.removeBeanDefinition(beanDefinitionName);
}
}
}
}
}
BeanDefiniton
的合并
动物是一个抽象类,动物包括猫猫狗狗,动物关联一个主人
public class Person {
}
public abstract class Animal {
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
public class Cat extends Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" + "name=" + name + ", person='" + getPerson() + '\'' + "}";
}
}
考虑这种注入情况,我们要给 Cat (或者 Dog 等更多的动物)注入 Person 的时候,虽然是父类的属性,但是每次都需要重复手动编写注入逻辑,有没有一种方式,能只在抽象类里面注入一次呢,然后就可以让子类都继承这个注入的逻辑
<bean id="person" class="com.linkedbear.spring.definition.d_merge.bean.Person"/>
<bean id="abstract-animal" class="com.linkedbear.spring.definition.d_merge.bean.Animal" abstract="true">
<property name="person" ref="person"/>
</bean>
<bean id="cat" class="com.linkedbear.spring.definition.d_merge.bean.Cat" parent="abstract-animal">
<property name="name" value="咪咪"/>
</bean>
我们指定 Animal 为 abstract,然后 Cat 指定 parent
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("definition/definition-merge.xml");
Cat cat = (Cat) ctx.getBean("cat");
System.out.println(cat);
BeanDefinition catDefinition = ctx.getBeanFactory().getBeanDefinition("cat");
System.out.println(catDefinition);
BeanDefinition mergedCatDefinition = ctx.getBeanFactory().getMergedBeanDefinition("cat"); // 获取到合并以后的BeanDefinition
System.out.println(mergedCatDefinition);
}
通过 getMergedBeanDefinition 获取到合并 后的 Cat 的 BeanDefiniton
BeanPostProcessor后置处理器
在对象创建以后,初始化前后调用
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
接口设计,可返回 Object ,说明对类型没有限制,可以改变返回 bean 的类型,但是返回 null,会被忽略
对于 FactoryBean 的后置处理器??
考虑 FactoryBean 是延迟加载的,第一次会执行 ,getBean的时候只会执行 postProcessAfterInitialization,返回类型是实际类型
q: BeanPostProcessor 与 ApplicationListener
BeanPostProcessor 会在每个bean的初始化前后执行,ApplicationListener 则是只执行一次(监听到容器事件以后),当然,如果手动刷新容器也会触发多次
BeanPostProcessor 的扩展
InstantiationAwareBeanPostProcessor
拦截(可替换)实例化动作
public class BallFactoryInstantiationProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if ("ball".equals(beanName)) {
// 返回非null,代表拦截创建
Ball ball = new Ball();
ball.setId("工厂球~");
return ball;
}
// 默认直接返回null,代表不拦截
return null;
}
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return false;
}
这个方法返回false,影响后面的方法
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
throws BeansException {
if ("ball2".equals(beanName)) {
// MutablePropertyValues
MutablePropertyValues values = new MutablePropertyValues(pvs);
values.addPropertyValue("id", "拦截球~");
return values;
}
return null;
}
SmartInstantiationAwareBeanPostProcessor
default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
- predictBeanType :预测 bean 的类型(不能预测时返回 null )
- determineCandidateConstructors :根据 bean 对应 Class 中的构造器定义,决定使用哪个构造器进行对象实例化
这个方法很重要,如果 bean 没有声明任何构造器,则此处会拿到默认的无参构造器;如果声明了多个构造器,则该处会根据 IOC 容器中的 bean 和指定的策略,选择最适合的构造器
- getEarlyBeanReference :提早暴露出 bean 的对象引用(该方法与 bean 的循环依赖解决有关,在 SpringBoot 的小册 15 章有讲)
MergedBeanDefinitionPostProcessor
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
形参中提供 BeanDefiniton 和 beanType,我们拿到 BeanDefiniton 信息,并不涉及 实际bean
初始化之后,在实例化之前执行,方便为依赖注入收集需要的属性,并缓存下来 -> eg AutowiredAnnotationBeanPostProcessor
spring 用来实现注解的自动注入
BeanFactoryPostProcessor
所有 BeanDefinition 都注册到 BeanFactory 后回调触发,用于访问 / 修改已经存在的 BeanDefinition 。
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException;
注意这个接口,拿到的整个 BeanFactory ,所以只会执行一次
程序启动 -> 容器启动 -> 注册 BeanDefinition -> BeanFactoryPostProcessor -> 实例化单实例 Bean -> 容器启动完成
对 BeanDefinition 增删属性,移除 BeanDefinition
BeanDefinitionRegistryPostProcessor
比起BeanFactoryPostProcessor
更早执行,所有 BeanDefinition 都准备好,即将加载到 BeanFactory 时回调触发(大的面上和BeanFactoryPostProcessor,执行时机相同,只不过在 BeanFactoryPostProcessor之前),用于给 BeanFactory 中添加新的 BeanDefinition
发生于配置类,配置文件被解析以后
@Component
public class DogRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (!registry.containsBeanDefinition("dog")) {
// 构造BeanDefinition,并注册进BeanFactory
BeanDefinition dogDefinition = BeanDefinitionBuilder.genericBeanDefinition(Dog.class).getBeanDefinition();
registry.registerBeanDefinition("dog", dogDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
编程式ioc
一个案例,手动调用api加载各种来源的BeanDefinition
- 手动创建的BeanDefiniton
- 编程式的加载 包扫描器加载的BeanDefinition
- xml 配置文件的手动加载BeanDefinition
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// 1. 加载手动创建的BeanDefinition
BeanDefinition personDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class)
.addPropertyValue("name", "老王").getBeanDefinition();
ctx.registerBeanDefinition("laowang", personDefinition);
// 2. 读取 xml 配置文件中的 BeanDefinition
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
reader.loadBeanDefinitions(new ClassPathResource("programmatic/programmatic-components.xml"));
// 3. 使用类扫描器 读取 BeanDefinition
// 类路径扫描器,并指定类扫描过滤器 -> Animal 实现类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(ctx);
scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
return metadataReader.getClassMetadata().getSuperClassName().equals(Animal.class.getName());
});
// int count = scanner.scan("com.linkedbear.spring.programmatic.b_di.bean");
Set<BeanDefinition> animalDefinitions = scanner
.findCandidateComponents("com.linkedbear.spring.programmatic.b_di.bean");
animalDefinitions.forEach(definition -> {
// 拿到 BeanDefinition 的 MutablePropertyValues 可以修改属性,这个在之前 BeanFactoryPostProcessor 中使用过
MutablePropertyValues propertyValues = definition.getPropertyValues();
String beanClassName = definition.getBeanClassName();
propertyValues.addPropertyValue("name", beanClassName);
propertyValues.addPropertyValue("person", new RuntimeBeanReference("laowang"));
ctx.registerBeanDefinition(Introspector.decapitalize(beanClassName.substring(beanClassName.lastIndexOf("."))), definition);
});
// 如果 BeanDefinition 是从外部读取的,然后 AnnotationConfigApplicationContext 没有指定具体的配置类,需要手动刷新才会触发 Bean 的初始化
// 更为精确的 refresh() 会触发非延迟加载的单例bean的初始化
ctx.refresh();
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
}
补充问:BeanDefinition 在读取依赖的关系的时候,如果对应 BeanDefinition 不存在会报错么
在BeanDefinition阶段,会延迟判断,直到 Bean 实例化的时候才会去检查依赖关系