2.12 Spring Framework 5.x 之基于Java Config的容器配置

1.12. Java-based Container Configuration

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

  • 基本概念:@Bean和@Configuration
  • 使用实例化Spring容器 AnnotationConfigApplicationContext
  • 使用@Bean注解
  • 使用@Configuration注解
  • 编写基于Java的配置
  • Bean定义配置文件
  • PropertySource 抽象化
  • 运用 @PropertySource
  • 占位符决议在声明中

1.12.1 基本概念:@Bean和@Configuration

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

该@Bean注解被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于那些熟悉Spring的XML配置的人来说,@Bean注解与元素扮演的角色相同。你可以@Bean在任何Spring中使用-annotated方法 @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注解类中声明方法时 ,它们被称为以“精简”模式处理。@Component在一个普通旧类中或甚至在普通旧类中声明的Bean方法被认为是“精简”,具有不同的包含类的主要目的,并且@Bean方法在那里是一种奖励。例如,服务组件可以通过@Bean每个适用组件类的附加方法将管理视图公开给容器。在这种情况下,@Bean方法是通用的工厂方法机制。

与full不同@Configuration,精简 @Bean方法不能声明bean间依赖关系。相反,它们对其包含组件的内部状态进行操作,并且可选地,对它们可能声明的参数进行操作。@Bean因此,这种方法不应该引用其他 @Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是不必在运行时应用CGLIB子类,因此在类设计方面没有限制(也就是说,包含类可能是final等等)。

在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止@Bean通过常规Java调用意外地调用相同的方法,这有助于减少在“精简”模式下操作时难以跟踪的细微错误。

@Bean和@Configuration注解的深度在以下章节中讨论。然而,首先,我们将介绍使用基于Java的配置创建Spring容器的各种方法

1.12.2. 通过使用 AnnotationConfigApplicationContext实例化Spring容器

以下部分AnnotationConfigApplicationContext介绍了在Spring 3.0中引入的Spring。这种多功能ApplicationContext实现不仅能够接受@Configuration类作为输入,还能接受@Component使用JSR-330元数据注解的普通类和类。

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

当@Component被提供和JSR-330类,它们被登记为bean定义,并且假定DI元数据,例如@Autowired或者@Inject是这些类中使用的必要。

简单的使用

与在实例化classpathxmlApplicationContext时将SpringXML文件用作输入的方式大致相同。在实例化AnnotationConfigApplicationContext时,可以将@Configuration类作为输入,这样可以完全自由使用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使用no-arg构造函数实例化,然后使用该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  {
    ...
}

此注解可启用组件扫描。

有经验的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,因此它们是组件扫描的候选者。在前面的示例中,假设AppConfig在com.acme包(或下面的任何包)中声明了它,它在调用期间被拾取scan()。之后refresh(),它的所有@Bean 方法都被处理并在容器中注册为bean定义。

支持Web应用程序 AnnotationConfigWebApplicationContext

可用的WebApplicationContext变体。在配置Spring servlet侦听器,Spring MVC等时 ,可以使用此实现。以下代码段配置典型的Spring MVC Web应用程序(请注意context-param和init-param的使用):AnnotationConfigApplicationContextAnnotationConfigWebApplicationContextContextLoaderListenerDispatcherServletweb.xmlcontextClass

<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>

1.12.3 使用@Bean注解

@Bean是方法级注释和XML 元素的直接模拟。注释支持一些提供的属性,例如:* init-method * destroy-method * autowiring * name。

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

声明一个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>

这两个声明都将一个名为transferServiceavailable 的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的注入点所需的具体相同)。

Bean依赖项

带@Bean注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们TransferService 需要a AccountRepository,我们可以使用方法参数来实现该依赖关系,如下例所示:

