阅读 792

浅析 Spring 的IOC容器

原文博客地址:pjmike的博客

前言

在前面的文章 浅析Spring 的IoC和DI中简述了 IOC和DI的基本概念和关系,总体上说,IOC 是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式,那么Spring 提供了两种容器类型来提供支持 IOC方式。这两种类型是:

  • BeanFactory: 基础类型的IOC容器,提供完整的IOC服务支持
  • ApplicationContext: ApplicationContext是在 BeanFactory的基础之上构建的,是相对高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext提供了其他高级特性。

ApplicationContext 和 BeanFactory的继承关系如下:

applicationContext

可以看到 ApplicationContext 间接继承自 BeanFactory。

BeanFactory

BeanFactory的介绍

BeanFactory 是基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入工作

BeanFactory的对象注册

BeanFactory,就是生产 Java Bean 的工厂,作为Spring 提供的基本的IoC容器,BeanFactory 帮助完成 业务对象的注册和对象间依赖关系的绑定

实际上,BeanFactory只是一个接口,它负责定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。下面是BeanFactory的接口代码:

package org.springframework.beans.factory;

public interface BeanFactory {

    /**
     * 用来引用一个实例,或把它和工厂产生的Bean区分开,就是说,如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
     */
    String FACTORY_BEAN_PREFIX = "&";

    /*
     * 四个不同形式的getBean方法,获取实例
     */
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    boolean containsBean(String name); // bean是否存在

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为单实例

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例)

    boolean isTypeMatch(String name, Class<?> targetType)
            throws NoSuchBeanDefinitionException;// 名称、类型是否匹配

    Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 获取类型

    String[] getAliases(String name);// 根据实例的名字获取实例的别名

}
复制代码

下面我们来测试下一般情况下 BeanFactory接口的具体实现类情况:


// 实体类
@Component
public class Demo {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//Junit测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Autowired
    private BeanFactory beanFactory;
    @Test
    public void test() {
        System.out.println("concrete factory is: " + beanFactory.getClass());
        Assert.assertTrue("Factory can't be null",beanFactory != null);
        Demo demo = (Demo) beanFactory.getBean("demo");
        System.out.println("Found the demo bean: "+demo.getClass());
    }

}
复制代码

输出结果如下:

concrete factory is: class org.springframework.beans.factory.support.DefaultListableBeanFactory
Found the demo bean: class com.pjmike.spring.Demo
复制代码

从结果可以看出,具体工厂是 org.springframework.beans.factory.support.DefaultListableBeanFactory的实例。再来看看 BeanFactory的继承体现:

BeanFactory

从上图可以看出,BeanFactory有三个直接子类:

  • ListableBeanFactory: 通过继承该接口可以列出所有的Bean,也可以只列出与预期类型相对应的bean
  • HierarchicalBeanFactory: 支持分层bean的管理,使BeanFactory支持双亲IOC容器的管理功能
  • AutowireCapableBeanFactory: 可以填充不受Spring 控制的 Bean

而三个类的子类体系就更多,详细的参考 Spring 源码。

再来看看之前提到的DefaultListableBeanFactory,它也是上图中最底层的实现类:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
		...
}
复制代码

这个类其实就是 BeanFactory的默认实现类,一个比较通用的BeanFactory实现类,它除了间接实现 BeanFactory接口外,还实现了 BeanDefinitionRegistry接口,该接口才是BeanFactory实现中担任 Bean注册管理的角色,它抽象的定义了Bean注册的逻辑,当然具体的是实现还是靠DefaultListableBeanFactory这等实现类。

ApplicationContext

ApplicationContext的介绍

ApplicationContext是在BeanFactory的基础上构建的,是相对比较高级的容器实现,除了拥有 BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如:

  • 统一资源加载策略
  • 国际化信息支持
  • 容器内部事件发布机制

在ApplicationContext 容器启动之后,默认全部初始化并绑定完成,所以,对于BeanFactory来说,ApplicationContext 往往要求更多的系统资源

ApplicationContext的实现

Spring 中的 Context

