Spring官方文档阅读(八)之IOC容器(基于Java config的容器配置)

11.使用JSR 330标准注释

从Spring 3.0开始,Spring提供对JSR-330标准注释(依赖注入)的支持。 这些注释的扫描方式与Spring注释的扫描方式相同。 要使用它们,您需要在类路径中有相关的jar。

注意:

  • 如果使用Maven,则标准Maven存储库(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)中提供了javax.inject工件。 您可以将以下依赖项添加到文件pom.xml中:

  • <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    

11.1使用@Inject和@Named进行依赖注入

可以使用@ javax.inject.Inject代替@Autowired,如下所示:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}

@Autowired一样,您可以在字段级别,方法级别和构造函数参数级别使用@Inject。 此外,您可以将注入点声明为Provider,从而允许按需访问范围较小的bean,或者通过Provider.get()调用来懒惰访问其他bean。 以下示例提供了先前示例的变体:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}

如果您想为应该注入的依赖项使用一个限定名,那么您应该使用@Named注解,如下面的示例所示:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

@Autowired一样,@ Inject也可以与java.util.Optional@Nullable一起使用。 这在这里更加适用,因为@Inject没有必需的属性。 以下一对示例显示了如何使用@Inject@Nullable

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}

11.2@Named和@ManagedBean:@Component注解的标准等效项

可以使用@ javax.inject.Namedjavax.annotation.ManagedBean代替@Component,如以下示例所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

在不指定组件名称的情况下使用@Component是非常常见的。 可以类似的方式使用@Named,如以下示例所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

当使用@Named@ManagedBean时,可以使用与使用Spring注解完全相同的方式来使用组件扫描,如以下示例所示:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

注意:

  • @Component相反,JSR-330 @Named和JSR-250 ManagedBean注解是不可组合的。 您应该使用Spring的原型模型来构建自定义组件注释。

11.3JSR-330标准注解的局限性

使用标准注解时,应该知道有些重要特性不可用,如下表所示:

Springjavax.inject.*javax.inject restrictions / comments
@Autowired@Inject@Inject没有“必需”属性。 可以与Java 8的Optional一起使用。
@Component@Named / @ManagedBeanJSR-330没有提供可组合模型,只是提供了一种识别已命名组件的方法。
@Scope(“singleton”)@SingletonJSR-330的默认范围类似于Spring的原型。 但是,为了使其与Spring的常规默认设置保持一致,默认情况下,在Spring容器中声明的JSR-330 bean是单例的。 为了使用单例以外的其他范围,您应该使用Spring的@Scope注解。 javax.inject还提供了@Scope注解。 不过,此仅用于创建自己的注解。
@Qualifier@Qualifier / @Namedjavax.inject.Qualifier只是用于构建自定义限定符的元注解。 可以通过javax.inject.Named关联具体的字符串限定符(例如Spring的@Qualifier带有值)。
@Value-没有等价的
@Required-没有等价的
@Lazy-没有等价的
ObjectFactoryProviderjavax.inject.Provider是Spring的ObjectFactory的直接替代方法,只不过具有较短的get()方法名称。 它也可以与Spring的@Autowired结合使用,也可以与无注解的构造函数和setter方法结合使用。

12.基于java的容器配置

本节介绍如何在Java代码中使用注解来配置Spring容器。 它包括以下主题:

  • 基本概念:@Bean和@Configuration
  • 使用AnnotationConfigApplicationContext实例化Spring容器
  • 使用@Bean注解
  • 使用@Configuration注解
  • 编写基于java的配置
  • Bean定义概要文件
  • PropertySource抽象
  • 使用@PropertySource
  • 语句中的占位符解析

12.1基本概念:@Bean和@Configuration

Spring的新Java配置支持中的主要工件是@Configuration注解的类和@Bean注解的方法。

@Bean注解用于指示方法实例化,配置和初始化要由Spring IoC容器管理的新对象。 对于那些熟悉Spring的 XML配置的人来说,@ Bean注释与元素具有相同的作用。 您可以将@Bean注解的方法与任何Spring @Component一起使用。 但是,它们最常与@Configuration bean一起使用。

