10.spring framework 5.2.3.RELEASE 文档之核心技术(Core Technologies) 第九篇 基于java的容器配置

 

目录

 

1.12. 基于java的容器配置

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

1.12.2. 使用 AnnotationConfigApplicationContext 实例化Spring容器

 

1.12.3. 使用@Bean注解

声明一个Bean

Bean依赖

接收生命周期回调

指定Bean作用域

使用@Scope注解

自定义Bean命名

Bean别名

Bean 描述

1.12.4. 使用@Configuration注解

注入bean间的依赖关系

查找方法注入

关于基于Java的配置如何在内部工作的更多信息

1.12.5. 组装基于java的配置

使用@Import 注解

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

Combining Java and XML Configuration


1.12. 基于java的容器配置

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

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

Spring新的Java配置支持中的核心构件是@configuration注解类和@Bean注解方法。

@Bean注解用于表示此方法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于熟悉Spring<Beans/>XML配置的人来说,@Bean注解与<Bean/>元素相同功能。您可以将@Bean注解方法与任何Spring的@Component一起使用。但是,它们通常与@Configuration bean一起使用。

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun myService(): MyService {
        return MyServiceImpl()
    }
}

前面的 AppConfig  类与下面的Spring 的 <beans/> XML配置相等:

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

完整的@Configuration与“lite” @Bean模式?

 

如果在未使用@Configuration注解的类中声明@Bean方法,则将它们称为以“lite”模式进行处理。 在@Component或甚至在简单的旧类中声明的Bean方法被认为是“lite”,其中包含类具有不同的主要用途,而@Bean方法在那里具有一定的优势。 例如,服务组件可以通过每个适用组件类上的其他@Bean方法将管理视图公开给容器。 在这种情况下,@ Bean方法是一种通用的工厂方法机制。

 

与完整的@Configuration不同,lite @Bean方法无法声明Bean之间的依赖关系。 取而代之的是,它们在其包含组件的内部状态上进行操作,并且还可以根据可能声明的自变量进行操作。 因此,此类@Bean方法不应调用其他@Bean方法。 每个此类方法实际上只是针对特定bean引用的工厂方法,而没有任何特殊的运行时语义。 这里的积极副作用是,不必在运行时应用CGLIB子类,因此在类设计方面没有任何限制(即,包含类可以是最终类,依此类推)。

 

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

 

 

以下各节将详细讨论@Bean和@Configuration注解。 但是,首先,我们介绍了通过基于Java的配置使用创建spring容器的各种方法。

 

1.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元数据。

结构简单

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

java

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

kotlin

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

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

java

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

kotlin

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

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

通过使用register(Class<?>…​)编程方式创建容器

您可以使用无参数构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法对其进行配置。 以编程方式构建AnnotationConfigApplicationContext时,此方法特别有用。 以下示例显示了如何执行此操作:

java

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();
}

kotlin

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.register(AppConfig::class.java, OtherConfig::class.java)
    ctx.register(AdditionalConfig::class.java)
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

使用scan(String…​)启用组件扫描

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

java

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

kotlin

@Configuration
@ComponentScan(basePackages = ["com.acme"]) 
class AppConfig  {
    // ...
}

有经验的Spring用户可能熟悉来自Spring的context:namespace的XML声明类似,如下例所示:

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

 

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

java

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

kotlin

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.scan("com.acme")
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
}

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

使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigApplicationApplicationContext的WebApplicationContext变体可用于AnnotationConfigWebApplicationContext。 在配置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>

 

1.12.3. 使用@Bean注解

@Bean是方法级注解,是XML <bean />元素的直接类似物。 注释支持<bean />提供的某些属性,例如: * init-method * destroy-method * autowiring *名称。

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

声明一个Bean

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun transferService() = TransferServiceImpl()
}

前面的配置与下面的Spring XML完全等效:

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

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

transferService -> com.acme.TransferServiceImpl

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}

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

如果通过声明的服务接口一致地引用类型,@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,更安全的做法是声明可能的最特定的返回类型(至少根据引用bean的注入点的要求指定)。

Bean依赖

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

解析机制与基于构造函数的依赖注入几乎相同。 有关更多详细信息,请参见相关部分

