天天看点

Spring4.x 笔记(2):Spring 的Ioc容器

文章目录

    • 了解Ioc容器
    • BeanFactory
      • BeanFactory 的类体系结构
      • 初始化 BeanFactory
    • ApplicationContext
      • ApplicationContext 类体系结构
      • ApplicationContext 初始化
        • 基于 Xml 配置实现
        • 基于 JavaConfig 配置实现
    • WebApplicationContext
      • WebApplicationContext 类体系结构
      • WebApplicationContext 初始化
        • 使用 ContextLoaderListener 启动 WebApplicationContext
        • 使用 SpringServletContainerInitializer 启动 WebApplicationContext
    • 父子容器
    • 参考

了解Ioc容器

  1. Spring 通过一个配置文件描述 Bean 与 Bean 之间的依赖关系,利用反射实例化 Bean 并且建立 Bean 之间的依赖关系。Spring Ioc 容器还提供了 Bean 实例缓存、生命周期管理、Bean 实例代理、事件发布、资源装载等服务
  2. BeanFactory(Bean 工厂)是 Spring 框架最核心的接口,提供了高级 Ioc 的配置机制;ApplicationContext(应用上下文)建立在 BeanFactory 的基础之上,提供更多的功能,如国际化、框架事件体系,更简单的创建实际应用。一般来说,称 BeanFactory 为 ico 容器,而 ApplicationContext 为应用上下文,其实本质上两者都是 Spring 的 Ioc 容器
  3. BeanFactory 是 Spring 框架的基础,面向 Spring 本身;ApplicationContext 面向使用 Spring 的开发者,建议在应用场合直接使用 ApplicationContext

BeanFactory

BeanFactory 的类体系结构

  1. BeanFactory 接口是基础接口,主要方法为 getBean(),从容器获取Bean 对象。BeanFactory 的功能通过其他接口得到扩展。
类名 描述
ListableBeanFactory 该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数(getBeanDefinitionCount),获取某一类型 Bean 的配置名称(getBeanNamesForType)
HierarchicalBeanFactory 父子层级关联的容器体系,子容器可以通过接口方法访问父容器。如 Spring MVC 展现层位于子容器中,业务层和持久层位于父容器中
ConfigurableBeanFactory 这个一个重要接口。增强了Ioc 容器的可定制。定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法
AutowireCapableBeanFactory 定义了兼容其中的 Bean 自动装配方法
SingletonBeanRegistry 定义了允许在运行期向容器注册单实例 Bean 的方法
BeanDefinitionRegistry spring 配置文件中每一个 节点元素在 spring 容器里都通过一个 BeanDefinition 对象表示,它描述了Bean的配置信息。而 BeanDefinitionRegistry 接口提供向先容器手工注册 BeanDefinition 对象的方法
  1. BeanFactory 接口类图
Spring4.x 笔记(2):Spring 的Ioc容器

初始化 BeanFactory

  1. 初始化 BeanFactory 容器:通过 DefaultListableBeanFactory 、XmlBeanDefinitionReader 实现。
  2. BeanFactory 容器初始化的时候,不会实例化 bean实例,第一次访问 Bean 才实例化
  3. 对于单例(singleton)的 Bean 来说,BeanFactory 会做缓存,所以第二次 getBean 的时候直接从容器中获取。Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例(singleton)的缓存器,是 Map 实现的,以 Bean 的 beanName 为键进行保存
  4. 示例
  • Bean 对象类
public interface BeanContainer {
    void done();
}
class BeanContainerImpl implements BeanContainer {
    public BeanContainerImpl() {
        System.out.println("BeanContainerImpl 被实例化了");
    }

    @Override
    public void done() {
        System.out.println("BeanContainerImpl.do() 方法被调用~~");
    }
}
           
  • 配置文件中,定义一个bean:
<bean name="beanContainer" class="com.learning.spring.ioc.container.BeanContainerImpl"/>
           
  • BeanFactory 初始化代码:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 使用 ClassPathContextResource