用@Configuration注解类表示该类的主要目的是作为Bean定义的来源。 此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义Bean之间的依赖关系。 最简单的@Configuration类的内容如下:

@Configuration
public class AppConfig {

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

前面的AppConfig类等同于下面的Spring XML:

<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容器的各种方法。

12.2使用AnnotationConfigApplicationContext实例化Spring容器

以下各节介绍了Spring 3.0中引入的Spring的AnnotationConfigApplicationContext。 这种通用的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通的@Component类以及带有JSR-330元数据注释的类。

当提供@Configuration类作为输入时,@Configuration类本身将注册为Bean定义,并且该类中所有已声明的@Bean方法也将注册为Bean定义。

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

12.21Simple Construction

与实例化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();
}

前面的示例假定MyServiceImplDependency1Dependency2使用Spring依赖项注入注解,例如@Autowired

12.22通过使用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();
}
12.23启用扫描组件scan(String…)

要启用组件扫描,可以按如下方式注解@Configuration类:

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

注意:

  • 经验丰富的Spring用户可能熟悉Spring的context:名称空间中的XML声明,如以下示例所示:

  • <beans>
        <context:component-scan base-package="com.acme"/>
    </beans>
    

在前面的示例中,对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进行元注解,因此它们是组件扫描的候选对象。 在前面的示例中,假定AppConfigcom.acme包(或下面的任何包)中声明,则在调用scan()时将其拾取。 根据refresh(),其所有@Bean方法都将被处理并注册为容器内的Bean定义。
12.24使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigWebApplicationContext提供了AnnotationConfigApplicationContextWebApplicationContext变体。 在配置Spring ContextLoaderListener Servlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。 以下web.xml代码片段配置了典型的Spring MVC Web应用程序(请注意contextClass context-param和init-param的使用):

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

12.3使用@Bean注解

@Bean是方法级别的注释,是XML 元素的直接类似物。 注释支持提供的某些属性,例如:* init-method * destroy-method * autowiring * name.

您可以在@Configuration注解或@Component注解的类中使用@Bean注解。

12.31声明一个Bean

要声明一个bean,可以用@Bean注解来注解一个方法。 您可以使用此方法在类型指定为该方法的返回值的ApplicationContext中注册Bean定义。 默认情况下,Bean名称与方法名称相同。 以下示例显示了@Bean方法声明:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

前面的配置与后面的Spring XML完全相同:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都使名为transferService的bean在ApplicationContext中可用,绑定到类型为TransferServiceImpl的对象实例,如下面的文本图像所示:

transferService -> com.acme.TransferServiceImpl

您还可以使用接口(或基类)返回类型声明@Bean方法,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

但是,这将预先类型预测的可见性限制为指定的接口类型(TransferService)。然后,容器只知道完整类型(TransferServiceImpl)一次,受影响的单例bean就被实例化了。非惰性单例bean根据它们的声明顺序被实例化,因此您可能会看到不同的类型匹配结果,这取决于其他组件何时尝试通过一个未声明的类型进行匹配(例如@Autowired TransferServiceImpl,它只在transferService bean被实例化后才解析)。

注意:

  • 如果您通过声明的服务接口一致地引用类型,则@Bean返回类型可以安全地加入该设计决策。 但是,对于实现多个接口的组件或由其实现类型潜在引用的组件,声明可能的最具体的返回类型(至少与引用您的bean的注入点所要求的具体类型一样)更为安全。
12.32Bean的依赖关系

@bean注解的方法可以有任意数量的参数,用于描述构建该bean所需的依赖关系。例如,如果我们的TransferService需要一个AccountRepository,我们可以用一个方法参数来具体化这个依赖关系,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

解析机制与基于构造器的依赖项注入非常相似。

12.33接收生命周期回调

任何使用@Bean注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct@PreDestroy注解。

常规的Spring生命周期回调也得到了完全支持。如果一个bean实现了InitializingBeanDisposableBeanLifecycle,容器将调用它们各自的方法。

