1. 概述

Spring框架的核心简单来说就是一个用于管理bean的依赖注入容器。Spring中有两种基本类型的容器:Bean Factory和ApplicationContext。前者提供了基础功能,这部分内容在这里介绍;后者是前者的扩展版本,应用最为广泛。

ApplicationContextorg.springframework.context包下的一个接口,它有多个实现类,其中ClassPathXmlApplicationContext就是其中之一。

本文将重点介绍ClassPathXmlApplicationContext提供的实用功能。

2. 基本使用

2.1. 初始化容器与管理bean

ClassPathXmlApplicationContext可以从类路径加载XML配置,并管理其中的bean:

我们有一个Student类:

public class Student {
    private int no;
    private String name;

    // standard constructors, getters and setters
}

我们在classpathxmlapplicationcontext-example.xml中配置了一个Student bean,并将其添加到类路径中:

<beans ...>
    <bean id="student" class="com.baeldung.applicationcontext.Student">
        <property name="no" value="15"/>
        <property name="name" value="Tom"/>
    </bean>
</beans>

现在我们可以使用ClassPathXmlApplicationContext加载XML配置并获取Student bean:

@Test
public void testBasicUsage() {
    ApplicationContext context 
      = new ClassPathXmlApplicationContext(
        "classpathxmlapplicationcontext-example.xml");
    
    Student student = (Student) context.getBean("student");
    assertThat(student.getNo(), equalTo(15));
    assertThat(student.getName(), equalTo("Tom"));

    Student sameStudent = context.getBean("student", Student.class);
    assertThat(sameStudent.getNo(), equalTo(15));
    assertThat(sameStudent.getName(), equalTo("Tom"));
}

2.2. 多个XML配置

有时我们需要使用多个XML配置来初始化Spring容器。在这种情况下,只需在构造ApplicationContext时添加多个配置位置:

ApplicationContext context 
  = new ClassPathXmlApplicationContext("ctx.xml", "ctx2.xml");

3. 其他能力

3.1. 温和关闭Spring IoC容器

在Web应用中使用Spring IoC容器时,Spring的基于Web的ApplicationContext实现会在应用关闭时温和地关闭容器。但在非Web环境,如独立桌面应用中,我们需要自行注册JVM的退出钩(shutdown hook)以确保Spring IoC容器能温和关闭并调用destroy方法释放资源。

让我们在Student类中添加一个destroy()方法:

public void destroy() {
    System.out.println("Student(no: " + no + ") is destroyed");
}

现在我们可以将这个方法配置为student bean的destroy方法:

<beans ...>
    <bean id="student" class="com.baeldung.applicationcontext.Student" 
      destroy-method="destroy">
        <property name="no" value="15"/>
        <property name="name" value="Tom"/>
    </bean>
</beans>

接下来,我们将注册一个退出钩:

@Test
public void testRegisterShutdownHook() {
    ConfigurableApplicationContext context 
      = new ClassPathXmlApplicationContext(
        "classpathxmlapplicationcontext-example.xml");
    context.registerShutdownHook();
}

当我们运行测试方法时,可以看到destroy()方法被调用了。

3.2. 国际化与MessageSource

ApplicationContext接口继承自MessageSource接口,因此提供了国际化功能。ApplicationContext容器在初始化时会自动搜索名为messageSourceMessageSource bean。

以下是使用MessageSource实现不同语言的例子:

首先,在类路径下添加一个dialog目录,并在其中添加两个文件:dialog_en.propertiesdialog_zh_CN.properties

dialog_en.properties

hello=hello
you=you
thanks=thank {0}

dialog_zh_CN.properties

hello=\u4f60\u597d
you=\u4f60
thanks=\u8c22\u8c22{0}

classpathxmlapplicationcontext-internationalization.xml中配置messageSource bean:

<beans ...>
    <bean id="messageSource"
      class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>dialog/dialog</value>
            </list>
        </property>
    </bean>
</beans>

然后,我们可以使用MessageSource获取不同语言的对话字词:

@Test
public void testInternationalization() {
    MessageSource resources 
      = new ClassPathXmlApplicationContext(
        "classpathxmlapplicationcontext-internationalization.xml");

    String enHello = resources.getMessage(
      "hello", null, "Default", Locale.ENGLISH);
    String enYou = resources.getMessage(
      "you", null, Locale.ENGLISH);
    String enThanks = resources.getMessage(
      "thanks", new Object[] { enYou }, Locale.ENGLISH);
 
    assertThat(enHello, equalTo("hello"));
    assertThat(enThanks, equalTo("thank you"));

    String chHello = resources.getMessage(
      "hello", null, "Default", Locale.SIMPLIFIED_CHINESE);
    String chYou = resources.getMessage(
      "you", null, Locale.SIMPLIFIED_CHINESE);
    String chThanks = resources.getMessage(
      "thanks", new Object[] { chYou }, Locale.SIMPLIFIED_CHINESE);
 
    assertThat(chHello, equalTo("你好"));
    assertThat(chThanks, equalTo("谢谢你"));
}

4. ApplicationContext引用

有时,我们需要在由其管理的bean中获取ApplicationContext的引用,可以使用ApplicationContextAware@Autowired来实现。让我们看看如何使用ApplicationContextAware

我们有一个带有名称的Course类:

public class Course {

    private String name;

    // standard constructors, getters and setters
}

有一个Teacher类,根据容器中的bean组装课程:

public class Teacher implements ApplicationContextAware {
    
    private ApplicationContext context;
    private List<Course> courses = new ArrayList<>();
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @PostConstruct
    public void addCourse() {
        if (context.containsBean("math")) {
            Course math = context.getBean("math", Course.class);
            courses.add(math);
        }
        if (context.containsBean("physics")) {
            Course physics = context.getBean("physics", Course.class);
            courses.add(physics);
        }
    }

    // standard constructors, getters and setters
}

我们在classpathxmlapplicationcontext-example.xml中配置了course bean和teacher bean:

<beans ...>
    <bean id="math" class="com.baeldung.applicationcontext.Course">
        <property name="name" value="math"/>
    </bean>

    <bean name="teacher" class="com.baeldung.applicationcontext.Teacher"/>
</beans>

然后测试courses属性的注入:

@Test
public void testApplicationContextAware() {
    ApplicationContext context 
       = new ClassPathXmlApplicationContext(
         "classpathxmlapplicationcontext-example.xml");
    Teacher teacher = context.getBean("teacher", Teacher.class);
    List<Course> courses = teacher.getCourses();
 
    assertThat(courses.size(), equalTo(1));
    assertThat(courses.get(0).getName(), equalTo("math"));
}

除了实现ApplicationContextAware接口,使用@Autowired注解也有相同效果。

让我们将Teacher类改为如下:

public class Teacher {

    @Autowired
    private ApplicationContext context;
    private List<Course> courses = new ArrayList<>();

    @PostConstruct
    public void addCourse() {
        if (context.containsBean("math")) {
            Course math = context.getBean("math", Course.class);
            courses.add(math);
        }
        if (context.containsBean("physics")) {
            Course physics = context.getBean("physics", Course.class);
            courses.add(physics);
        }
    }

    // standard constructors, getters and setters
}

运行测试,可以看到结果相同。

5. 总结

相比于BeanFactoryApplicationContext具有更多面向企业级的功能,而ClassPathXmlApplicationContext是其最常用的实现之一。

本文介绍了ClassPathXmlApplicationContext的一些方面,包括基本使用、退出钩注册、国际化功能以及获取其引用的方法。

如往常一样,示例的完整源代码可以在GitHub上找到。