1.12. 基于Java的容器配置

本节介绍如何在Java代码中使用注释来配置Spring容器。

1.12.1. 基本概念:@Bean和@Configuration

Spring新的Java配置支持中的中心构件是@Configuration-annotated 类和@Bean注释方法。
@Bean注释用于指示方法实例化、配置和初始化要由Spring IOC容器管理的新对象。对于熟悉Spring XML配置中<beans/>的用户,@Bean注释与<bean/>元素具有相同的作用。您可以将@Bean注释的方法与任何spring @Component一起使用。但是,它们最常用于@Configuration bean。
用@Configuration注释类表明它的主要用途是作为bean定义的源。此外,@Configuration 类允许通过调用同一类中的其他@Bean方法来定义bean间的依赖关系。最简单的@Configuration类如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

上面的AppConfig类等效于以下

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

完整@Configuration vs @Bean模式?
当@Bean方法在没有用@Configuration注释的类中声明时,它们被称为在“lite”模式。在@Component甚至是普通老类中声明的bean方法被认为是“lite”,包含类的主要目的不同,@Bean方法在这里是一种奖励。例如,服务组件可以通过在每个适用的组件类上附加的@Bean 方法向容器公开管理视图。在这种情况下,@Bean方法是一种通用的工厂方法机制。
与完整的@Configuration不同,lite类型的@Bean方法不能声明bean之间的依赖关系。相反,它们对其包含组件的内部状态进行操作,也可以选择对可能声明的参数进行操作。因此,这样的@Bean方法不应调用其他@Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的副作用是不需要在运行时应用CGLIB子类化,所以在类设计方面没有限制(也就是说,包含类可能是最终的等等)。
在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“full”模式,因此跨方法引用将被重定向到容器的生命周期管理。这防止了通过常规Java调用意外调用相同的bean方法,这有助于减少在“Lite”模式下操作时难以跟踪的细微错误。

下面的章节将深入讨论@Bean和@Configuration注释。但是,首先,我们介绍了使用基于Java的配置创建Spring容器的各种方法。

1.12.2. 使用AnnotationConfigApplicationContext实例化Spring容器

以下部分记录了Spring3.0中介绍的Spring的AnnotationConfigApplicationContext。这种通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,而且能够接受普通的@Component类和用JSR-330元数据注释的类。
当@Configuration 类作为输入提供时,@Configuration 类本身注册为bean定义,类中所有声明的@Bean方法也注册为bean定义。

当提供@Component和JSR-330类时,它们被注册为bean定义,并且假设在必要时在这些类中使用诸如@Autowired或@Inject之类的DI元数据。

简单结构
与在实例化ClassPathXmlApplicationContext时将Spring XML文件用作输入的方式大致相同,在实例化AnnotationConfigApplicationContext时,可以使用@Configuration类作为输入。这允许完全不使用XML的Spring容器,如下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

如前所述,AnnotationConfigApplicationContext不仅限于使用@Configuration类。任何@Component或JSR-330注释类都可以作为构造函数的输入提供,如下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

前面的示例假设MyServiceImpl, Dependency1和 Dependency2 使用Spring依赖项注入注释,如@Autowired。

使用register(Class<?>…​)以编程方式生成容器
可以使用无参构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法配置它。这种方法在以编程方式构建AnnotationConfigApplicationContext时特别有用。以下示例显示了如何执行此操作:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

使用scan(String…​)扫描组件
要启用组件扫描,可以按如下方式注释@Configuration类:

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    ...
}

此注释启用组件扫描。
在前面的示例中,将扫描com.acme包以查找任何@Component注释类,这些类在容器中注册为Spring Bean定义。AnnotationConfigApplicationContext公开了scan(String…​) 方法,以允许使用相同的组件扫描功能,如下示例所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

记住,@Configuration类是用@Component元注释的,因此它们是组件扫描的候选者。在前面的示例中,假设AppConfig是在com.acme包(或下面的任何包)中声明的,则会在调用scan()期间拾取它。在refresh()之后,它的所有@Bean方法都被处理并注册为容器中的bean定义。

支持带有AnnotationConfigWebApplicationContext的Web应用程序
AnnotationConfigApplicationContext的WebApplicationContext变体是AnnotationConfigWebApplicationContext。在配置Spring ContextLoaderListener servlet侦听器、Spring MVC DispatcherServlet等时,可以使用此实现。下面的web.xml片段配置了一个典型的Spring MVC Web应用程序(注意使用contextClass context-param 和init-param):

<web-app>
    <!-- 
配置ContextLoaderListener以使用AnnotationConfigWebApplicationContext而不是默认的XMLWebApplicationContext -->
    <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>com.acme.AppConfig</param-value>
    </context-param>

    <!-- 使用ContextLoaderListener像往常一样引导根应用程序上下文-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 像往常一样声明Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置DispatcherServlet以使用AnnotationConfigWebApplicationContext而不是默认的xmlWebApplicationContext-->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 同样,配置位置必须由一个或多个逗号或空格分隔和完全限定的@Configuration类组成。 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- 将/app/*的所有请求映射到Dispatcher servlet-->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>