接收生命周期回调

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

还完全支持常规的Spring生命周期回调。如果bean实现了initializengbean、DisposableBean或Lifecycle,则容器将调用它们各自的方法。

标准的*aware接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等)也完全受支持。

@Bean注释支持指定任意的初始化和销毁回调方法,就像Spring XML的init方法和Bean元素上的destroy方法属性一样,如下例所示:

java

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();
    }
}

kotlin

class BeanOne {

    fun init() {
        // initialization logic
    }
}

class BeanTwo {

    fun cleanup() {
        // destruction logic
    }
}

@Configuration
class AppConfig {

    @Bean(initMethod = "init")
    fun beanOne() = BeanOne()

    @Bean(destroyMethod = "cleanup")
    fun beanTwo() = BeanTwo()
}

默认情况下,使用Java配置定义的具有公共close或shutdown方法的bean将自动通过销毁回调登记。如果您有一个公共的close或shutdown方法,并且您不希望在容器关闭时调用它,那么您可以将@Bean(destroyMethod=)添加到Bean定义中,以禁用默认(inferred)模式。

 

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

 

以下示例演示如何阻止数据源的自动销毁回调:

java

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

kotlin

@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    return jndiTemplate.lookup("MyDS") as DataSource
}

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

在前面说明的示例中的BeanOne的情况下,在构造期间直接调用init()方法同样有效,如下例所示:

java

@Configuration
public class AppConfig {

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

    // ...
}

kotlin

@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne().apply {
        init()
    }

    // ...
}
当您直接在Java中工作时,您可以对对象执行任何您喜欢的操作,而不必总是依赖于容器生命周期。

指定Bean作用域

Spring包含@Scope注解,以便您可以指定bean的作用域。

使用@Scope注解

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

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

java

@Configuration
public class MyConfiguration {

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

kotlin

@Configuration
class MyConfiguration {

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

@Scope 和 scoped-proxy

Spring提供了一种通过作用域代理来处理作用域依赖关系的便捷方式。使用XML配置时创建这样一个代理的最简单方法是<aop:scoped proxy/>元素。用@Scope注解在Java中配置bean提供了对proxyMode属性的等效支持。默认值是no proxy(ScopedProxyMode.no),但可以指定ScopedProxyMode.TARGET_类或ScopedProxyMode.INTERFACES。

如果您使用Java从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;
}

kotlin

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()

@Bean
fun userService(): Service {
    return SimpleUserService().apply {
        // a reference to the proxied userPreferences bean
        setUserPreferences(userPreferences()
    }
}

自定义Bean命名

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean("myThing")
    fun thing() = Thing()
}

Bean别名

正如在命名bean中所讨论的,有时需要为单个bean指定多个名称,或者称为bean别名。@Bean注解的name属性为此接受一个字符串数组。下面的示例演示如何为bean设置多个别名:

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

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

Bean 描述

有时,提供bean的更详细的文本描述是有帮助的。当bean公开(可能通过JMX)用于监视目的时,这尤其有用。

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

java

@Configuration
public class AppConfig {

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

kotlin

@Configuration
class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    fun thing() = Thing()
}

 

1.12.4. 使用@Configuration注解

@Configuration是类级别的注释,指示对象是Bean定义的源。 @Configuration类通过公共@Bean注释方法声明bean。 对@Configuration类的@Bean方法的调用也可以用于定义Bean之间的依赖关系。 有关一般性介绍,请参见基本概念:@Bean和@Configuration。

注入bean间的依赖关系

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

java

@Configuration
public class AppConfig {

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

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne(beanTwo())

    @Bean
    fun beanTwo() = BeanTwo()
}

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

只有在@Configuration类中声明@bean方法时,这种声明bean间依赖关系的方法才起作用。不能使用纯@Component类声明bean之间的依赖关系。

查找方法注入

如前所述,查找方法注入是一个高级特性,您应该很少使用它。在单例作用域bean依赖于原型作用域bean的情况下,它非常有用。使用Java进行这种类型的配置为实现这种模式提供了一种自然的方法。下面的示例演示如何使用查找方法注入:

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();
}

kotlin

abstract class CommandManager {
    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val 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 fun createCommand(): Command
}

