当前位置: 首页 > article >正文

spring-ioc-高阶知识

高阶知识重点在于单独介绍 Spring 一些稍微偏底层的知识,不涉及源码的情况下介绍 Spring 的一些核心组件和后处理器

配置源与配置元信息

配置源可以来自于 xml 和 一个 Configuration 类,用于后续spring容器创建bean的时候读取(BeanDefiniton)

bean 的元信息

  1. 全限定名 className
  2. 作用域 scope
  3. 是否延迟加载 lazy

ioc容器 配置的元信息

  1. beans
    • profile
  2. context
    • context:annotation-config/ 开启注解驱动
    • context:component-scan/ 开启组件扫描
    • context:property-placeholder/ 引入外部的资源文件( properties xml yml 等)

Environment

是在 ApplicationContext 创建之后创建的,随着 ApplicationContext 存在而存在,影响后面 bean 的注册和创建

本身也是一个特殊的 bean

包括

  1. profile 基于模式的环境
  2. 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 AutowiredAnnotationBeanPostProcessorspring 用来实现注解的自动注入

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 实例化的时候才会去检查依赖关系


http://www.kler.cn/a/539529.html

相关文章:

  • 2025/2/10 心得
  • 电脑重启后vscode快捷方式失效,找不到code.exe
  • git命令行删除远程分支、删除远程提交日志
  • DeepSeek在FPGA/IC开发中的创新应用与未来潜力
  • ChunkKV:优化 KV 缓存压缩,让 LLM 长文本推理更高效
  • Windows 本地部署大模型 OpenWebUI+Ollama
  • PHP语言的物联网
  • python实战(十六)——基于LSTM的股价预测
  • MCU应用踩坑笔记(ADC 中断 / 查询法)
  • UnityShader学习笔记——渲染路径
  • 数据表记录
  • Nginx配置 ngx_http_proxy_connect_module 模块及安装
  • 加速汽车软件升级——堆栈刷写技术的应用与挑战
  • go语言文件和目录
  • RuoYi-Vue-Oracle的oracle driver驱动配置问题ojdbc8-12.2.0.1.jar的解决
  • 用jit部分python函数效率过低的问题
  • java: framework from BLL、DAL、IDAL、MODEL、Factory using oracle
  • HTML之CSS三大选择器
  • 110,【2】攻防世界 web mfw
  • ScrapeGraphAI颠覆传统网络爬虫技术
  • 未来科技趋势浅析
  • scss模块化
  • 如何用.NET Core Identity实现定制化的用户身份验证系统
  • Vue 响应式渲染 - 条件渲染
  • PHP-综合3
  • PrimeFaces Poll组件实现周期性Ajax调用