一起来读官方文档-----SpringIOC(10)

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

如果你使用Maven, javax。  
注入工件在标准Maven存储库中可用
(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。  

你可以添加以下依赖到你的文件pom.xml:

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

1.11.1。@Inject和@Named
除了@Autowired,您可以使用@javax.inject.Inject注入:

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。

此外,您可以将注入点声明为提供者,从而允许按需访问作用域较短的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(...);
        // ...
    }
}

如果要为应该注入的依赖项使用qualified名称,则应使用@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它没有required属性。
以下示例展示了如何使用@Inject和 @Nullable:

public class SimpleMovieLister {

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

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

1.11.2。@Named和@ManagedBean:
您可以使用@javax.inject.Named或@javax.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名称的情况下使用@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 注解是不可组合的。
1.11.3。JSR-330标准注解的局限性

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

Spring javax.inject.* javax.inject限制/解释
@Autowired @Inject @Inject没有“required”属性。
可以与Java 8一起使用Optional
@Component @Named/@ManagedBean JSR-330没有提供可组合模型,
只是提供了一种识别已命名组件的方法。
就是说这俩注解仅仅是把类标识为bean
@Scope("singleton") @Singleton JSR-330的默认作用域类似于Spring的prototype。
但是,为了保持它与Spring的一般默认值一致,
在Spring容器中声明的JSR-330 bean在默认情况下是单例的。
为了使用除singleton之外的作用域,
您应该使用Spring的@Scope注解。
javax.inject包中还提供了一个@Scope注解。
不过,这个注解仅用于创建您自己的注解。
具体例子就看同层级包下的@Singleton注解就可以了
@Qualifier @Qualifier / @Named javax.inject.Qualifier只是用于构建自定义限定符的元注解。
具体String qualifiers (例如Spring的带有value的@Qualifier)
可以通过javax.inject.Named关联。
@Value -- no equivalent
@Required -- no equivalent
@Lazy -- no equivalent
ObjectFactory Provider javax.inject.Provider是Spring的
直接替代方法ObjectFactory,
只是get()方法名称较短。
它也可以与Spring@Autowired或
非注解构造函数和setter方法结合使用。
1.12。基于Java的容器配置

本节介绍如何在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的<beans></beans>XML配置的人来说,@Bean注解扮演着与<bean></bean>元素相同的角色。

您可以对任何Spring @Component使用@bean注解的方法。
但是,它们最常与@Configuration bean一起使用。

用@Configuration注解类表明它的主要用途是作为bean定义的源。
此外,@Configuration类通过调用同一类中的其他@Bean方法来定义bean间的依赖关系。
最简单的@Configuration类如下:

@Configuration
public class AppConfig {

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

上一AppConfig类等效于以下Spring <beans/>XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration 与  “lite” @Bean?
当@Bean方法在没有使用@Configuration注解的类中声明时,它们被称为在“lite”模式下处理。

与 full 的@Configuration不同,lite@Bean方法不能声明bean之间的依赖关系。
    Spring会正常实例化 lite @Bean,并执行依赖注入
    但是单独调用这个@Bean方法的话,就仅仅是执行new 操作没有进行依赖注入

在常见的场景中,@Bean方法将在@Configuration类中声明,以确保始终使用“full”模式,这样可以使用方法之间互相调用。
并防止通过常规Java调用意外地调用相同的@Bean方法,这有助于减少在“lite”模式下操作时难以跟踪的细微错误。

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

1.12.2。使用AnnotationConfigApplicationContext实例化Spring容器

下面几节介绍了Spring3.0中引入的AnnotationConfigApplicationContext。
这个多功能的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通的@Component类和用JSR-330元数据注解的类。

当@Configuration类作为输入提供时,@Configuration类本身被注册为bean定义,类中所有声明的@bean方法也被注册为bean定义。
当@Component和JSR-330类被提供时,它们被注册为bean定义,并且会被用在在必要的地方进行注入,比如@Autowired或@Inject。

Simple Construction

与使用Spring XML文件需要实例化ClassPathXmlApplicationContext用作输入的方式几乎相同,使用@Configuration类则需要实例化AnnotationConfigApplicationContext。

如下面的示例所示,这允许完全不使用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使用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  {
    ...
}

ComponentScan注解启用组件扫描。

有经验的Spring用户可能熟悉Springcontext:命名空间中的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

一个基于AnnotationConfigApplicationContext的WebApplicationContext变种是AnnotationConfigWebApplicationContext。
可以使用此实现来配置Spring ContextLoaderListenerservlet监听器,Spring MVC的 DispatcherServlet。
以下web.xml代码片段配置了典型的Spring MVC Web应用程序(请注意使用contextClasscontext-param和init-param),这里的启动流程前面的文章有讲过的这里不再赘述:

<web-app>
    <!-- 配置 ContextLoaderListener来使用AnnotationConfigWebApplicationContext
        替换默认的 default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 指定 配置类-->
    <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>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置 ContextLoaderListener来使用AnnotationConfigWebApplicationContext替换默认的 default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 指定mvc config-->
        <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 * name *。

你可以一个注解@Configuration-annotated或在 @Component-annotated类使用@Bean。

声明一个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中可用,该bean绑定到类型为TransferServiceImpl的对象实例,
如下图所示:

transferService -> com.acme.TransferServiceImpl

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

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
如果您一直通过声明的服务接口引用您的类型,
@Bean返回类型可以安全地加入到设计决策中。

但是,对于实现多个接口的组件或其实现类型可能引用的组件,
声明最具体的返回类型更安全(至少与引用bean的注入点所需的返回类型相同)。
Bean依赖

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

@Configuration
public class AppConfig {

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

解析机制与基于构造函数的依赖注入几乎相同。

接收生命周期回调

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

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

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

该@Bean注解支持指定任意初始化和销毁回调方法,就像SpringXML中的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配置定义的具有公共close或shutdown方法的bean将自动与销毁回调一起调用。
比如这样:在容器销毁的时候就会被调用,不需要继承任何接口
@Bean
public class ServiceFour  {
    public void close(){
        System.out.println("20201030-close");
    }
}
如果您有一个公共的close或shutdown方法,并且不希望在容器关闭时调用它,
那么可以将@Bean(destroyMethod=“”)添加到Bean定义中,以禁用默认(推断)模式。

@Bean(destroyMethod="")
public class ServiceFour  {
    public void close(){
        System.out.println("20201030-close");
    }
}

默认情况下,您可能希望对使用JNDI获取的资源执行此操作,因为它的生命周期是在应用程序外部管理的。
特别是,确保总是对数据源执行此操作。

以下示例显示如何阻止数据源的自动销毁回调:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,对于@Bean方法,通常使用程序化JNDI查找,
方法是使用SpringJndiTemplate或JndiLocatorDelegate辅助方法,或者直接InitialContext使用JNDI, 
但不使用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默认作用域是singleton,但是您可以使用@Scope注解覆盖它,如以下示例所示:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
@Scope 和 scoped-proxy

Spring提供了一种通过作用域代理处理作用域依赖关系的方便方法。
在使用XML配置时创建这样一个代理的最简单方法是<aop:scoped-proxy/>元素。