@Configuration
public class AppConfig {

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

接收生命周期回调

使用@Bean注释定义的任何类都支持常规生命周期回调,并且可以使用JSR-250中的注释@PostConstruct和@PreDestroy注释。有关更多详细信息,请参阅 JSR-250注释。

完全支持常规的Spring 生命周期回调。如果bean实现InitializingBean,DisposableBean或者Lifecycle它们各自的方法被容器调用。

还完全支持标准*Aware接口集(例如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等)。

该@Bean注释支持指定任意初始化和销毁回调方法,就像春天XML的init-method,并destroy-method在属性上的bean元素,如下例所示:

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配置定义的具有public close或shutdown method的bean 会自动使用销毁回调登记。如果您有公共 close或shutdown方法,并且您不希望在容器关闭时调用它,则可以添加@Bean(destroyMethod="")到bean定义以禁用默认(inferred)模式。

对于使用JNDI获取的资源,您可能希望默认执行此操作,因为其生命周期在应用程序之外进行管理。特别是,确保始终为a执行此操作DataSource,因为已知它在Java EE应用程序服务器上存在问题。

以下示例显示如何防止自动销毁回调 DataSource:

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,使用@Bean方法,您通常使用编程JNDI查找,通过使用Spring JndiTemplate或JndiLocatorDelegate帮助程序或直接JNDI InitialContext用法但不使用JndiObjectFactoryBean变量(这将强制您将返回类型声明为FactoryBean类型而不是实际目标类型,使得更难以用于其他@Bean打算在此处引用所提供资源的方法中的交叉引用调用。

在BeanOne前面注释的上述示例中,init() 在构造期间直接调用该方法同样有效,如以下示例所示:

@Configuration
public class AppConfig {

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

    // ...
}

当您直接使用Java工作时,您可以使用对象执行任何您喜欢的操作,并且不必总是依赖于容器生命周期。

指定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属性的等效支持。默认值为no proxy(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果将scoped代理示例从XML参考文档(请参阅范围代理)移植 到@Bean使用Java,它类似于以下内容:

// 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;
}

自定义Bean命名

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

@Configuration
public class AppConfig {

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

Bean 别名

正如Naming Beans中所讨论的,有时需要为单个bean提供多个名称,也称为bean别名。 为此目的name,@Bean注释的属性接受String数组。以下示例显示如何为bean设置多个别名:

@Configuration
public class AppConfig {

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

Bean描述

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

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

@Configuration
public class AppConfig {

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

1.12.4 使用@Configuration注解

@Configuration是一个类级别的注解,指示对象是bean定义的来源。@Configurationclasses通过公共@Bean注解方法声明bean 。@Bean对@Configuration类上的方法的调用也可用于定义bean间依赖项。请参阅基本概念:@Bean并@Configuration进行一般性介绍。
注入bean间依赖关系
当bean彼此依赖时,表达该依赖关系就像让一个bean方法调用另一个bean一样简单,如下例所示:

@Configuration
public class AppConfig {

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

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

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

这种声明bean间依赖关系的@Bean方法只有在@Configuration类中声明方法时才有效。您不能使用普通@Component类声明bean间依赖项。

查找方法注入

如前所述,查找方法注入是一项很少使用的高级功能。在单例范围的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();
        }
    }
}

有关基于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 singleton默认具有范围。这就是魔术的用武之地:所有@Configuration类在启动时都是子类化的CGLIB。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域)bean。

根据bean的范围,行为可能会有所不同。我们在这里谈论singletons 单例模式

从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经重新打包org.springframework.cglib并直接包含在spring-core JAR中。

由于CGLIB在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从4.3开始,配置类上允许使用任何构造函数,包括使用 @Autowired默认注入的单个非默认构造函数声明。

如果您希望避免任何CGLIB强加的限制,请考虑@Bean 在非@Configuration类上声明您的方法(例如,在普通@Component类上)。@Bean然后不会拦截方法之间的跨方法调用,因此您必须在构造函数或方法级别专门依赖依赖项注入。

1.12.5 编写基于Java的配置

Spring的基于Java的配置功能允许您撰写注释,这可以降低配置的复杂性。

使用@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.class以及ConfigB.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 Framework4.2开始,@Import还支持引用常规组件类,类似于AnnotationConfigApplicationContext.register方法。如果要避免组件扫描,这一点特别有用,可以使用一些配置类作为明确定义所有组件的入口点。

注入对导入@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:这意味着它们可以利用 @Autowired和@Value注入以及与任何其他bean相同的其他功能。

确保以这种方式注入的依赖项只是最简单的。@Configuration 在上下文初始化期间很早就处理了类,并且强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能采用基于参数的注入,如前面的示例所示。

另外,要特别注意BeanPostProcessor和BeanFactoryPostProcessor定义@Bean。这些通常应该声明为static @Bean方法,而不是触发其包含配置类的实例化。否则,@Autowired并且@Value不能在配置类本身上工作,因为它过早地被创建为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;

    @Autowired
    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");
}

@Configuration仅在Spring Framework 4.3中支持类中的 构造函数注入。另请注意,无需指定@Autowired目标bean是否仅定义了一个构造函数。在前面的示例中,构造函数@Autowired上没有必要RepositoryConfig。

完全符合条件的改进的Bean,便于导航

在前面的场景中,使用@Autowired效果很好并提供了所需的模块性,但确定声明自动装配的bean定义的确切位置仍然有些模棱两可。例如,作为开发人员ServiceConfig,您如何确切地知道@Autowired AccountRepositorybean的声明位置?它在代码中并不明确,这可能就好了。请记住, Spring Tool Suite提供的工具可以呈现图表,显示所有内容的连线方式,这可能就是您所需要的。此外,您的Java IDE可以轻松找到该AccountRepository类型的所有声明和用法,并快速显示@Bean返回该类型的方法的位置。

如果这种歧义是不可接受的,并且您希望从IDE中从一个@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,超出后者的直接依赖意味着什么)。

有条件地包括@Configuration类或@Bean方法

基于某些任意系统状态,有条件地启用或禁用完整@Configuration类或甚至单个@Bean方法通常很有用。一个常见的例子是@Profile只有在Spring中启用了特定的配置文件时才使用注释来激活bean Environment( 有关详细信息,请参阅Bean定义配置文件)。

该@Profile注释是通过使用一种称为更灵活的注释实际执行@Conditional。该@Conditional注释指示特定 org.springframework.context.annotation.Condition前应谘询的实施@Bean是注册。

Condition接口的实现提供了一个matches(…) 返回true或的方法false。例如,以下列表显示了Condition用于的实际 实现@Profile:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // 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;
}

有关@Conditional 更多详细信息,请参阅javadoc。

结合Java和XML配置

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

以XML为中心的@Configuration类的使用
最好从XML引导Spring容器并@Configuration以ad-hoc方式包含 类。例如,在使用Spring XML的大型现有代码库中,可以@Configuration根据需要更轻松地创建类,并将其包含在现有XML文件中。在本节的后面部分,我们将介绍@Configuration在这种“以XML为中心”的情况下使用类的选项。

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

以下示例显示了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引用它,并且不太可能通过名称从容器中明确地获取它。类似地,DataSourcebean只是按类型自动装配,因此id 不严格要求显式bean 。

使用<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>

@Configuration 以类为中心的XML使用 @ImportResource
在@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,您可以@ImportResource根据需要使用和定义尽可能多的XML。这样做可以实现“以Java为中心”的方法来配置容器并将XML保持在最低限度。以下示例(包括配置类,定义bean的XML文件,属性文件和main类)显示了如何使用@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);
    // ...
}

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86476845