Resource resource = resolver.getResource("spring-context.xml");

// XmlBeanFactory factory = new XmlBeanFactory(resource); 废弃,不用
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

System.out.println("BeanFactory 容器初始化完成,但是没有实例化bean");

BeanContainer beanContainer = factory.getBean("beanContainer", BeanContainer.class);
beanContainer.done();

// 对于单例(singleton)的Bean来说,会做缓存,锁着第二次 getBean 的时候直接从容器中获取
// 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例(singleton)的缓存器,是Map实现的。
beanContainer = factory.getBean("beanContainer", BeanContainer.class);
beanContainer.done();

输出:
BeanFactory 容器初始化完成,但是没有实例化bean
BeanContainerImpl 被实例化了
BeanContainerImpl.do() 方法被调用~~
BeanContainerImpl.do() 方法被调用~~
           

ApplicationContext

ApplicationContext 类体系结构

  1. 继承了 ListableBeanFactory、HierarchicalBeanFactory接口,又扩展了 BeanFactory 功能,这些扩展的功能接口如下:
接口 描述
ApplicationEventPublisher 让容器拥有发布上下文事件的功能,包括容器的启动事件、关闭事件。(具体见spring 事件监听相关博文)
ResourcePatternResolver 让所有的 ApplicationContext 实现了类似 PathMatchingResourcePatternResolver 的功能。通过带前缀的 Ant 风格的资源文件路径装载 spring的配置文件
MessageSource 为应用提供 i18n 国际化
Lifecycle 该接口提供start()、stop() 两个方法,主要用于控制异步处理过程。该接口被 ConfigurableApplicationContext 实现,会将start\stop的信息传递给容器中所有实现了该接口的Bean,以达到管理与控制JMX、任务调度等目的
  1. ApplicationContext 类继承体系
接口与类 描述
ConfigurableApplicationContext 该接口扩展了 ApplicationContext,新增了两个主要的方法,refresh()和close(),让ApplicationContext 具有启动、刷新、关闭应用上下文的功能。
ClassPathXmlApplicationContext Xml(Sechma)配置实现的容器扩展,默认配置文件放置在类路径下,

classpath:

可以省略。如果配置文件放置在文件系统中,需要显示的使用带资源类型前缀的路径,或者直接使用 FileSystemXmlApplicationContext
AnnotationConfigApplicationContext Java Config (Java-based)实现的容器扩展
Spring4.x 笔记(2):Spring 的Ioc容器

ApplicationContext 初始化

  1. ApplicationContext 在初始化应用上下文时就实例化所有单实例(singleton)的 Bean,因此初始化的时间会比 beanFactory 长
  2. Spring 支持基于注解的配置方式,是基于 JavaConfig 实现。使用 @Configuration、 @Bean 注解实现,比 xml 文件配置更加灵活(详细的见相关博文)

基于 Xml 配置实现

  1. Bean 对象类
public interface BeanContainer {
    void done();
}
class BeanContainerImpl implements BeanContainer {
    public BeanContainerImpl() {
        System.out.println("BeanContainerImpl 被实例化了");
    }

    @Override
    public void done() {
        System.out.println("BeanContainerImpl.do() 方法被调用~~");
    }
}
           
  1. Xml 配置:定义一个bean
<bean name="beanContainer" class="com.learning.spring.ioc.container.BeanContainerImpl"/>
           
  1. 容器初始化:与 BeanFactory 的初始化输出可以做个对比
// 一般xml配置,在classpath下优先使用 ClassPathXmlApplicationContext,spring-context.xml 等同于 classpath:spring-context.xml
// 初始化的容器会实例化bean,所以一开始启动会比 BeanFactory 慢一点
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");

System.out.println("初始化 ApplicationContext 完成,同时实例化 bean");