    <bean id="serviceOne"  class="org.springframework.example.service.impl.ServiceOne">
        <aop:scoped-proxy />
    </bean>

用@Scope注解在Java中配置bean提供了与proxyMode属性相同的支持。
默认设置为无代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASS or ScopedProxyMode.INTERFACES。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- http session 的作用域 将作为代理出现 -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <aop:scoped-proxy/> 
    </bean>

    <!-- 单例注入 的是一个代理不是 一个实例 避免只引用http session的一个实例 -->
    <bean id="userService" class="com.something.SimpleUserService">
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

如果您使用Java Config将上述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;
}
自定义Bean命名

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

@Configuration
public class AppConfig {

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

正如在命名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...
    }
}
bean描述

有时,提供bean的更详细的文本描述是很有帮助的。当bean(可能通过JMX)公开以进行监视时,这一点特别有用。

要向@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 definition的聚合。
@Configuration 类通过public@Bean注解方法声明Bean。

对@Configuration类上的@Bean方法的调用也可以用来定义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在@Configuration类中声明该方法时,此声明bean间依赖关系的方法才有效。
您不能使用普通@Component类声明bean间的依赖关系。
Lookup Method注入

如前所述,Lookup Method注入是一种高级特性,应该很少使用。
在单例范围bean依赖于原型范围bean的情况下,它非常有用。

将Java用于此类配置提供了实现此模式的自然方法。
以下示例演示如何使用查找方法注入:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        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();
    return command;
}