Spring 为基本的 BeanFactory 类型容器提供了 XmlBeanFactory 实现(继承自DefaultListableBeanFactory),相应的,它也为 ApplicationContext 类型容器提供了以下几个常用的实现:

  • org.springframework.context.support.FileSystemXmlApplicationContext: 在默认情况下,从文件系统加载 bean 定义以及相关资源的 ApplicationContext 实现
  • org.springframework.context.support.ClassPathXmlApplicationContext: 在默认情况下,从Classpath 加载bean 定义以及相关资源的 ApplicationContext 实现
  • org.springframework.web.context.support.XmlWebApplicationContext: Spring提供的用于 Web 应用程序的 ApplicationContext 实现。

在传统的基于 XML的Spring项目中,经常会使用到上面的实现类

SpringBoot 中的 Context

在官方文档中给出对于一个 SpringBoot 应用它对应的Context的情况:

context

  • 对于 web应用,context 是 AnnotationConfigServletWebServerApplicationContext
  • 对于 响应式应用,context 是 AnnotationConfigReactiveWebServerApplicationContext
  • 对于普通非 web应用,context 是 AnnotationConfigApplicationContext

以上的 context实际上也是实现了 ApplicationContext接口

ApplicationContext 的简单实践

我们都知道IOC容器一般有两种对象注入方式:基于XML配置文件 与 基于注解驱动的方式。下面就分别从这两个角度来看如何使用 ApplicationContext

基于 XML 配置文件

  1. 定义个实体类
public class User {
    private Integer id;

    private String username;

    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }

    public User() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
复制代码
  1. 设置一个XML配置文件,声明 User Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.pjmike.spring.domain.User">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="username" value="pjmike"/>
    </bean>
</beans>
复制代码
  1. 主程序
public class XmlBootStrap {
    public static void main(String[] args) {
        //构建一个 ApplicationContext 上下文
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        //设置此应用上下文的配置路径
        context.setConfigLocations("classpath:/META-INF/spring/context.xml");
        //调用 refresh 方法,完成配置的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造
        context.refresh();
        User user = context.getBean("user", User.class);
        System.out.print("user.getName() = "+ user.getUsername());
    }
}
复制代码

输出结果

user.getName() = pjmike
复制代码

基于注解方式

  1. 声明一个配置类
@Configuration
public class UserConfiguration {
    @Bean(name = "user")
    public User user() {
        User user = new User();
        user.setUsername("pj");
        return user;
    }
}
复制代码
  1. 主程序
public class AnnotationBootStrap {
    public static void main(String[] args) {
        // 构建一个 ApplicationContext 应用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册一个配置 Bean
        context.register(UserConfiguration.class);
        // 调用 refresh 启动容器
        context.refresh();
        User user = context.getBean("user", User.class);
        System.out.println("user.getName() = "+user.getUsername());
    }
}
复制代码

输出结果

user.getName() = pj
复制代码

XML 与 Annotation 简单对比

从上面的两个例子可以看出基于XML和基于注解注入Bean 的方式是不一样的,基于XML的应用上下文ClassPathXmlApplicationContext需要设置配置路径,基于注解的应用上下文AnnotationConfigApplicationContext需要注册一个配置Bean,但它们相同的一步就是必须要调用 refresh()方法,该方法可以看做是IOC容器的启动方法,它会做很多操作,比如完成配置的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造等等,不调用它,容器就无法启动。这里只是简要说明,更加详细的介绍会在后面的文章介绍。

现在是springboot盛行的阶段,基于XML配置文件的方式已经逐步被基于注解的方式所取代,如今的项目中,更多的使用 注解的方式。 关于XML与注解更详细的对比可以参阅开涛大神的文章: jinnianshilongnian.iteye.com/blog/187991…

小结

上面的文章比较简单的总结了 BeanFactory 和 ApplicationContext,为后续分析Spring IOC详细的初始化过程、Spring Bean的加载等做一个铺垫

参考资料 & 鸣谢

关注下面的标签,发现更多相似文章
评论