通过使用Java配置,您可以创建CommandManager的子类,其中抽象的createCommand()方法被重写,从而查找新的(原型)命令对象。下面的示例演示了如何执行此操作:

java

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

kotlin

@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
    val command = AsyncCommand()
    // inject dependencies here as required
    return command
}

@Bean
fun commandManager(): CommandManager {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return object : CommandManager() {
        override fun createCommand(): Command {
            return asyncCommand()
        }
    }
}

关于基于Java的配置如何在内部工作的更多信息

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

java

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

kotlin

@Configuration
class AppConfig {

    @Bean
    fun clientService1(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientService2(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientDao(): ClientDao {
        return 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类(例如,在普通的@Component类上)声明@Bean方法。 然后,不会拦截@Bean方法之间的跨方法调用,因此您必须在那里的构造函数或方法级别仅依赖依赖项注入。

 

1.12.5. 组装基于java的配置

Spring基于java的配置特性可以让您编写注释,从而减少配置的复杂性

使用@Import 注解

正如<import/>元素在Spring XML文件中用于帮助模块化配置一样,@import注释允许从另一个配置类加载@Bean定义,如下例所示:

java

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

kotlin

@Configuration
class ConfigA {

    @Bean
    fun a() = A()
}

@Configuration
@Import(ConfigA::class)
class ConfigB {

    @Bean
    fun b() = B()
}

现在,在实例化上下文时不需要同时指定ConfigA.class和ConfigB.class,只需要显式地提供ConfigB,如下例所示:

java

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

kotlin

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)

    // now both beans A and B will be available...
    val a = ctx.getBean<A>()
    val b = ctx.getBean<B>()
}

这种方法简化了容器实例化,因为只需要处理一个类,而不需要在构造期间记住大量的@Configuration类。

从Spring Framework 4.2开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果您想避免组件扫描,这将特别有用,方法是使用几个配置类作为入口点显式定义所有组件。

对导入的@Bean定义注入依赖项

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

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

java

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

kotlin

import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig {

    @Bean
    fun accountRepository(dataSource: DataSource): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}


fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

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

确保以这种方式注入的依赖项是最简单的。@Configuration类在上下文初始化过程中很早就被处理,强制以这种方式注入依赖项可能会导致意外的早期初始化。如果可能,请使用基于参数的注入,如前一个示例所示。

 

另外,通过@Bean使用BeanPostProcessor和BeanFactoryPostProcessor定义时要特别小心。这些通常应该声明为static@Bean方法,而不是触发其包含的配置类的实例化。否则,@Autowired和@Value对配置类本身不起作用,因为它作为bean实例创建得太早了。

下面的示例演示如何将一个bean自动连接到另一个bean:

java

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

kotlin

import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    lateinit var accountRepository: AccountRepository

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig(private val dataSource: DataSource) {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
从SpringFramework4.3开始,@Configuration类中只支持构造函数注入。还要注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired。

完全限定导入的bean以便于导航

在前面的场景中,使用@Autowired可以很好地工作并提供所需的模块性,但是确定自动装配bean定义的确切声明位置仍然有点模棱两可。例如,作为一个查看ServiceConfig的开发人员,您如何确切地知道@Autowired AccountRepository bean声明在哪里?它在代码中并不明确,这可能很好。请记住,Spring工具套件提供了一些工具,这些工具可以呈现显示所有东西是如何连接的图形,这可能是您所需要的全部。另外,Java IDE可以很容易地找到AccountRepository类型的所有声明和使用,并快速地显示返回该类型的@Bean方法的位置。

如果这种模糊性是不可接受的,并且您希望在IDE中从一个@Configuration类直接导航到另一个@Configuration类,请考虑自动连接配置类本身。下面的示例演示了如何执行此操作:

java

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

kotlin

@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

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

在前面的情况中,定义AccountRepository是完全显式的。但是,ServiceConfig现在与RepositoryConfig紧密耦合。这就是折衷。这种紧密的耦合可以通过使用基于接口或基于抽象类的@Configuration类来减轻。请考虑以下示例:

java

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

kotlin

import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

@Configuration
interface RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository
}

@Configuration
class DefaultRepositoryConfig : RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(...)
    }
}

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

    @Bean
    fun dataSource(): DataSource {
        // return DataSource
    }

}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

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