还完全支持标准的* Aware接口集(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。

@Bean注解支持指定任意的初始化和销毁回调方法,非常类似于bean元素上Spring XML的init-methoddestroy-method属性,如以下示例所示:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

注意:

  • 默认情况下,使用Java配置定义的具有公共closeshutdown方法的bean将与销毁回调一起被自动征用。如果您有一个公共closeshutdown方法,并且不希望在容器关闭时调用它,那么您可以将@Bean(destroyMethod="")添加到bean定义中,以禁用默认(推断)模式。

  • 默认情况下,您可能希望对使用JNDI获取的资源执行此操作,因为它的生命周期是在应用程序之外管理的。特别是,确保总是对数据源执行此操作,因为在Java EE应用程序服务器上这是有问题的。

  • 下面的示例显示如何防止对DataSource的自动销毁回调:

  • @Bean(destroyMethod="")
    public DataSource dataSource() throws NamingException {
        return (DataSource) jndiTemplate.lookup("MyDS");
    }
    
  • 另外,对于@Bean方法,通常使用程序化JNDI查找,方法是使用Spring的JndiTemplateJndiLocatorDelegate帮助器,或者直接使用JNDI InitialContext用法,而不使用JndiObjectFactoryBean变体(这将迫使您将返回类型声明为FactoryBean类型,而不是实际的类型。 目标类型,因此很难在打算引用此处提供的资源的其他@Bean方法中用于交叉引用调用。

对于上面示例中的BeanOne,在构造期间直接调用init()方法同样有效,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

注意:

  • 当您直接在Java中工作时,您可以对对象做任何您喜欢的事情,而不总是需要依赖容器的生命周期。
12.34指定Bean范围

Spring包含@Scope注解,以便您可以指定bean的范围。

使用@Scope注解

您可以指定使用@Bean注解定义的bean应该具有特定范围。 您可以使用Bean Scopes部分中指定的任何标准范围。

默认范围是singleton,但是您可以用@Scope注解覆盖它,如下面的示例所示:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

@Scope和scoped-proxy

Spring提供了一种通过作用域代理处理作用域依赖性的便捷方法。 使用XML配置时创建此类代理的最简单方法是<aop:scoped-proxy />元素。 使用@Scope注解在Java中配置bean,可以通过proxyMode属性提供同等的支持。 默认值为无代理(ScopedProxyMode.NO),但是您可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES

如果您使用Java将范围代理示例从XML参考文档(请参阅范围代理)移植到我们的@Bean,它类似于以下情况:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
12.35Customizing Bean Naming

默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。 但是,可以使用name属性覆盖此功能,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
12.36Bean Aliasing

正如在命名bean中所讨论的,有时希望给单个bean起多个名称,否则称为bean混叠。为此,@Bean注解的name属性接受一个字符串数组。下面的示例展示了如何为一个bean设置大量别名:

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
12.37Bean Description

有时,提供有关bean的更详细的文本描述会很有帮助。 当出于监视目的而暴露(可能通过JMX)bean时,这尤其有用。

要向@Bean添加描述,可以使用@Description注解,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

12.4使用@Configuration注解

@Configuration是类级别的注解,指示对象是Bean定义的源。 @Configuration类通过公共@Bean注解方法声明bean。 对@Configuration类的@Bean方法的调用也可以用于定义Bean之间的依赖关系。

12.41注入bean间的依赖关系

当bean相互依赖时,表示这种依赖关系就像让一个bean方法调用另一个一样简单,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中,beanOne通过构造函数注入接收到对beanTwo的引用。

注意:

  • 仅当在@Configuration类中声明@Bean方法时,此声明bean间依赖关系的方法才有效。 您不能使用简单的@Component类声明Bean间的依赖关系。
12.42查找方法注入

如前所述,查找方法注入是一项高级功能,您应该很少使用。 在单例作用域的bean依赖于原型作用域的bean的情况下,这很有用。 将Java用于这种类型的配置为实现此模式提供了自然的方法。 以下示例显示如何使用查找方法注入:

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

通过使用Java配置,可以创建CommandManager的子类,在该子类中,抽象的createCommand()方法将被覆盖,以便它查找新的(原型)命令对象。 以下示例显示了如何执行此操作:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
12.43有关基于Java的配置如何在内部工作的更多信息

考虑下面的示例,它显示了被调用两次的@Bean注释方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()clientService1()中被调用一次,并在clientService2()中被调用一次。 由于此方法会创建一个ClientDaoImpl的新实例并返回它,因此通常希望有两个实例(每个服务一个)。 那肯定是有问题的:在Spring中,实例化的bean默认情况下具有单例作用域。 这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB进行了子类化。 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的bean

注意:

  • 根据bean的范围,行为可能不同。我们在这里讨论的是单例。
  • 从Spring 3.2开始,不再需要将CGLIB添加到您的类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包并直接包含在spring-core JAR中。
  • 由于CGLIB在启动时动态添加特性,所以有一些限制。特别是,配置类不能是最终的。然而,在4.3中,配置类允许使用任何构造函数,包括使用@Autowired或为默认注入使用单一的非默认构造函数声明。
  • 如果您希望避免任何cglib强加的限制,可以考虑在非@configuration类上声明@Bean方法(例如,改为在纯@Component类上)。这样,@Bean方法之间的交叉方法调用就不会被拦截,因此您只能在构造函数或方法级别上独家依赖于依赖注入。

12.5编写基于java的配置

Spring的基于Java的配置功能使您可以撰写注解,从而降低配置的复杂性。

12.51使用@Import注解

与Spring XML文件中使用元素来帮助模块化配置一样,@Import注解允许从另一个配置类加载@Bean定义,如以下示例所示:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在,无需在实例化上下文时同时指定ConfigA.classConfigB.class,只需显式提供ConfigB,如以下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器的实例化,因为只需要处理一个类,而不是要求您在构造过程中记住潜在的大量@Configuration类。

注意:

  • 从Spring Framework 4.2开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。 如果要通过使用一些配置类作为入口点来显式定义所有组件,从而避免组件扫描,则此功能特别有用。
12.52在导入的@Bean定义上注入依赖项

前面的示例有效,但过于简单。 在大多数实际情况下,Bean在配置类之间相互依赖。 使用XML时,这不是问题,因为不涉及任何编译器,并且您可以声明ref =“ someBean”并信任Spring在容器初始化期间进行处理。 使用@Configuration类时,Java编译器会在配置模型上施加约束,因为对其他bean的引用必须是有效的Java语法。

幸运的是,解决这个问题很简单。 正如我们已经讨论的那样,@Bean方法可以具有任意数量的参数来描述Bean的依赖关系。 考虑以下具有多个@Configuration类的更真实的场景,每个类取决于在其他类中声明的bean:

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有另一种方法可以达到同样的效果。请记住,@Configuration类最终只是容器中的另一个bean:这意味着它们可以利用与任何其他bean相同的@Autowired@Value注入以及其他特性。

注意:

  • 确保以这种方式注入的依赖项只是最简单的一种。 @Configuration类在上下文的初始化过程中很早就被处理,并且强制以这种方式注入依赖项可能导致意外的早期初始化。 如上例所示,请尽可能使用基于参数的注入。
  • 另外,通过@Bean使用BeanPostProcessorBeanFactoryPostProcessor定义时要特别小心。 通常应将这些声明为静态@Bean方法,而不触发其包含的配置类的实例化。 否则,@Autowired@Value可能不适用于配置类本身,因为可以将其创建为比AutowiredAnnotationBeanPostProcessor早的bean实例。

以下示例说明如何将一个bean自动连接到另一个bean:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

注意:

  • 从Spring Framework 4.3开始,仅支持@Configuration类中的构造方法注入。 还要注意,如果目标bean仅定义一个构造函数,则无需指定@Autowired
12.53完全限定导入的bean以方便导航

在前面的场景中,使用@Autowired可以很好地工作并提供所需的模块化,但是确切地确定在何处声明自动装配的Bean定义仍然有些模棱两可。 例如,当开发人员查看ServiceConfig时,您如何确切知道@Autowired AccountRepository bean的声明位置? 它在代码中不是明确的,这可能很好。 请记住,Spring Tools for Eclipse提供了可以渲染图形的工具,这些图形显示了所有事物的连接方式,这也许就是您所需要的。 另外,您的Java IDE可以轻松找到AccountRepository类型的所有声明和使用,并快速向您显示返回该类型的@Bean方法的位置。

如果这种歧义是不可接受的,并且您希望从IDE内部直接从一个@Configuration类导航到另一个@Configuration类,请考虑自动装配配置类本身。 以下示例显示了如何执行此操作:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

在上述情况下,定义AccountRepository的位置是完全明确的。 但是,ServiceConfig现在与RepositoryConfig紧密耦合。 那是权衡。 通过使用基于接口或基于抽象类的@Configuration类,可以在某种程度上缓解这种紧密耦合。 考虑以下示例:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在,ServiceConfig与具体的DefaultRepositoryConfig松散耦合,并且内置的IDE工具仍然有用:您可以轻松地获得RepositoryConfig实现的类型层次结构。 以这种方式,导航@Configuration类及其依赖项与导航基于接口的代码的通常过程没有什么不同。

注意:

  • 如果要影响某些bean的启动创建顺序,请考虑将其中一些声明为@Lazy(用于首次访问而不是在启动时创建)或声明为@DependsOn某些其他bean(确保在创建其他特定bean之前) 当前的bean,而不是后者的直接依赖项所暗示的)。
12.54有条件地包含@Configuration类或@Bean方法

根据某些系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法通常很有用。 一个常见的示例是仅在Spring环境中启用了特定的配置文件时才使用@Profile注解来激活Bean

@Profile注解实际上是通过使用更灵活的称为@Conditional的注解来实现的。 @Conditional注解指示在注册@Bean之前应咨询的特定org.springframework.context.annotation.Condition实现。

Condition接口的实现提供了一个match(…)方法,该方法返回truefalse。 例如,以下清单显示了用于@Profile的实际Condition实现:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
12.55结合Java和XML配置

Spring的@Configuration类支持并非旨在100%完全替代Spring XML。 某些工具(例如Spring XML名称空间)仍然是配置容器的理想方法。 在使用XML方便或有必要的情况下,您可以选择:使用“ ClassPathXmlApplicationContext”以“ XML中心”方式实例化容器,或使用AnnotationConfigApplicationContext和“ AnnotationConfigApplicationContext”以“ Java中心方式”实例化容器。 @ImportResource注解可根据需要导入XML。

使用以xml为中心的@Configuration类

最好是从XML引导Spring容器,并以特别的方式包含@Configuration类。例如,在使用Spring XML的大型现有代码库中,更容易根据需要创建@Configuration类并从现有XML文件中包含它们。在本节的后面,我们将介绍在这种以xml为中心的情况下使用@Configuration类的选项。

@Configuration类声明为纯Spring 元素

请记住,@Configuration类最终是容器中的bean定义。 在本系列示例中,我们创建一个名为AppConfig@Configuration类,并将其作为定义包含在system-test-config.xml中。 因为<context:annotation-config />已打开,所以容器可以识别@Configuration注解并正确处理AppConfig中声明的@Bean方法。

下面的示例展示了Java中的一个普通配置类:

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

以下示例显示了样本system-test-config.xml文件的一部分:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

以下示例显示了可能的jdbc.properties文件:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

注意:

  • system-test-config.xml文件中,AppConfig 没有声明id元素。 尽管这样做是可以接受的,但是由于没有其他bean曾经引用过它,因此这是不必要的,并且不太可能通过名称从容器中显式获取。 同样,DataSource bean只能按类型自动装配,因此也不严格要求显式bean id。
12.56使用<context:component-scan />拾取@Configuration类

由于@Configuration使用@Component进行元注解,因此@Configuration注解的类自动成为组件扫描的候选对象。 使用前面示例中描述的相同方案,我们可以重新定义system-test-config.xml以利用组件扫描的优势。 请注意,在这种情况下,我们无需显式声明<context:annotation-config />,因为<context:component-scan />可启用相同的功能。

以下示例显示了修改后的system-test-config.xml文件:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
12.57@Configuration**Class-centric Use of XML with**@ImportResource

@Configuration类是配置容器的主要机制的应用程序中,仍然有必要至少使用一些XML。 在这些情况下,您可以使用@ImportResource并仅定义所需的XML。 这样做实现了“以Java为中心”的方法来配置容器,并使XML保持在最低限度。 以下示例(包括配置类,定义Bean的XML文件,属性文件和主类)显示了如何使用@ImportResource注解来实现按需使用XML的以Java为中心的配置:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值