@Bean
public CommandManager commandManager() {
    // return CommandManager 的匿名实现类 并且实现了 createCommand()方法
    // return a new 原型类的 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的范围,行为可能有所不同。  
我们在这里谈论单例。
从Spring 3.2开始,不再需要将CGLIB添加到您的类路径中,
因为CGLIB类已经被重新打包org.springframework.cglib并直接包含在spring-core JAR中。
由于CGLIB在启动时动态添加特性,因此有一些限制。尤其是,配置类不能是final。
但是,从4.3开始,配置类上允许使用任何构造函数,包括使用@Autowired或一个用于默认注入的非默认构造函数声明。

如果您希望避免任何CGLIB强加的限制,请考虑在非@configuration 类上声明@Bean方法(例如,在普通@Component类上)。
然后@Bean方法之间的跨方法调用不会被拦截,但是因此您必须在构造函数或方法级别专门进行依赖的注入,直接调用非@configuration的@Bean方法Spring将不再负责依赖注入。
1.12.5。组成基于Java的配置

Spring基于Java的配置特性允许您编写注解,这可以降低配置的复杂性。

使用@Import注解

正如Spring XML文件中使用<import><import/>该元素来帮助模块化配置一样,该@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 Framework 4.2开始,@Import还支持对常规component 类的引用,类似于该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 类在上下文初始化过程中很早就被处理,强制以这种方式注入依赖项可能会导致意外的早期初始化。
只要有可能,就尽可能使用基于参数的注入,如前面的示例所示。

另外,通过@Bean使用BeanPostProcessor和BeanFactoryPostProcessor定义要特别小心。这些方法通常应该声明为static@Bean方法,而不是触发其包含的配置类的实例化。

如果自定义的BeanPostProcessor创建为早于AutowiredAnnotationBeanPostProcessor的bean实例,那么,@Autowired和@Value不适用于配置类本身,

以下示例说明如何将一个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");
}
从springframework4.3开始,@Configuration类中的构造函数注入才受支持。

还要注意,如果目标bean只定义了一个构造函数,则不需要指定@Autowired。
Fully-qualifying imported beans for ease of navigation

在前面的场景中,使用@Autowired可以很好地工作并提供所需的模块性,但是准确地确定注入的bean定义的声明位置仍然有些模糊。
例如,作为一个正在查看ServiceConfig的开发人员,您如何确切地知道@autowiredaccountrepository 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的启动创建顺序,可以考虑将其中一些bean声明为@Lazy(用于在首次访问时而不是在启动时创建)或@DependsOn某些其他bean(确保在当前bean之前创建特定的其他bean,而不是后者的直接依赖关系)。
结合Java和XML配置

Spring的@Configuration类支持并不是要完全替代Spring XML。
有些工具(如Spring XML名称空间)仍然是配置容器的理想方式。
在XML方便或必要的情况下,你有一个选择:要么通过使用ClassPathXmlApplicationContext在容器实例化在一个“以XML为中心”的方式使用,或通过使用AnnotationConfigApplicationContext搭配@ImportResource注解导入XML。

以XML为中心的@Configuration类使用

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

请记住,@Configuration类最终是容器中的bean定义。
在本系列示例中,我们创建一个@Configuration名为的类,AppConfig并将其包含在其中system-test-config.xml作为<bean/>定义。
因为 <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>

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

因为@Configuration元注解是@Component,@Configuration注解的类会自动成为组件扫描的候选类。

使用与前一个例子中描述的相同场景,我们可以重新定义system-test-config.xml利用组件扫描。

注意,在这种情况下,我们不需要显式声明<context:annotation-config/>,因为<context:component-scan/>启用相同的功能。

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

<beans>
    <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 类为中心搭配 @ImportResource 的使用XML

在@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要至少使用一些XML。
在这些场景中,您可以使用@ImportResource只定义所需的XML。

这样做可以实现“以Java为中心”的方法来配置容器,并将XML保持在最低限度。
下面的示例(包括一个配置类、一个定义bean的XML文件、一个属性文件和一个主类)演示了如何使用@ImportResource注解来实现“以Java为中心”的配置,并根据需要使用XML:

@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 = adsdfas
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342