BeanContainer beanContainer = context.getBean("beanContainer", BeanContainer.class);
beanContainer.done();
((ClassPathXmlApplicationContext) context).close();

输出:
BeanContainerImpl 被实例化了
初始化 ApplicationContext 完成,同时实例化 bean
BeanContainerImpl.do() 方法被调用~~
           

基于 JavaConfig 配置实现

  1. Bean 对象类
public interface BeanContainer {
    void done();
}
class BeanContainerImpl implements BeanContainer {
    public BeanContainerImpl() {
        System.out.println("BeanContainerImpl 被实例化了");
    }

    @Override
    public void done() {
        System.out.println("BeanContainerImpl.do() 方法被调用~~");
    }
}
           
  1. JavaConfig 配置:使用 @Configuration( 标签)、@Bean(标签)
@Configuration
public class AnnotationConfig {

    @Bean(name = "annotationBean")
    public BeanContainer beanContainer() {
        return new BeanContainerImpl();
    }
}
           
  1. 容器初始化
// AnnotationConfig 配置类,相当于<beans> 标签
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
System.out.println("初始化 AnnotationConfigApplicationContext 完成, 同时实例化 bean");

BeanContainer beanContainer = context.getBean("annotationBean", BeanContainer.class);
beanContainer.done();
context.close();

输出:
BeanContainerImpl 被实例化了
初始化 AnnotationConfigApplicationContext 完成
BeanContainerImpl.do() 方法被调用~~
           

WebApplicationContext

WebApplicationContext 类体系结构

  1. WebApplicationContext 是专门为 Web 应用准备,扩展了 ApplicationContext 的功能,实现了与 ServletContext 的相互访问
功能 描述
加载配置路径 默认从Web根目录下加载配置文件,可以显示使用资源文件前缀,如 classpath:XXX
增加 Bean作用域 在非 web 环境,Bean 只有singleton、prototype,新增 request、session、global session、application(具体见Scope 相关博文)
支持获取 ServletContext 定义了一个常量 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ,在上下文启动时,WebApplicationContext 实例以此键放置在 ServletContext 的属性列表中,通过 getServletContext() 方法获取 ServletContext 对象
支持在 ServletContext 中获取WebApplicationContext 工具类 WebApplicationContextUtils.getWebApplicationContext() 方法可以从 servletContext 中获取 WebApplicationContext 实例,内部就是通过 getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) 方法获取的
  1. WebApplicationContext 类继承体系
类名 描述
ConfigurableWebApplicationContext 扩展了 WebApplicationContext,允许通过配置的方式实例化 WebApplicationContext。setServletContext 为Spring设置 Web 应用上下文;setConfigLocation 设置spring 配置文件地址,一般情况下,配置文件地址是相对于 Web 根目录的地址,如/WEB-INF/XXX。可以使用带资源类型前缀的地址,classpath:XXX 指定
XmlWebApplicationContext 支持使用 Xml 配置实现
AnnotationConfigWebApplicationContext 支持使用 JavaConfig 实现
Spring4.x 笔记(2):Spring 的Ioc容器

WebApplicationContext 初始化

  1. WebApplicationContext 初始化,需要ServletContext 实例,就需要Web 容器支持。需要在 Web.xml 中配置web容器监听器(ServletContextListener)
  2. Spring 提供了用于启动 WebApplicationContext 的 Web 容器监听器:

    org.springframework.web.context.ContextLoaderListener

使用 ContextLoaderListener 启动 WebApplicationContext

  1. Xml 配置实现的web配置
  • 通过获取 contextConfigLocation 上下文参数,得到 spring 配置文件的位置。多个配置文件可以使用逗号、空格等分隔。不显示指定资源类型前缀,默认从Web的根路径获取
// 指定配置文件
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/spring/spring-context.xml</param-value>
</context-param>

