Loading

Spring的IoC和Bean

Spring的IoC和Bean

Spring框架的特性之一就是IoC(Invocation of Control)容器。IoC中文称作控制反转,为什么这么命名呢?以前都是由开发者来控制对象实例的创建、依赖等,而控制反转,顾名思义,就是Spring会帮我们完成对象的创建和依赖等操作,开发者最多只需要做一些配置或注解的声明即可。所以,IoC容器的出现为基于Spring框架开发的程序员来说大大地提高了开发效率。

Bean其实就是一个对象实例,只不过和一般的对象实例不同的是,它存储来IoC容器中,由IoC容器维护创建、依赖等操作。并且,Spring会为Bean创建BeanDefinition配置信息,用于描述这个Bean创建、依赖需要的元数据,这也是Bean和普通的对象实例很大的区别之一。

BeanFactory和ApplicationContext

BeanFactory是Spring框架内置的Bean工厂类,可用于获取创建的Bean实例或者判断Bean的类型,一般在框架逻辑中使用,不用于业务场景;而ApplicationContext,顾名思义,就是整个应用的上下文,可用于获取应用管理的Bean信息,常用于业务环境下获取IoC容器中维护的Bean。

Bean元数据配置

Bean的元数据配置一般有三种方式:

1、XML

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

2、注解

@Component("dataManager")
public class DataManager {

    public String sayHello(String name) {
        return "hello, " + name;
    }
}

3、Java Config

@Configuration
public class WebConfiguration {
    
    @Bean(name = "dataManager1", initMethod = "init", destroyMethod = "destroy")
    public DataManager getDataManager() {
        return new DataManager();
    }
}

Bean的生命周期

1. BeanDefinition

扫描通过XML、注解、javaConfig定义的bean生成对应的元数据信息BeanDefinition,然后存储BeanDefinitionMap中,key为beanName,value为BeanDefinition,用于后续创建bean使用。

2. BeanFactoryPostProcessor

BeanFactoryPostProcessor是BeanFactory后置处理器,用于对收集完的BeanDefinition进行修改。它会遍历BeanDefinitionMap中的BeanDefinition,执行所有实现类逻辑。

比如PropertyPlaceholderConfigurer,用于将Bean配置中的占位符替换成目标值,下面举个简单的例子说明:

1)声明PropertyPlaceholderConfigurer实例,指定配置文件路径,允许未找到占位符目标值;

2)声明一个存在占位符的bean实例,并在jdbc.properties中定义占位符目标值;

3)创建一个自定义BeanFactoryPostProcessor实现类,监控替换前的name和value;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd ">

    <!--ignore-unresolvable:默认false,如果没有找到占位符的替换值,直接报错-->
    <context:property-placeholder location="classpath:xml/jdbc.properties" ignore-unresolvable="true"/>

    <bean class="org.xiangzhu.test.common.EchoBeanFactoryPostProcessor"/>

    <bean id="dataSource"
          class="org.xiangzhu.test.common.DataSource">
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
public class EchoBeanFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition dataSourceBd = beanFactory.getBeanDefinition("dataSource");
        MutablePropertyValues propertyValues = dataSourceBd.getPropertyValues();
        log.info("postProcessBeanFactory pv size = {}", propertyValues.size());
        //打印占位符的name和value
        propertyValues.forEach(pv -> log.info("postProcessBeanFactory name={}, value={}", pv.getName(), pv.getValue()));
    }

    //最高执行优先级
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

执行结果:

3. Create&PopulateBean

Bean三级缓存:

  • bean对象实例;

  • 早期bean对象实例(未依赖注入);

  • beanFactory对象(实现了createBean方法的闭包对象);

为了理解前面Bean三级缓存的执行时机,我们举个例子说明下,现在有两个对象A和B,它们相互依赖对方,来看下它们的实例化过程:

  • 假设A先实例化

    • 由于A不存在缓存,调用doCreateBean方法创建A,并写入三级缓存

  • A依赖注入

    • A依赖B,B不存在缓存,B实例化,写入三级缓存

    • B依赖注入

      • A存在缓存,将三级缓存写入二级,并移除三级缓存

      • B依赖注入完成

    • B初始化

    • B写入一级缓存

      doGetBean -》getSingleton -》addSingleton(写入一级缓存)

    • A依赖注入完成

  • A初始化

  • A写入一级缓存

上面介绍的AB实例加载顺序同样也解答了Spring是如何处理循环依赖问题的,对于循环依赖,它本身是一个设计问题,不应该由框架层面来解决,虽然Spring提供了循环依赖的解决方案,但是它同样带来了负面问题,比如应用启动不稳定、系统复杂性上升、测试样例设计困难等问题。所以,Spring框架在2.6版本后默认不启用循环依赖支持,因为这个问题根本上应该由业务开发者优化依赖解决。

4.InitializeBean

