Spring笔记(三)—— IOC 容器之 BeanFactory 和 ApplicationContext

简介

BeanFactory 是 Spring 框架最核心的接口,它提高了高级 IOC 的配置机制。BeanFactory 使管理不同类型的 Java 对象成为可能,ApplicationContext 建立在 BeanFactory 的基础之上,提供了更多面向应用的功能,提供了国际化支持和框架事件体系,更易于创建实际应用。BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身,ApplicationContext 面向使用 Spring 框架的开发者。

BeanFactory

BeanFactory 提供了 Spring IOC 的基础功能,但它只能在第三方框架中直接使用。和 BeanFactory 相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,仍然存在于 Spring 中用于与 Spring 集成的大量第三方框架向后兼容的作用。BeanFactory 通常使用在运行于资源有限(内存消耗严重)的嵌入式应用中。

  • 使用 BeanFactory 实现注册一个后处理器(PostProcessor):

    1
    2
    3
    4
    5
    6
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // populate the factory with bean definitions
    // now register any needed BeanPostProcessor instances
    MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
    factory.addBeanPostProcessor(postProcessor);
    // now start using the factory
  • 使用 BeanFactory 实现注册一个 BeanFactoryPostProcessor:

    1
    2
    3
    4
    5
    6
    7
    8
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
    // bring in some property values from a Properties file
    PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
    cfg.setLocation(new FileSystemResource("jdbc.properties"));
    // now actually do the replacement
    cfg.postProcessBeanFactory(factory);

ApplicationContext

ApplicationContext 继承自 BeanFactory,提供了更多面向实际应用的功能。在 BeanFactory 中,很多功能需要以编程的方式实现,而在 ApplicationContext 中则可以通过配置的方式实现。

下图为 ApplicationContext 的继承体系图:
beanfactory

ApplicationContext 的主要实现类是 ClassPathXmlApplicationContext,FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext,第一个默认从类路径加载配置文件,第二个默认从文件系统中装载配置文件,第三个可直接传入注解类,通过 register(Class…) 注册类或 scan(String…) 扫描路径 classpath 逐个注册类。

如上图所示,ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此之上还通过其他的接口扩展了 BeanFactory 的功能。这类接口包括:

  • ApplicationEventPublisher:让容器拥有发布 ApplicationContext 事件的功能,包括容器启动时间、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件,并对事件进行响应处理。在 ApplicationContext 抽象实现类 AbstractApplicationContext 中,可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生 ApplicationContext 事件时通知这些事件监听者。
  • MessageSource:为应用提供 i18n 国际化消息访问的功能。
  • ResourcePatternResolver:所有 ApplicationContext 实现类都实现了通过 Ant 风格的资源文件路径装载 Spring 的配置文件。
  • Lifecycle:该接口于 Spring2.0 加入,提供了 start() 和 stop() 两个方法,主要用于控制异步处理过程。具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现,ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。

ConfigurableApplicationContext 继承自 ApplicationContext,增加了两个主要的方法:refresh() 和 close(),让 ApplicationContext 具有启动、刷新和关闭上下文的能力。在 ApplicationContext 关闭时,调用 refresh 可启动 ApplicationContext;在启动状态下调用,则清除缓存并重新装载配置信息。调用 close 关闭 ApplicationContext。

ApplicationContext 的初始化:

ApplicationContext 在实例化后和 BeanFactory 一样调用 getBean(beanName) 返回 Bean;在初始化时,BeanFactory 初始化容器时并未实例化 Bean 直至第一次访问 Bean,ApplicationContext 在初始化上下文时实例化所有单实例的 Bean。和基于 XML 文件配置方式的相比,类注解的配置方式可以很容易地让开发者控制 Bean 的初始化过程。

  • 配置文件在类路径下,优先使用 ClassPathXmlApplicationContext,“com/example/context/beans.xml” 等同于 “classpath:com/example/context/beans.xml”:

    1
    2
    3
    4
    ApplicationContext ctx = new
    ClassPathXmlApplicationContext("com/example/context/beans.xml");
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[]{ "com/beans1.xml", "com/beans2.xml" });
  • 配置文件在类路径下,优先使用 FileSystemXmlApplicationContext,“com/example/context/beans.xml” 等同于 “file:com/example/context/beans.xml”:

    1
    2
    ApplicationContext ctx = new
    FileSystemXmlApplicationContext("com/example/context/beans.xml");
  • 需要解析的是注解类,使用 AnnotationConfigApplicationContext:

    • Bean 类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      package com.lake.context;
      public class Car {
      private String brand;
      private int maxSpeed;
      public String getBrand() {
      return brand;
      }
      public void setBrand(String brand) {
      this.brand = brand;
      }
      public int getMaxSpeed() {
      return maxSpeed;
      }
      public void setMaxSpeed(int maxSpeed) {
      this.maxSpeed = maxSpeed;
      }
      }
    • 带注解的 POJO 类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package com.lake.context;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      @Configuration
      public class Beans {
      @Bean(name = "car")
      public Car buildCar() {
      Car car = new Car();
      car.setBrand("奔驰");
      car.setMaxSpeed(300);
      return car;
      }
      }
    • 通过带 @Configuration 的 POJO 类启动容器:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      package com.lake.context;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      public class AnnotationApplicantionContext {
      public static void main(String[] args) {
      ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);
      Car car = context.getBean("car", Car.class);
      car.getBrand();
      }
      }