// 监听器
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
           
  1. 使用注解 @Configuration实现的Java类提供配置信息,web.xml 配置如下:
  • 配置 contextClass 参数,指定Spring 使用 AnnotationConfigWebApplicationContext 上下文替代 XmlWebApplicationContext
  • ContextLoaderListener 获取参数 contextClass ,如果不为空,

    createWebApplicationContext()

    方法会创建 AnnotationConfigWebApplicationContext 上下文,并且解析 contextConfigLocation 获取的标注了@Configuration 的类
// JavaConfig 配置,配置 contextClass 参数,指定Spring 使用  AnnotationConfigWebApplicationContext 上下文
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
// 指定配置类:配置标注了@Configuration 的类
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>cn.com.sinosoft.smp.web.sys.config.SysConfig</param-value>
</context-param>

// 监听器,会根据上面配置使用 AnnotationConfigWebApplicationContext 根据 contextConfigLocation 指定的配置类启动容器
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
           

使用 SpringServletContainerInitializer 启动 WebApplicationContext

  1. Servlet3.x 新增 ServletContext 的性能增强:(具体的新功能见博文 Servlet笔记系列(12):Servlet3.X版本新特性)
  • 支持在运行时动态部署监听器、过滤器等
  • 使用web容器初始化接口

    ServletContainerInitializer

    取代web.xml配置
  • ServletContainerInitializer

    使用
    • /META-INF/services/javax.servlet.ServletContainerInitializer

      文件中配置实现类的全路径类名
    • 实现类需要使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类
  1. Spring 中

    org.springframework.web.SpringServletContainerInitializer

    实现了

    ServletContainerInitializer

    接口:
  • 在 spring-web 模块中有如下配置:

    /META-INF/services/javax.servlet.ServletContainerInitializer

    ,内容为

    org.springframework.web.SpringServletContainerInitializer

  • 并且通过 @HandlesTypes 注解指定

    org.springframework.web.WebApplicationInitializer

    来处理具体的业务
  1. 实现方法:
  • 实现 WebApplicationInitializer,重写 onStartup 方法,依次添加各种配置,如spring的context上下文、Spring Listener。
public class WebInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 注册上下文与配置,这边使用 Xml 实现或者 JavaConfig 实现都可以
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(MvcConfig.class);
        ctx.setServletContext(servletContext);

        // 添加 Spring Listener
        servletContext.addListener(new ContextLoaderListener(ctx));

        // 添加 CharacterEncodingFilter
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("utf-8");
        FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
        registration.setAsyncSupported(isAsyncSupported());
        registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");

        // 配置spring mvc,这边可以配置servlet的配置
        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);

    }

    private boolean isAsyncSupported() {
        return true;
    }

    private EnumSet<DispatcherType> getDispatcherTypes() {
        return isAsyncSupported() ?
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
    }
}
           
  • 默认 WebApplicationInitializer 有一个实现类 AbstractContextLoaderInitializer,可以实现他,重写

    createRootApplicationContext()

    即可。
  • Spring-mvc 模块中有 AbstractDispatcherServletInitializer、AbstractAnnotationConfigDispatcherServletInitializer 类实现了

    WebApplicationInitializer

    接口,使用 springmvc 可以直接继承,比较简单
public class WebInitializer2 extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
           

父子容器

  1. 通过 HierarchicalBeanFactory 接口,Spring 容器可以实现父子层级关联的容器体系,子容器可以通过接口方法访问父容器中的 Bean,但是父容器不能访问子容器中的 Bean
  2. 在容器中 Bean 的id 必须是唯一的,但是子容器可以拥有一个和父容器id相同的 Bean
  3. 在 Spring MVC 中,展现层 Bean 位于子容器中,业务层和持久层 Bean 位于父容器中,这样展现层 Bean 可以引用业务层和持久层 Bean,反之就不可以

参考

  1. 源码地址
  2. Servlet笔记系列(12):Servlet3.X版本新特性
Spring4.x 笔记(2):Spring 的Ioc容器