java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > IOC 容器启动Bean实例化

IOC 容器启动和Bean实例化两个阶段详解

作者:昨天的风

这篇文章主要为大家介绍了IOC 容器启动和Bean实例化两个阶段详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

IOC 容器的两个阶段

IOC 容器可以分为两个阶段 : 容器启动阶段和 Bean 实例化阶段。

Spring 的 IoC 容器在实现的时候, 充分运用了这两个阶段的不同特点, 在每个阶段都加入了相应的容器扩展点, 支持开发者根据具体场景的需要, 添加自定义的扩展逻辑.

容器启动阶段

容器启动, 首先要加载配置元数据 ( Configuration MetaData ).

容器使用工具类 BeanDefinitionReader 对加载的配置元数据进行解析和分析, 并将分析后的信息组装为相应的 Bean 定义对象 BeanDefinition, 最后把这些保存了 bean 定义必要信息的 BeanDefinition, 注册到相应的 BeanDefinitionRegistry, 这样容器启动工作就完成了. ( 将 XML 信息映射到 BeanDefinition 对象上 )

BeanDefinition 对象中保存的属性很多,如下 :

BeanDefinitionRegister 接口用来注册 BeanDefinition. 该接口的默认实现类为 DefaultListableBeanFactory. 在该实现类中, 有一个成员属性定义如下 :

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, bean>(64); 

BeanDefinition 就是被保存到这个 map 中的, key 为 beanName, value 为 BeanDefinition 对象.

Bean 实例化阶段

当调用者通过容器的 getBean() 方法明确地想要获取某个对象, 或者因依赖关系, 容器需要隐式地调用 getBean() 方法时, 就会触发容器的第二阶段.

该阶段, 容器会首先检查所请求的对象之前是否已经初始化. 如果没有, 则会根据注册的 BeanDefinition 的信息, 实例化这个对象 , 并为其注入依赖. 如果该对象实现了某些回调接口, 也会根据回调接口的要求来装配它. 当该对象装配完毕之后, 容器会立即将其返回给请求方去使用.

Bean 创建的步骤和普通对象的创建步骤不同, 普通的对象在创建时, 由类加载器找到 xxx.class 文件, 然后创建 xxx 对象即可. Spring 中 bean 的创建多了一步, 先是由 类加载器找到 xxx.class 文件, 然后将其解析组装为 BeanDefinition 对象, 然后 Spring 根据 BeanDefinition 信息来创建对象.

可以看到, Spring 在实例化对象时, 脱离了配置元数据中的信息, 而是使用的 BeanDefinition 中的信息, 这就意味着我们可以修改 BeanDefinition, 从而改变 Spring 生成的对象的属性, 甚至是修改最后生成的对象所属的类.

插手容器的启动

Spring 提供了一种叫做 BeanFactoryPostProcessor 的容器扩展机制. 该机制允许开发者在容器实例化相应 Bean 对象之前, 对注册到容器的 BeanDefinition 的信息做出修改. 即在容器的第一阶段的最后进行一些自定义操作. 比如修改其中 bean 定义的某些属性, 为 bean 定义增加其他信息等.

Spring 中自带的 BeanFactoryPostProcessor 接口的实现:

同时也支持开发者通过实现 BeanFactoryPostProcessor 接口自定义扩展.

PropertyPlaceholderConfigurer 占位符机制

PropertyPlaceholderConfigurer 允许开发者在 XML 配置文件中使用占位符, 并将这些占位符所代表的资源单独配置到简单的 properties 文件中来加载.

比如将数据库的连接信息保存在 properties 中.

<context:property-placeholder location="classpath:dbconfig.properties" />
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
  <property name="driverClass" value="${jdbc.driverClass}"></property>
  <property name="user" value="${jdbc.user}"></property>
  <property name="password" value="${jdbc.password}"></property>
</bean>

基本流程 :

PropertyOverrideConfigurer 重写属性值

PropertyOverrideConfigurer 允许你对容器中配置的任何你想处理的 bean 定义的 property 信息进行覆盖替换.

PropertyOverrideConfigurer 的 properties 文件中的配置项, 可以覆盖掉原来 XML 中的 bean 定义的 property 信息.

实例-使用容器扩展点修改 BeanDefinition

此案例使用注解的方式来配置元数据.

现在有两个类, 一个是 User 类, 一个是 Good 类, User 类归 Spring 管理, 而 Good 类并不归 Spring 管理. 类的定义如下 :

@Data
@AllArgsConstructor
@Accessors(chain = true)
@Component              //被 Spring 扫描
public class User {
    private int id;
    private String name;
    public User(){
        System.out.println("调用了 user 的无参数的构造方法");
    }
}
@Data
@AllArgsConstructor
@Accessors(chain = true)
public class Good {
    private int id;
    private String name;
    public Good(){
        System.out.println("调用了 good 的无参数的构造方法");
    }
}

AppConfig 类进行注解的扫描.

@ComponentScan(value = {"it.com"})
public class AppConfig {
}

Test 类用来测试 :

public static void main(String[] args){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    User user = (User) context.getBean("user");
    Good good = (Good) context.getBean("good");
}

运行测试类, 结果如下 :

User 类配置了被 Spring 扫描, 所以可以获取到 user 对象, 而 Good 类没有配置被扫描, 所以无法获取 good 对象, 而报错.

现在我们要做的就是, 让没有被 Spring 管理的 Good 类, 也能从 Spring 容器中获取到它的实例. 实现上述目的的思路就是, 对容器启动的第一阶段生成的 User 类的 BeanDefinition 类做修改.

创建自定义的 BeanFactoryPostProcessor 如下 :

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 通过 Spring 中 bean 的名字获取到指定的 bean
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanFactory.getBeanDefinition("user");
        // 输出 beanDefinition 的信息
        System.out.println(beanDefinition.getBeanClass());
        System.out.println(beanDefinition.getFactoryBeanName());
        // 然后进行狸猫换太子,将 User 类对应的 BeanDefinition 对象的 beanClass 属性替换掉
        beanDefinition.setBeanClass(Good.class);
    }
}

然后再次运行测试类, 结果如下 :

可以看到,虽然表面上是通过 getBean("user") 来获取 user 对象,但是实际调用的确实 Good 对象的构造方法,返回的是 good 对象. 但 Good 并没有让 Spring 扫描. 这个例子就展示了如何通过 BeanFactoryPostProcessor 机制插手容器的启动

以上就是IOC 容器启动和Bean实例化两个阶段详解的详细内容,更多关于IOC 容器启动Bean实例化的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文