WebApplicationContext

WebApplicationContext 为 Web 应用而准备,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。Spring 提供了工具类 WebApplicationContextUtils,通过 getWebApplicationContext(ServletContext sc) 获取 WebApplicationContext 实例。

ConfigurableWebApplicationContext 继承自 WebApplicationContext,它允许通过配置的方式实例化 WebApplicationContext,它有两个重要的方法:

  • setServletContext(ServletContext servletContext):为 Spring 设置 WebApplicationContext。
  • setConfigLocations(String[] configLocations):设置 Spring 配置文件地址,相对于 Web 根目录,如 /WEB-INF/lake-dao.xml,/WEB-INF/lake-service.xml 等。但用户也可使用带资源类型前缀的地址,如 classpath:com/example/context/beans.xml 等。

WebApplicationContext 初始化

WebApplicationContext 需要 ServletContext 实例,它必须在拥有 Web 容器的情况下才能完成启动工作,和通常的 Web 开发类似,在 web.xml 中配置自启动的 Servlet 或容器监听器(ServletContextListener),即可完成启动 Spring WebApplicationContext 的工作。
Spring 提供了用于启动 WebApplicationContext 的 Servlet 和 Web 容器监听器:

  • org.springframework.web.context.ContextLoaderServlet
  • org.springframework.web.context.ContextLoaderListener

两者都实现了启动 WebApplicationContext 实例的逻辑,只要根据 Web 容器的具体情况选择其一,并在 web.xml 中完成配置即可。

ContextLoaderListener 启动 WebApplicationContext:

1
2
3
4
5
6
7
8
9
<!-- 指定配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 设置Web容器监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在不支持容器监听器的低版本 Web 容器中,使用 ContextLoaderServlet:

1
2
3
4
5
6
7
8
9
10
11
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 声明自启动的Servlet -->
<servlet>
<servlet-name>springContextLoaderServlet</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<!-- 启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>

标准和自定义事件

ApplicationContext 通过 ApplicationEvent 和 ApplicationListener 接口提供事件处理,当某个 bean 实现了 ApplicationListener 接口时,当 ApplicationEvent 发布给 ApplicationContext 时,该 bean 会被告知。

标准事件

  • ContextRefreshedEvent:当 ApplicationContext 被初始化或刷新时发布事件。这里的初始化是指所有的 beans 被加载,后处理器 beans 被检测并激活,单例被预实例化,ApplicationContext 对象已准备好并可用。当上下文关闭时,refresh 可以被多次触发,这样的 ApplicationContext 支持热刷新。XmlWebApplicationContext 支持热刷新,而 GenericApplicationContext 不支持。
  • ContextStartedEvent:ApplicationContext 被启动时发布事件,使用 ConfigurableApplicationContext 接口的 start() 方法。这里的启动意味着 beans 接收一个显式的启动信号,该信号用于在显式的停止之后重新启动 beans,也可以用于启动没有被配置成自动启动的组件。
  • ContextStoppedEvent:ApplicationContext 被停止时发布事件,使用 ConfigurableApplicationContext 接口的 stop() 方法。
  • RequestHandledEvent:告知所有 beans 一个 HTTP 请求已被处理的特定的 web 事件,当请求完成时事件将会被发布,该事件只能通过 Spring 的 DispatcherServlet 应用于 Web 应用中。

自定义事件

自定义事件需要继承 Spring 的 ApplicationEvent 基类:

1
2
3
4
5
6
7
8
9
10
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}

调用 ApplicationEventPublisher 的 publishEvent() 方法发布自定义的 ApplicationEvent,该类需要实现 ApplicationEventPublisherAware 接口并作为 bean 来注册。在配置期间,Spring 容器会检测到实现了 ApplicationEventPublisherAware 接口的 EmailService 并自动的调用 setApplicationEventPublisher() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}

创建一个实现 ApplicationListener 接口的类,并作为 bean 来注册,以此接受自定义的 ApplicationEvent。

1
2
3
4
5
6
7
8
9
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}

下面的代码用于配置和注册上述的类:

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>

当 emailService 的 sendEmail() 方法被调用时,如果有任何邮件需要被加入黑名单,那么自定事件 BlackListEvent 会被发布。blackListNotifier bean 被注册作为 ApplicationListener 并接收 BlackListEvent。

基于注解的事件监听器

1
2
3
4
5
6
7
8
9
10
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}

异步监听器

1
2
3
4
5
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}

顺序监听器

1
2
3
4
5
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

参考资料:

Spring 3.x 企业应用开发实战
Spring Framework Reference Documentation

热评文章