如上图,初始化方法执行顺序为:

  • Aware方法
  • BeanPostProcessor Before
    • ApplicationContextAwareProcessor(Aware方法)
    • AbstractAutoProxyCreator(代理对象生成)
    • InitDestroyAnnotationBeanPostProcessor(@PostConstruct方法)
    • ...
  • InitMethods
    • InitializingBean(afterPropertiesSet)
    • init-method
  • BeanPostProcessor After

1) Aware

在Spring中Aware是一个接口,aware英文有感知的含义,所以实现Aware接口的对象具备感知框架运行状态和上下文的能力。常见的Aware细化接口有:

  • BeanNameAware
  • BeanClassloaderAware
  • BeanFactoryAware
  • ApplicationContextAware(最常用,感知应用上下文)

以下是对应Aware接口的回调位置,第一个是在invokeAwareMethods中:

第二个是在ApplicationContextAwareProcessor这个后置处理器的invokeAwreInterfaces方法中:

2) BeanPostProcessor

BeanPostProcessor是一个后置处理器接口,在Bean的初始化前后做一些事情,包含before和after方法。这是Spring框架的重要拓展点之一,该接口实现了AbstractAutoProxyCreator这个后置处理器实例,使得框架可以在其after方法中完成代理对象的生成,从而实现Spring重要特性之一:AOP代理。

其他常见的BeanPostProcessor还有AutowiredAnnotationBeanPostProcessor,它也用于占位符替换目标值,不过应用的是@Value("${aaa.bbb}")的场景。

3) InitMethods

InitMethod用于在Bean实例化完成之后,执行一些开发者自定义的初始化方法,InitMethod和执行顺序如下:

  • @PostConstruct

  • InitializingBean的afterPropertiesSet

  • init-method

下面是InitMethods的具体执行时机:

@PostConstruct注解的方法在InitDestroyAnnotationBeanPostProcessor的before方法中回调。

而afterPropertiesSet和init-method方法在invokeInitMethods方法中回调。

对于InitMethods和BeanFactory的执行顺序,下面举例说明下:

1)创建UserService对象,并实现拓展点

public class UserService implements InitializingBean {

    //step2 di
    @Resource
    private DataManager dataManager;

    @Value("${mysql.namespace}")
    private int namespace;

    //step1 construct
    public UserService() {
        log.info("UserService construct, namespace = {} dataManager = {}", namespace, dataManager);
    }

    //step3 postConstruct
    @PostConstruct
    private void postConstruct() {
        log.info("UserService postConstruct, namespace = {} dataManager = {}", namespace, dataManager);
    }

    //step4 afterPropertiesSet
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("UserService afterPropertiesSet, namespace = {} dataManager = {}", namespace, dataManager);
    }

    public int getNamespace() {
        return namespace;
    }

    public void setNamespace(int namespace) {
        this.namespace = namespace;
    }
}

2)实现BeanPostProcessor自定义类,监控UserService对象,并修改属性值

public class EchoBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getName().startsWith("org.xiangzhu.test")) {
            log.info("postProcessBeforeInitialization beanName = {}, bean = {}", beanName, bean);

            if (bean instanceof UserService) {
                UserService userService = (UserService) bean;
                userService.setNamespace(666);
            }
        }
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getName().startsWith("org.xiangzhu.test")) {
            log.info("postProcessAfterInitialization beanName = {}, bean = {}", beanName, bean);
        }
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

3)应用启动完成后,打印UserService属性值,观察是否修改成功

public class SpringLearnApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext context = SpringApplication.run(SpringLearnApplication.class, args);
		UserService userService = context.getBean(UserService.class);
		log.info("applicationContext bootstrap finish, userService.namespace = {}", userService.getNamespace());
	}

}

4)执行结果

5.使用中

使用实例执行业务逻辑。

6.销毁

调用destroy-method,实例gc。

其他

autoconfig

SpringBoot之AutoConfiguration自动配置原理

Spring Boot Starter自动配置的加载原理

EventListener

EventListenerMethodProcessor是一个BeanFactoryPostProcessor,后置处理中通过遍历所有bean,找到其中添加了@EventListener注解的,并将其包装成ApplicationListenerMethodAdapter,添加到上下文,从而使得@EventListener注解的类和实现了ApplicationListener的类目功能一样,可以监听Spring环境中发布的事件Event。

参考

怎么理解spring bean的生命周期,实际应用场景? - 知乎 (zhihu.com)

Spring源码解读『占位符@Value(“${…}”)替换』_卓立~的博客-CSDN博客

[Spring源码解读『占位符${…}替换』_spring 占位符字符串替换_卓立的博客-CSDN博客](https://blog.csdn.net/weixin_41835612/article/details/107242055#::text=Spring处理以上两种占位符的替换采用不同的方式, xml注入的占位符Spring采用bean工厂后置处理器处理,注解方式的占位符Spring采用bean后置处理器处理,,本篇文章我们先来看一下xml注入的占位符的替换过程。 1. xml占位符注入示例)

posted @ 2023-05-13 15:19  flowers-bloom  阅读(67)  评论(0)    收藏  举报