如果要影响某些bean的启动创建顺序,请考虑将其中一些bean声明为@Lazy(用于在第一次访问时创建,而不是在启动时创建)或@DependsOn某些其他bean(确保在当前bean之前创建特定的其他bean,超出后者的直接依赖的含义)。

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

根据某些任意的系统状态,有条件地启用或禁用完整的@Configuration类甚至个别的@Bean方法通常是有用的。一个常见的例子是,只有在Spring环境中启用了特定的概要文件时,才使用@Profile注解激活Bean(有关详细信息,请参阅Bean定义概要文件)。

@Profile 注解实际上是使用了一种更灵活的注解,称为 @Conditional。@Conditional注解表示在注册@Bean之前应该参考的特定org.springframework.context.annotation.Condition实现。

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

java

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

kotlin

override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    // Read the @Profile annotation attributes
    val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
    if (attrs != null) {
        for (value in attrs["value"]!!) {
            if (context.environment.acceptsProfiles(Profiles .of(*value as Array<String>))) {
                return true
            }
        }
        return false
    }
    return true
}

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

Combining Java and XML Configuration

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

以XML为中心使用@Configuration类

最好从XML引导Spring容器,并以特别的方式包含@Configuration类。例如,在使用Spring XML的大型现有代码库中,更容易在需要的基础上创建@配置类,并将它们从现有的XML文件中包含出来。在本节的后面,我们将介绍在这种“XML-centric”的情况下使用@Configuration类的选项。

将@Configuration类声明为纯Spring <bean />元素

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

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

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());
    }
}

kotlin

@Configuration
class AppConfig {

    @Autowired
    private lateinit var dataSource: DataSource

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun transferService() = 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=

java

public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

kotlin

fun main() {
    val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
    val transferService = ctx.getBean<TransferService>()
    // ...
}
在system-test-config.xml文件中,AppConfig<bean/>不声明id元素。尽管这样做是可以接受的,但这是不必要的,因为没有其他bean引用它,而且不太可能按名称从容器显式获取它。类似地,数据源bean只按类型自动连接,因此不严格要求显式bean id。

使用<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 Class-centric Use of XML with @ImportResource

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

kotlin

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

    @Value("\${jdbc.url}")
    private lateinit var url: String

    @Value("\${jdbc.username}")
    private lateinit var username: String

    @Value("\${jdbc.password}")
    private lateinit var password: String

    @Bean
    fun dataSource(): DataSource {
        return 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=

 

java

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

kotlin

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    // ...
}

 

spring mvc-5.2.3.release.zip 是Spring框架的一个版本,具体说就是Spring MVC(Model-View-Controller)模块的5.2.3版本的发布文件。 Spring MVC是Spring框架中用于开发Web应用程序的一部分,它提供了一种基于MVC模式的架构来组织和管理Web应用程序的各个组件。 首先,spring mvc-5.2.3.release.zip文件是一个压缩文件,它包含了Spring MVC框架的相关文件和资源。你可以通过下载和解压这个文件,来获取Spring MVC的相关代码和配置文件。 在这个压缩文件中,你可以找到一些重要的文件和文件夹。其中包括: 1. lib文件夹:这个文件夹中包含了Spring MVC框架所依赖的一些库文件,例如Spring核心库、Servlet API等。通过将这些库文件添加到你的项目中,你就可以在你的应用程序中使用Spring MVC提供的功能了。 2. config文件夹:这个文件夹中包含了Spring MVC框架的一些配置文件,例如web.xml、spring配置文件等。你需要根据你的具体需求修改和配置这些文件,以使得Spring MVC可以正确地工作在你的应用程序中。 3. sample文件夹:这个文件夹中包含了一些示例代码和示例应用程序,这些示例可以帮助你理解和学习Spring MVC的使用方法和开发技巧。 总之,spring mvc-5.2.3.release.zip是Spring MVC框架5.2.3版本的发布文件,通过下载和解压这个文件,你可以获取到Spring MVC框架的相关代码和资源,从而在你的Web应用程序中使用和开发Spring MVC功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值