第7章 IoC容器 V (Configuration) -- Spring4.3.8参考文档中文版

7.12基于Java的容器配置

@sunRainAmazing

7.12.1基本概念:@Bean和@Configuration

Spring新的Java配置支持中的中心工件是 @Configuration注释类和@Bean注释方法。
该@Bean注释被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于熟悉Spring的<beans/>XML配置的人员,@Bean注释与<bean/>元素的作用相同。您可以@Bean在任何Spring中使用带注释的方法 @Component,但是它们最常用于@Configurationbean。
注释类,@Configuration表示其主要用途是作为bean定义的来源。此外,@Configuration类允许通过简单地调用@Bean同一个类中的其他方法来定义bean之间的依赖关系。最简单的@Configuration类可以如下所示:

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

AppConfig上面的类将相当于以下Spring <beans/>XML:

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

完整@Configuration vs’lite’@Beans模式?
当@Bean方法在不被注释的类中声明时, @Configuration它们被称为在”lite”模式下被处理。例如,在一个@Component或甚至一个普通的老类中声明的bean方法将被认为是’lite’。
与完整的@Configurationlite @Bean方法不同,它们不能轻易地声明bean之间的依赖关系。通常,在”lite”模式下操作时,一种@Bean方法不应该调用另一种@Bean方法。
只有@Bean在@Configurationclass堂内使用方法是确保始终使用”全”模式的推荐方法。这将防止相同的@Bean方法被意外地被多次调用,并有助于减少在”精简”模式下操作时很难跟踪的细微错误。
的@Bean和@Configuration注解将在深度在下面的章节中讨论。首先,我们将介绍使用基于Java的配置创建Spring容器的各种方法。

7.12.2使用AnnotationConfigApplicationContext实例化Spring容器

以下部分介绍AnnotationConfigApplicationContextSpring 3.0中的新增功能。这种多功能ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受@Component使用JSR-330元数据注释的简单类和类。

当@Configuration提供类作为输入时,@Configuration类本身被注册为bean定义,并且@Bean类中所有声明的方法也被注册为bean定义。
当@Component被提供和JSR-330类,它们被登记为bean定义,并且假定DI元数据,例如@Autowired或者@Inject是这些类中使用的必要。

简单配置
以几乎相同的方式,实例化时,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。

使用寄存器(Class <?> …)以编程方式构建容器
一个AnnotationConfigApplicationContext可以使用一个无参数的构造被实例化,然后使用配置的register()方法。这种方法在以编程方式构建时尤其有用AnnotationConfigApplicationContext。

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

使用scan(String …)启用组件扫描
要启用组件扫描,只需对您的@Configurationclass堂进行注释,如下所示:

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

【注】经验丰富的Spring用户将熟悉Spring context:命名空间中的XML声明

<beans> <contextcomponent-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.acmepackage(或下面的任何包)中声明它,它将在调用期间被提取scan(),并且refresh()所有@Bean方法将被处理并在容器内被注册为bean定义。

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

<web-app> 
    < - 配置ContextLoaderListener以使用AnnotationConfigWebApplicationContext
        而不是默认的XmlWebApplicationContext  - > 
    <context-param> 
    <param-name> contextClass </param-name> 
    <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value> 
    </context-param>
    < - 配置位置必须由一个或多个逗号或空格分隔
        完全限定的@Configuration类。完全合格的包也可以
        指定用于组件扫描 - > 
    <context-param> 
    <param-name> contextConfigLocation </param-name> 
    <param-value> com.acme.AppConfig </param-value> 
    </context-param>
    < - 使用ContextLoaderListener引用根应用程序上下文 - > 
    <listener> 
        <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> 
    </listener>
    < -声明一个Spring MVCDispatcherServlet像往常一样- > 
    <servlet> 
     <servlet-name>dispater</servlet-name> 
      <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> 
        < ! - 配置DispatcherServlet以使用AnnotationConfigWebApplicationContext
            而不是默认的XmlWebApplicationContext  - > 
          <init-param> 
        <param-name> contextClass </param-name> 
        <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value> 
    </init-param> 
    < - 同样,配置位置必须由一个或多个逗号或空格分隔
            和完全限定的@Configuration- > 
    <init-param> 
        <param-name> contextConfigLocation </param-name> 
        <param-value> com.acme.web.MvcConfig </param-value> 
    </init-param > 
   </servlet>
    < - 将/app /*的所有请求映射到dispatcher servlet  - > 
    <servlet-mapping> 
    <servlet-name> dispatcher </servlet-name> 
    <url-pattern> /app /* </url-pattern > 
    </servlet-mapping> </web-app>

7.12.3使用@Bean注释

@Bean是一种方法级注释和XML <bean/>元素的直接模拟。注释支持一些提供的属性<bean/>,例如: init-method, destroy-method, autowiring和name。
您可以在@Bean注释中@Configuration或在 注释类中使用注释@Component。
声明一个bean
要声明一个bean,只需使用注释来注释一个方法@Bean。您可以使用此方法在ApplicationContext指定为方法返回值的类型中注册bean定义。默认情况下,bean名称将与方法名称相同。以下是@Bean方法声明的一个简单示例:

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

上述配置与以下Spring XML完全相同:

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

这两个声明使一个名为transferService可用 的bean ApplicationContext,绑定到一个类型为的对象实例TransferServiceImpl:

transferService  - > com.acme.TransferServiceImpl
Bean依赖关系

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

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

解析机制与基于构造函数的依赖注入几乎相同,有关详细信息,请参阅相关章节。
接收生命周期回调
与定义的任何类@Bean注释支持定时生命周期回调,可以使用@PostConstruct并@PreDestroy注解从JSR-250,见 JSR-250注解进一步的细节。
常规的Spring 生命周期回调也被完全支持。如果一个bean实现InitializingBean,DisposableBean或者Lifecycle它们各自的方法被容器调用。
还完全支持标准的*Aware接口,如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等。
该@Bean注释支持指定任意的初始化和销毁​​回调方法,就像Spring XML init-method和元素destroy-method上的属性一样bean:

public class Foo {
     public void init(){
         //初始化逻辑
    }
}
public class Bar {
     public void cleanup(){
         //destroy logic
    }
}
@Configuration 
public class AppConfig {
    @Bean(initMethod ="init")
    public Foo foo(){
         return new Foo();
    }
    @Bean(destroyMethod ="cleanup")
    public Bar bar(){
         return new Bar();
    }
}

【注】默认情况下,使用具有publicclose或shutdown 方法的Java配置定义的bean 将自动使用销毁回调。如果您有public close或shutdown方法,并且您不希望在容器关闭时调用它,只需添加@Bean(destroyMethod=”“)到您的bean定义以禁用默认(inferred)模式。
默认情况下,您可能希望通过JNDI获取的资源,因为其生命周期在应用程序之外进行管理。特别地,请确保始终为此做,DataSource因为它已知在Java EE应用程序服务器上有问题。

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

另外,使用@Bean方法,通常会选择使用编程的JNDI查找:使用Spring的JndiTemplate/ JndiLocatorDelegatehelpers或直接的JNDI InitialContext使用,而不是JndiObjectFactoryBean强制您将返回类型声明为FactoryBean类型而不是实际的目标类型的变体在其他@Bean方法中更难用于交叉引用调用,这些方法将在此引用提供的资源。

当然,在上述情况下, 在配置过程中直接Foo调用init()方法同样有效:

@Configuration
 public class AppConfig {
     @Bean
    public Foo foo(){
         Foo foo = new Foo();
         foo.init();
    return foo;
    }
    //...
}

【注】当您直接在Java中工作时,您可以对对象执行任何您喜欢的操作,并不总是需要依赖容器生命周期!

指定bean范围
使用@Scope注释
您可以指定使用@Bean注释定义的bean 应具有特定的作用域。您可以使用” Bean Scopes”部分中指定的任何标准作用域 。
默认范围是singleton,但您可以使用@Scope注释覆盖此:

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

@Scope和范围代理
Spring提供了一个方便的方法,通过范围代理来处理范围依赖 。使用XML配置创建此类代理的最简单的方法是<aop:scoped-proxy/>元素。使用@Scope注释在Java中配置bean提供了与proxyMode属性相似的支持。默认值为no proxy(ScopedProxyMode.NO),但可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
如果将范围限定的代理示例从XML参考文档(请参阅前面的链接)移植到我们@Bean使用Java,那么它将如下所示:
//作为代理public的HTTP Session作用域bean

@Bean 
@SessionScope 
public UserPreferences userPreferences(){
     return new UserPreferences();
}
@Bean 
public Service userService(){
    UserService service = new SimpleUserService();
    //对代理的userPreferences bean的引用
    service.setUserPreferences(userPreferences());
    return service;
}

自定义bean命名
默认情况下,配置类使用@Bean方法的名称作为生成的bean的名称。但是,该name属性可以覆盖此功能。

@Configuration 
public class AppConfig {
    @Bean(name ="myFoo")
    public Foo foo(){
         return  new Foo();
    }
}

bean混叠
如第7.3.1节”命名bean”中所述,有时候需要给出一个单个的多个名称,也称为bean别名。 注释的name属性@Bean为此目的接受一个String数组。

@Configuration 
public class AppConfig {
     @Bean(name = {"dataSource""subsystemA-dataSource""subsystemB-dataSource"})
     public DataSource dataSource(){
         //实例化,配置和返回DataSource bean ...
    }
}

bean描述
有时,提供一个更详细的文本描述是有用的。当bean暴露(可能通过JMX)进行监视时,这可能特别有用。
为了说明添加到@Bean的 @Description 注释,可以使用:

@Configuration 
public class AppConfig {
    @Bean 
    @Description("提供一个bean的基本示例")
    public Foo foo(){
         return new Foo();
    }
}

7.12.4使用@Configuration注释

@Configuration是一个类级的注释,表示一个对象是一个bean定义的源。@Configuration类通过public@Bean注释方法声明bean 。@Bean对@Configuration类上的方法的调用也可以用于定义bean之间的依赖关系。有关一般性介绍,请参见第7.12.1节”基本概念:@Bean和@Configuration”。
注入bean之间的依赖关系
当@Bean彼此之间有依赖关系时,表示该依赖关系与使用一个bean方法调用另一个方法一样简单:

@Configuration 
public class AppConfig {
    @Bean
    public Foo foo(){
         return  new Foo(bar());
    }
    @Bean
    public Bar bar(){
         return new Bar();
    }
}

在上面的例子中,foobean接收到bar通过构造函数注入的引用。
【注】这种声明bean间依赖关系的@Bean方法仅在方法在@Configuration类中声明时有效。您不能使用普通@Component类声明bean间依赖关系。
查找方法注入
如前所述,查找方法注入是一种您应该很少使用的高级功能。在单例范围的bean对原型范围的bean具有依赖性的情况下,这是有用的。对这种类型的配置使用Java提供了实现此模式的自然方法。

public abstract class CommandManager {
     public Object process(Object commandState){
         //获取相应Command界面的新实例
        Command command = createCommand();
    //设置(希望全新)命令实例的状态
        command.setState(commandState);
    return command.execute();
    }
    //好的,但是这个方法的实现在哪里?
    protected abstract Command createCommand();
}

使用Java配置支持,您可以创建CommandManager抽象createCommand()方法被覆盖的子类,以便它查找一个新的(原型)命令对象:

@Bean 
@Scope("prototype") 
public AsyncCommand asyncCommand(){
    AsyncCommand command= new AsyncCommand();
    //将依赖关系作为必需的
    return command;
}
@Bean public CommandManager commandManager(){ 
//返回一个新的原型的命令commandManager的新的匿名实现返回一个新的原型Command对象
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并返回它,所以通常会有2个实例(每个服务一个)。这肯定是有问题的:在Spring中,实例化的bean singleton默认有一个范围。这就是神奇的地方:所有的@Configuration类都在启动时被子类化CGLIB。在子类中,子方法在调用父方法并创建新实例之前首先检查任何缓存(作用域)bean。请注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经重新打包org.springframework.cglib并直接包含在spring-core JAR中。

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

由于CGLIB在启动时动态添加功能,特别是配置类不能是最终的,所以有一些限制。但是,从4.3开始,在配置类中允许使用任何构造函数,包括使用 @Autowired默认注入的单个非默认构造函数声明。
如果您希望避免任何CGLIB限制,请考虑@Bean 在非@Configurationclass程中声明方法,例如在简单@Component类上。方法之间的交叉方法调用@Bean将不会被截获,所以你必须专门依赖于依赖注入在那里的构造函数或方法级别。

7.12.5编写基于Java的配置

使用@Import注释
<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);
    //现在既beanA和B将提供... 
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求开发人员@Configuration在构建期间记住潜在的大量 类。
【注】从Spring Framework 4.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(){
         //返回新的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");
}

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

【注】确保您注入的依赖关系是最简单的。@Configuration 类在上下文的初始化期间处理相当早,并强制以这种方式注入依赖关系可能会导致意外的早期初始化。只要有可能,就像上面的例子一样使用基于参数的注入。

另外,要特别小心BeanPostProcessor和BeanFactoryPostProcessor通过定义@Bean。那些应该通常被声明为static @Bean方法,而不是触发它们的包含配置类的实例化。否则,@Autowired并且@Value将不会在配置类本身上工作,因为它被早期创建为一个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(){
         //返回新的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");
}

【注】@Configuration类中的构造方法注入仅支持Spring Framework 4.3。还要注意,没有必要指定@Autowired目标bean是否只定义一个构造函数; 在上面的示例中,@Autowired在RepositoryConfig构造函数上不是必需的。

在上述情况下,使用@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(){
         //通过'配置类导航到@Bean方法!
    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})//导入具体的配置!
public class SystemTestConfig {
    @Bean
     public DataSource dataSource(){
         //返回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类及其依赖性与通常基于界面的代码的过程没有什么不同。
有条件地包括@Configuration类或@Bean方法
有条件地启用或禁用基于某些任意系统状态的完整@Configuration类,甚至是单个@Bean方法,这通常是有用的。一个常见的例子是@Profile仅在Spring中启用特定配置文件时才使用注释来激活bean Environment( 有关详细信息,请参见第7.13.1节”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) {
         //读取@profile注释属性 
        MultiValueMap <String,Object> 
        attrs= metadata.getAllAnnotationAttributes(Profile.class.getName ());
        if(attrs!= null){
             for(Object value:attrs.get( "value")){
                 if(context.getEnvironment()。acceptableProfiles(((String [])value))){
                     return true;
                }
            }
    return false;
        }
    }
    return true;
}

有关详细信息,请参阅javadocs。 @Conditional
结合Java和XML配置

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

以XML为中心使用@Configuration类
可能最好从XML引导Spring容器,并@Configuration以ad-hoc方式包含 类。例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration类更容易,并将它们从现有的XML文件中包含起来。下面你可以找到使用@Configuration这种”以XML为中心”的类的类的选项。
请记住,@Configuration类最终只是容器中的bean定义。在这个例子中,我们创建了一个@Configuration名为类AppConfig,包括其内system-test-config.xml的<bean/>定义。因为 <context:annotation-config/>打开,容器将识别 @Configuration注释并处理 正确@Bean声明的方法AppConfig。

@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> 
    <! - 启用处理注释,如@Autowired和@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 <bean/>不声明id 元素。虽然这样做是可以接受的,但是没有其他bean将不会引用它是不必要的,并且它不太可能以名称从容器显式提取。与DataSourcebean 类似,它只能由类型自动连接,因此id不需要严格的显式bean 。

因为@Configuration是用元注释的@Component,所以注释的@Configuration类是组件扫描的自动候选。使用与上述相同的场景,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明 <context:annotation-config/>,因为<context:component-scan/>启用相同的功能。
system-test-config.xml:

<bean> 
    <! - 取出并注册AppConfig作为bean定义 - > 
    <contextcomponent-scan base-package = "com.acme" /> 
    <contextproperty-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保持在最低限度。

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

7.13环境抽象

该Environment 集成在容器中的模型应用环境的两个关键方面的抽象:型材 和性能。

一个轮廓是bean定义一个命名的逻辑组,只有当指定的配置文件是活动的容器进行登记。bean可以被分配到不管是以XML定义还是通过注释的配置文件。所述的作用Environment的对象与相对于简档是在确定哪个简档(如果有的话)是当前有效的,并且其配置文件(如果有的话)应该是默认的活性。

属性在几乎所有应用程序中起着重要作用,可能来自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,Maps等。Environment与属性相关的对象的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性。

7.13.1 Bean定义配置文件

Bean定义配置文件是核心容器中允许在不同环境中注册不同bean的机制。环境这个词 对于不同的用户可能意味着不同的东西,这个功能可以帮助许多用例,包括:
在开发中反对内存中的数据源,在QA或生产中从JNDI查找相同的数据源
仅在将应用程序部署到性能环境中时注册监控基础架构
注册客户A与客户B部署的bean的自定义实现
让我们考虑一个需要一个实际应用的第一个用例 DataSource。在测试环境中,配置可能如下所示:

@Bean 
public DataSource dataSource(){
     return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my- schema.sql ")
        .addScript("my-test-data.sql")
        .build();
}

现在我们考虑如何将该应用程序部署到QA或生产环境中,假设应用程序的数据源将在生产应用程序服务器的JNDI目录中注册。我们的dataSourcebean子现在看起来像这样:

@Bean(destroyMethod ="")
 public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return(DataSource)ctx.lookup("java:comp /env /jdbc /datasource");
}

问题是如何根据当前的环境在这两种变化之间进行切换。随着时间的推移,Spring用户已经设计了许多方法来完成此操作,通常依赖于系统环境变量和<import/>包含${placeholder}令牌的XML 语句的组合,根据环境变量的值来解析到正确的配置文件路径。Bean定义配置文件是一个核心容器功能,可以解决这个问题。
如果我们将上述环境特定的bean定义的用例范例概括为一,我们最终需要在某些上下文中注册某些bean定义,而不在其他的上下文中。您可以说要在情况A中注册一个bean定义的特定配置文件,并且在情况B中注册一个不同的配置文件。我们先看看如何更新我们的配置以反映这种需求。
@profile
@Profile 当一个或多个指定的配置文件处于活动状态时,注释允许您指示组件有资格注册。使用我们上面的例子,我们可以重写dataSource配置如下:

@Configuration 
@Profile("development") 
public  class StandaloneDataConfig {
    @Bean
    public DataSource dataSource(){
         return  new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com /bank /config /sql /schema.sql")
            .addScript("classpath:com /bank /config /sql /test-data.sql")
            .build();
    }
}
@Configuration 
@Profile("production") 
public class JndiDataConfig {
    @Bean(destroyMethod ="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return(DataSource)ctx.lookup("java:comp /env /jdbc /datasource");
    }
}

【注】如前所述,使用@Bean方法,通常会选择使用程序化的JNDI查找:使用Spring JndiTemplate/ JndiLocatorDelegatehelpers或InitialContext上面显示的直接JNDI 使用,但不会JndiObjectFactoryBean 强制您强制将返回类型声明为类型的变体FactoryBean。

@Profile可用作创建自定义组合注释目的的元注释。以下示例定义了一个自定义 @Production注释,可以用作以下替代方法

@Profile("production"):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
 public @interface Production {
}

【注】如果一个@Configuration类被标记@Profile,与该类关联的所有@Bean方法和 @Import注释将被旁路,除非一个或多个指定的配置文件处于活动状态。如果某个@Component或某个@Configuration类别被标记@Profile({“p1”, “p2”}),该类别将不会被注册/处理,除非已经激活了个人资料”p1”和/或”p2”。如果给定的配置文件带有NOT运算符(!)的前缀,则如果配置文件不 活动,则注释的元素将被注册。例如,@Profile({“p1”, “!p2”})如果配置文件”p1”处于活动状态或配置文件”p2”不活动,则会发生注册。

@Profile 也可以在方法级别声明,以仅包括配置类的一个特定bean,例如对于特定bean的替代变体:

@Configuration 
public class AppConfig {
    @Bean("dataSource")
    @Profile("development")
    public DataSource standaloneDataSource(){
         return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com /bank /config /sql /schema.sql")
            .addScript("classpath:com /bank /config /sql /test-data.sql")
            .build();
    }
    @Bean("dataSource")
    @Profile("production")
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return(DataSource)ctx.lookup("java:comp /env /jdbc /datasource");
    }
}

【注】随着@Profile对@Bean方法,特殊情况可以申请:在重载的情况下@Bean相同的Java方法名的方法(类似于构造函数重载),一个@Profile条件需要对所有重载方法一致声明。如果条件不一致,只有重载方法中第一个声明的条件才会重要。@Profile因此不能用于选择具有特定参数签名的重载方法; 在同一个bean的所有工厂方法之间的分辨率遵循创建时的Spring的构造函数解析算法。
如果要定义具有不同配置文件条件的替代bean,请使用通过@Beanname属性指向同一个bean名称的不同Java方法名称,如上例所示。如果参数签名全部相同(例如,所有变体都具有无参数工厂方法),则这是首先在有效的Java类中表示此类排列的唯一方法(因为只能有一种方法一个特定的名称和参数签名)。

XML bean定义配置文件
XML对应是元素的profile属性<beans>。我们上面的示例配置可以重写为两个XML文件,如下所示:

<beans profile = "development" 
    xmlns = "http://www.springframework.org/schema/beans" 
    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:jdbc = " http://www.springframework.org/schema/jdbc " 
    xsi:schemaLocation = " ..." >
    <jdbc:embedded-database id = "dataSource" > 
    <jdbc:script location = "classpath:com /bank /config /sql /schema.sql" /> 
    <jdbc:script location = "classpath:com /bank /config /sql /test-data.sql" /> 
    </jdbc:embedded-database> </beans>
<beans profile = "production" 
    xmlns = "http://www.springframework.org/schema/beans" 
    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:jee = " http://www.springframework.org/schema/jee " 
    xsi:schemaLocation = " ..." >
    <jee:jndi-lookup id = "dataSource" jndi-name = "java:comp /env /jdbc /datasource" /> </beans>


也可以避免<beans/>同一文件中的拆分和嵌套元素:

<beans  xmlns = "http://www.springframework.org/schema/beans" 
    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:jdbc = "http://www .springframework.org /schema /jdbc" 
    xmlns:jee = "http://www.springframework.org/schema/jee" 
    xsi:schemaLocation = "..." >
    <! - 其他bean定义 - >
    <bean profile = "production" > 
        <jdbc:embedded-database id = "数据源" > 
    <jdbc:script location = "classpath:com /bank /config /sql /schema.sql" /> 
    <jdbc:script location = "classpath:com /bank /config /sql /test-data.sql" /> 
    </jdbc:embedded-database> 
    </beans>
    <beans profile = "production" > 
    <jee:jndi-lookup id = "dataSource" jndi-name = "java:comp /env /jdbc /datasource" /> 
    </bean> </beans>

在spring-bean.xsd受到了制约,使这些元素只能作为文件中的最后一个人。这应该有助于提供灵活性,而不会在XML文件中产生混乱。

激活配置文件
现在我们已经更新了我们的配置,我们仍然需要指示Spring哪个配置文件是活动的。如果我们现在开始我们的示例应用程序,我们会看到一个NoSuchBeanDefinitionException抛出的,因为容器找不到名为的Spring bean dataSource。
激活配置文件可以通过几种方式完成,但最简单的方法是通过以下方式对EnvironmentAPI进行 编程ApplicationContext:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment()。setActiveProfiles("development");
ctx.register(SomeConfig.class,StandaloneDataConfig.class,JndiDataConfig.class);
ctx.refresh();

此外,配置文件也可以通过spring.profiles.active可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml或甚至作为JNDI中的条目指定的属性声明性地激活 (参见第7.13.2节”PropertySource抽象”)。在集成测试中,可以通过模块中的@ActiveProfiles注释来声明活动配置文件spring-test(请参见”具有环境配置文件的上下文配置”一节)。
请注意,配置文件不是”任一或”命题; 可以一次激活多个配置文件。以编程方式,只需为setActiveProfiles()方法提供多个配置文件名称即可 接受String…​varargs:

ctx.getEnvironment().setActiveProfiles("profile1""profile2");

以声明方式,spring.profiles.active可以接受以逗号分隔的配置文件名称列表:

-Dspring.profiles.active = "profile1,profile2"

默认配置文件
在默认的配置文件表示默认启用的配置文件。考虑以下几点:

@Configuration 
@Profile("default") 
public class DefaultDataConfig {
    @Bean
    public DataSource dataSource(){
         return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com /bank /config /sql /schema.sql")
            .build();
    }
}

如果没有配置文件激活,dataSource将创建以上内容; 这可以被看作是为一个或多个bean 提供默认定义的一种方式。如果启用任何配置文件,则默认配置文件将不适用。
默认的配置文件的名称可以使用改变setDefaultProfiles()的Environment或声明使用的spring.profiles.default属性。

7.13.2 PropertySource抽象

Spring的Environment抽象提供了可配置的属性源层次结构的搜索操作。要充分解释,请考虑以下几点:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();boolean containsFoo = env.containsProperty("foo");

System.out.println(“我的环境是否包含’foo’属性?” + containsFoo);
在上面的代码段中,我们看到一个高级的方法来询问Spring是否foo为当前环境定义了属性。为了回答这个问题,Environment对象执行一组PropertySource 对象的搜索。APropertySource是对任何键值对的简单抽象,Spring的StandardEnvironment 配置有两个PropertySource对象,一个表示一组JVM系统属性(一个System.getProperties()),一个表示一组系统环境变量(a la System.getenv())。

【注】这些默认属性来源StandardEnvironment用于独立应用程序。StandardServletEnvironment 填充了额外的默认属性源,包括servlet配置和servlet上下文参数。StandardPortletEnvironment 同样可以访问portlet配置和portlet上下文参数作为属性源。两者都可以选择启用JndiPropertySource。有关详细信息,请参阅javadoc。

具体来说,在使用时StandardEnvironment,env.containsProperty(“foo”) 如果在运行时存在foo系统属性或foo环境变量,则调用将返回true 。

【注】执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此如果foo在调用期间恰好在两个地方设置了属性env.getProperty(“foo”),则系统属性值将’win’并优先通过环境变量返回。请注意,属性值将不会被合并,而是完全被前面的条目覆盖。
对于普通的StandardServletEnvironment,完整的层次结构如下,顶部的最高优先级条目:

ServletConfig参数(如果适用,例如在DispatcherServlet上下文的情况下)
ServletContext参数(web.xml上下文参数条目)
JNDI环境变量(“java:comp /env /”条目)
JVM系统属性(“-D”命令行参数)
JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许你有一个自定义的属性来源,你想要整合到这个搜索。没问题 - 只需实现和实例化自己的PropertySource,并将其添加到PropertySources当前的集合中Environment:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources source = ctx.getEnvironment()。getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码中,MyPropertySource在搜索中添加了最高优先级。如果它包含一个 foo属性,它将被检测到并返回foo任何其他任何属性之前PropertySource。所述MutablePropertySources API公开了大量的,其允许该组的属性源的精确操作方法。

7.13.3 @PropertySource

该@PropertySource 注解提供便利和声明的机制添加PropertySource 到Spring的Environment。
给定一个包含键/值对的文件”app.properties” testbean.name=myTestBean,下面的@Configuration类使用@PropertySource一个调用来testBean.getName()返回”myTestBean”。

@Configuration @PropertySource("classpath:/com/myco/app.properties") 
public class AppConfig {
     @Autowired
    Environment env;
    @Bean
    public TestBean testBean(){
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

${…​}存在于@PropertySource资源位置的任何占位符将针对已经针对环境注册的一组资源来解决。例如:

@Configuration 
@PropertySource( "classpath:/com/acme/properties-config.xml")
public class AppConfig {
     @Autowired
     Environment env;
    @Bean
    public TestBean testBean(){
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设”my.placeholder”存在于已注册的资源之一(例如系统属性或环境变量)中,占位符将被解析为相应的值。如果没有,那么将使用”default /path”作为默认值。如果未指定默认值,并且无法解析属性, IllegalArgumentException则将抛出一个。

7.13.4声明中的占位符解析

历史上,元素中占位符的值可以仅针对JVM系统属性或环境变量来解决。不再是这种情况。由于环境抽象集成在整个容器中,因此可以很容易地通过它来分配占位符。这意味着您可以以任何您喜欢的方式配置解决流程:更改通过系统属性和环境变量搜索的优先级,或者完全删除它们; 根据需要将您自己的财产来源添加到组合中。
具体而言,下面的语句作品无论在哪里customer 属性定义,只要它是可用的Environment:

<beans> <import  resource = "com /bank /service /$ {customer} -config.xml" /> </beans>

7.14注册LoadTimeWeaver

在LoadTimeWeaver用于由Spring动态变换的类,因为它们被装载到Java虚拟机(JVM)。
为了启用加载时编译,添加@EnableLoadTimeWeaving到你的一个 @Configuration类中:

@Configuration 
@EnableLoadTimeWeaving 
public class AppConfig {
}

或者,对于XML配置,使用context:load-time-weaver元素:

<beans>  <context:load-time-weaver /> </beans>

一旦配置了ApplicationContext。任何ApplicationContext 可能实现的bean LoadTimeWeaverAware,从而接收对加载时间织造器实例的引用。这与Spring的JPA支持相结合特别有用,JPA类转换可能需要加载时间编译。请查询LocalContainerEntityManagerFactoryBeanjavadocs了解更多详细信息。有关AspectJ加载时编译的更多信息,请参见第11.8.4节”Spring框架中使用AspectJ的加载时编译”。

7.15 ApplicationContext的附加功能

如本章介绍中所述,该org.springframework.beans.factory 包提供了管理和操作bean的基本功能,包括以编程方式。该org.springframework.context包增加了ApplicationContext 扩展BeanFactory接口的接口,除了扩展其他接口以在更多的应用程序面向框架中提供额外的功能。许多人ApplicationContext以完全声明的方式使用,甚至没有以编程方式创建它,而是依赖于支持类,例如ContextLoader自动实例化 ApplicationContext作为Java EE Web应用程序正常启动过程的一部分。
为了BeanFactory在更加面向框架的风格中增强功能,上下文包还提供以下功能:
通过MessageSource界面访问i18n风格的邮件。
通过ResourceLoader界面访问资源,如URL和文件。
事件发布即是bean实现的ApplicationListener接口,通过使用ApplicationEventPublisher接口。
加载多个(分层)上下文,允许每个上下文通过HierarchicalBeanFactory接口集中在一个特定的层,如应用程序的Web层 。

7.15.1使用MessageSource进行国际化

该ApplicationContext接口扩展了一个称为接口的接口MessageSource,因此提供国际化(i18n)功能。Spring还提供了HierarchicalMessageSource可以分层解析消息的接口。这些接口一起为Spring效果消息解析提供了基础。在这些接口上定义的方法包括:
String getMessage(String code, Object[] args, String default, Locale loc):用于从中检索消息的基本方法MessageSource。当没有找到指定区域设置的消息时,将使用默认消息。传递的任何参数都将成为替换值,使用MessageFormat标准库提供的功能。

String getMessage(String code, Object[] args, Locale loc):与以前的方法基本相同,但有一个区别:不能指定默认消息; 如果无法找到该消息,NoSuchMessageException则抛出一个消息。
String getMessage(MessageSourceResolvable resolvable, Locale locale):上述方法中使用的所有属性也被包装在一个名为的类中 MessageSourceResolvable,您可以使用此方法。
当ApplicationContext被加载时,它自动搜索MessageSource 在上下文中定义的bean。bean必须有这个名字messageSource。如果找到这样一个bean,那么所有调用上述方法都将被委派给消息源。如果没有找到消息源,则ApplicationContext尝试查找包含具有相同名称的bean的父。如果是,它会使用该bean作为MessageSource。如果ApplicationContext找不到任何消息来源,则将 DelegatingMessageSource实例化为空 ,以便能够接受对上述方法的调用。

Spring提供了两种MessageSource实现方式,ResourceBundleMessageSource和 StaticMessageSource。两个实现HierarchicalMessageSource为了做嵌套消息传递。在StaticMessageSource很少使用,但提供了编程的方式向消息源添加消息。在ResourceBundleMessageSource被示出在下面的例子:

<beans> 
    <bean id = "messageSource" 
    class = "org.springframework.context.support.ResourceBundleMessageSource" > 
    <property name = "basenames" > 
       <list> 
        <value>format</value> 
        <value> exception </value > 
        <value> windows </value> 
       </list> 
    </property> 
    </bean> </beans>

在这个例子中,假设你在你的类路径中定义了三个资源束format,exceptions并且windows。解决消息的任何请求将以JDK标准方式通过ResourceBundles解析消息来处理。为了示例的目的,假设两个上述资源束文件的内容是…

# in format.properties

message=Alligators rock!

# in exceptions.properties

argument.required=The {0} argument is required. # 参数时必须的
MessageSource下一个示例中显示了执行功能的程序。请记住,所有ApplicationContext实现也是MessageSource 实现,因此可以转换到MessageSource接口。

public static void main(String [] args){
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message"null"Default"null);
    System.out.println(message);
}

上述程序的结果将是…
Alligators rock! //杂乱无章
因此,要总结,将MessageSource是在一个名为文件中定义的beans.xml,它存在于你的classpath的根目录。该messageSourcebean定义是指通过它的一些资源包的basenames属性。这是在列表中传递的三个文件basenames属性存在于你的classpath根目录的文件,被称为format.properties,exceptions.properties和 windows.properties分别。
下一个示例显示传递给消息查找的参数; 这些参数将被转换为字符串并插入到查询消息中的占位符中。

<beans>
    <! - 这个MessageSource正在一个Web应用程序中使用 - > 
    <bean id = "messageSource" class =           
        "org.springframework.context.support.ResourceBundleMessageSource" > 
    <property name = "basename" value = "exceptions" /> 
    </bean>
    <! - 可以将上述MessageSource注入到这个POJO中 - > 
    <bean id = "example" class = "com.foo.Example" > 
        <property name = "messages" ref = "messageSource" /> 
    </bean></beans>
public class Example{
    private MessageSource messages;
    public void setMessages(MessageSource messages){
         this .messages = messages;
    }
    public void execute(){
        String message = this .messages.getMessage("argument.required"new Object [] { "userDao" },"Required"null);
        System.out.println(messages);
    }
}

来自调用该execute()方法的结果将是…
userDao参数是必需的。

关于国际化(i18n),Spring的各种MessageSource 实现遵循与标准JDK相同的语言环境分辨率和回退规则 ResourceBundle。总之,和继续该示例messageSource先前定义的,如果你想解析British(消息en-GB)语言环境中,您将创建文件名为format_en_GB.properties,exceptions_en_GB.properties和 windows_en_GB.properties分别。
通常,区域设置分辨率由应用程序的周围环境进行管理。在此示例中,手动指定针对(英国)消息将被解析的区域设置。

#在exceptions_en_GB.properties中
argument.required = Ebagum lad,需要{0}参数。
public static void main(final String [] args){
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required"new Object [] { "userDao" },"Required",Locale.UK);
    的System.out.println(messages);
}

从上述程序的运行产生的结果将是…
Ebagum lad,需要’userDao’参数,我说是必需的。
您还可以使用该MessageSourceAware接口获取对MessageSource已定义的任何引用 。在创建和配置bean时,在ApplicationContext实现该MessageSourceAware接口的任何bean中定义 了该应用程序上下文MessageSource。
【注】作为替代ResourceBundleMessageSource,Spring提供了一个 ReloadableResourceBundleMessageSource类。此变体支持相同的捆绑文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活 。特别是,它允许从任何Spring资源位置(不仅仅是类路径)读取文件,并且支持对bundle属性文件进行热重新加载(同时在它们之间高效缓存)。查看ReloadableResourceBundleMessageSourcejavadocs的详细信息。

7.15.2标准和自定义事件

ApplicationContext通过ApplicationEvent 类和ApplicationListener接口提供事件处理。如果实现该ApplicationListener接口的bean 被部署到上下文中,则每次ApplicationEvent发布到该ApplicationContextBean时,该Bean被通知。本质上,这是标准的观察者设计模式。
【注】从Spring 4.2开始,事件基础设施得到了显着改进,并提供了基于注释的模型,以及发布任何不必要扩展的任意事件的能力ApplicationEvent。当这样的对象被发布时,我们将它包装在一个事件中。
Spring提供以下标准事件:

表7.7 内置事件
这里写图片描述

您还可以创建和发布自己的自定义事件。这个例子演示了一个扩展Spring ApplicationEvent基类的简单类:

public class BlackListEvent extends ApplicationEvent {
    private final String address;
    private finalString test
    public BlackListEvent(Object source,String address,String test){
         super(source);
        this .address = address;
        this .test = test
    }
    //访问器和其他方法...
}

要发布自定义ApplicationEvent,请调用publishEvent()方法 ApplicationEventPublisher。通常,这是通过创建一个实现ApplicationEventPublisherAware并注册为Spring bean 的类完成的 。以下示例演示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {
    private List <String> blackList;
    private ApplicationEventPublisher publisher;
    public void setBlackList(List <String> blackList){
         this .blackList = blackList;
    }
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher){
         this .publisher = publisher;
    }
    public void sendEmail(String address,String text){
         if(blackList.contains(address)){
            BlackListEvent event = new BlackListEvent(this,address,text);
            publisher.publishEvent(event);
        return ;
        }
    //发电子邮件...
    }
}

在配置时,Spring容器会检测到这个EmailService实现, ApplicationEventPublisherAware并会自动调用 setApplicationEventPublisher()。实际上,传入的参数将是Spring容器本身; 您只需通过其ApplicationEventPublisher界面与应用程序上下文进行 交互。
要接收定制ApplicationEvent,创建一个实现 ApplicationListener并注册为Spring bean的类。以下示例演示了这样一个类:

public class BlackListNotifier implements ApplicationListener <BlackListEvent> {
    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress){
         this .notificationAddress = notificationAddress;
    }
    public void onApplicationEvent(BlackListEvent event){
         //通过notificationAddress ...通知对方
    }
}

请注意,ApplicationListener您的自定义事件的类型通常参数化BlackListEvent。这意味着该onApplicationEvent()方法可以保持类型安全,避免任何需要下载。您可以根据需要注册尽可能多的事件侦听器,但请注意,默认情况下,事件侦听器将同步接收事件。这意味着publishEvent()方法将阻塞,直到所有侦听器都完成处理事件。这种同步和单线程方法的一个优点是,当监听器接收到一个事件时,如果事务上下文可用,则它在发布者的事务上下文中运行。如果需要另外的事件发布策略,请参考Spring的ApplicationEventMulticaster界面的javadoc 。
以下示例显示了用于注册和配置上述每个类的bean定义:

<bean id = "emailService" class = "example.EmailService" > 
    <property name = "blackList" > 
    <list> 
       <value> [email protected] </value> 
       <value> [email protected] </value> 
       <value> [email protected] </value> 
    </list> 
    </property> </bean>
<bean id = "blackListNotifier" class = "example.BlackListNotifier" > 
    <property name = "notificationAddress" value = "[email protected]" /> </bean>

将它们放在一起,当调用bean 的sendEmail()方法时emailService,如果有任何电子邮件应该被列入黑名单,BlackListEvent则会发布类型的自定义事件 。所述blackListNotifierbean被注册为一个 ApplicationListener,从而接收到BlackListEvent,在该点它可以通知适当方。

【注】Spring的事件机制是为了在同一应用程序环境中的Spring Bean之间进行简单的通信而设计的。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration项目为构建基于着名的Spring编程模型的轻量级,面向模式的事件驱动架构提供了完整的支持 。

基于注释的事件侦听器
从Spring 4.2开始,可以通过EventListener注释在托管bean的任何public方法上注册事件侦听器。该BlackListNotifier可改写如下:

public class BlackListNotifier {
    private String notificationAddress;
    public void setNotificationAddress(String notificationAddress){
         this .notificationAddress = notificationAddress;
    }
    @EventListener
    public void processBlackListEvent(BlackListEvent event){
         //通过notificationAddress ...通知适当的方
    }
}

如上所述,方法签名再次声明其侦听的事件类型,但这次使用灵活的名称,而不实现特定的侦听器接口。事件类型也可以通过泛型来缩小,只要实际的事件类型在其实现层次结构中解析通用参数即可。
如果您的方法应该监听几个事件,或者如果要根本没有参数定义它,也可以在注释本身指定事件类型:

@EventListener({ContextStartedEvent.class,ContextRefreshedEvent.class}) 
public void handleContextStart(){
    ...
}

还可以通过condition定义与实际调用特定事件的方法匹配的SpEL表达式的注释属性添加额外的运行时过滤。
例如,如果test事件的属性等于foo:我们的通知程序可以被重写为只被调用

@EventListener(condition ="#blEvent.test =='foo'")
 public void processBlackListEvent(BlackListEvent blEvent){
     //通过notificationAddress ...通知适当的方... 
}

每个SpEL表达式再次评估专用上下文。下表列出了可用于上下文的项目,因此可以将它们用于条件事件处理:

表7.8 事件SpEL可用元数据
这里写图片描述

请注意#root.event,即使您的方法签名实际上是指已发布的任意对象,也可以访问底层事件。
如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名即可返回应发布的事件,如下所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event){
//通过notificationAddress通知有关当事人和
//然后发表ListUpdateEvent …
}
【注】异步侦听器不支持此功能。

这种新方法将为上述方法处理的ListUpdateEvent每个方法发布一个新BlackListEvent的方法。如果您需要发布多个事件,只需返回一个Collection事件。
异步侦听器
如果您希望特定的侦听器异步处理事件,只需重复使用 常规@Async支持:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event){
//BlackListEvent 在单独的线程中处理
}
使用异步事件时,请注意以下限制:
1.如果事件侦听器抛出Exception它,则不会传播给调用者,请AsyncUncaughtExceptionHandler查看更多详细信息。
2.此类事件侦听器无法发送回复。如果您需要作为处理结果发送另一个事件,ApplicationEventPublisher请注意手动发送事件。

Order优先级
如果您需要在另一个之前调用侦听器,只需将@Order 注释添加到方法声明中:

@EventListener 
@Order(42)
 public void processBlackListEvent(BlackListEvent event){
     //通过notificationAddress ... 
}

通用事件
您还可以使用泛型来进一步定义事件的结构。考虑 创建实际实体的类型EntityCreatedEvent<T>在哪里T。您可以创建以下侦听器定义只接收

EntityCreatedEvent了 Person:
@EventListener 
public void onPersonCreated(EntityCreatedEvent <Person> event){
    ...
}

由于类型擦除,只有在被触发的事件解析了事件监听器过滤的通用参数(就是这样class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ })的情况下,它才会起作用 。
在某些情况下,如果所有事件都遵循相同的结构(如上述事件应该是这样),这可能变得相当乏味。在这种情况下,您可以实施ResolvableTypeProvider以引导框架超出运行时环境提供的范围:

public class EntityCreatedEvent <T>  extends ApplicationEvent implements ResolvableTypeProvider {
    public EntityCreatedEvent(T entity){
         super(entity);
    }
    @Override
    public ResolvableType getResolvableType(){
         return ResolvableType.forClassWithGenerics(getClass(),
                ResolvableType.forInstance(的getSource()));
    }
}

【注】这不仅适用于ApplicationEvent您作为事件发送的任何任意对象。

7.15.3方便访问低级资源

为了最佳地使用和理解应用程序上下文,用户应该熟悉Spring的Resource抽象,如 第8章资源中所述。
应用程序上下文是一个ResourceLoader可用于加载Resources 的应用程序上下文。A Resource本质上是JDK类的更多功能丰富的版本java.net.URL,实际上,在适当Resource的情况下包装实例java.net.URL。A Resource可以以透明的方式从几乎任何位置获取低级别的资源,包括从类路径,文件系统位置,任何可用标准URL描述的地方以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源来源是特定的并且适合于实际的应用程序上下文类型。
您可以将部署到应用程序上下文中的bean配置为实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动调用应用程序上下文本身 ResourceLoader。您还可以公开类型的属性,Resource用于访问静态资源; 它们将像任何其他属性一样注入它。您可以将这些Resource属性指定为简单的字符串路径,并依赖PropertyEditor上下文自动注册的特殊JavaBean ,以便Resource在部署bean时将这些文本字符串转换为实际对象。

提供给ApplicationContext构造函数的位置路径或路径实际上是资源字符串,并且以简单的形式对特定的上下文实现进行了适当的处理。ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。您还可以使用具有特殊前缀的位置路径(资源字符串)强制从类路径或URL加载定义,而不管实际的上下文类型如何。

7.15.4 Web应用程序的方便的ApplicationContext实例化

您可以ApplicationContext通过使用例如a来声明式创建实例 ContextLoader。当然,您也可以ApplicationContext通过使用其中一个ApplicationContext实现来以编程方式创建实例。
您可以ApplicationContext使用ContextLoaderListener以下方式注册:

<context-param> 
    <param-name> contextConfigLocation </param-name> 
    <param-value> /WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml </param-value>
 </context-param>
<listener> 
    <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class>
 </listener>

侦听器检查contextConfigLocation参数。如果参数不存在,监听器将使用 /WEB-INF/applicationContext.xml默认值。当参数确实存在时,侦听器通过使用预定义的分隔符(逗号,分号和空格)来分隔字符串,并将值作为应用程序上下文搜索的位置。支持Ant风格的路径模式。 例如/WEB-INF/*Context.xml,所有名称以"Context.xml"结尾的文件,位于"WEB-INF"目录中/WEB-INF/**/*Context.xml,对于”WEB-INF”的任何子目录中的所有这些文件。

7.15.5将Spring ApplicationContext部署为Java EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文和所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext,它只是在Java EE环境中托管,能够访问Java EE服务器设施。RAR部署更适合部署无头WAR文件,实际上是没有任何HTTP入口点的WAR文件,仅用于在Java EE环境中引导Spring ApplicationContext。

RAR部署是不需要HTTP入口点的应用程序上下文的理想选择,而只包括消息端点和计划作业。在这种情况下,Bean可以使用JTA事务管理器和JNDI绑定的JDBC DataSources和JMS ConnectionFactory实例等应用程序服务器资源,还可以通过Spring的标准事务管理和JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件也可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。
查看SpringContextResourceAdapter 有关RAR部署中涉及的配置详细信息的类的javadoc 。

对于Spring ApplicationContext作为Java EE RAR文件的简单部署:将所有应用程序类打包成一个RAR文件,该文件是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR存档的根目录中。添加”META-INF /ra.xml”部署描述符(如SpringContextResourceAdapterjavadoc所示)和相应的Spring XML bean定义文件(通常为”META-INF /applicationContext.xml”),并将生成的RAR文件进入应用服务器的部署目录。

【注】这种RAR部署单位通常是独立的; 它们不会将组件暴露于外界,甚至不会将同一应用程序的其他模块暴露在外。与基于RAR的ApplicationContext的交互通常通过与其他模块共享的JMS目标发生。例如,基于RAR的ApplicationContext也可以安排一些作业,对文件系统(或类似的)中的新文件做出反应。如果需要允许从外部进行同步访问,则可以例如导出RMI端点,这当然可以由同一机器上的其他应用程序模块使用。

7.16 BeanFactory

该BeanFactory规定在IoC容器功能的基础的基础,但它仅是与其他第三方框架集成直接使用,现在在自然界的Spring的大多数用户主要是历史性的。在BeanFactory与相关的接口,如BeanFactoryAware,InitializingBean,DisposableBean,仍然存在于Spring为向后兼容性与大量与Spring集成第三方框架的目的。通常是不能使用更多现代等同物的第三方组件,例如@PostConstruct或@PreDestroy为了与JDK 1.4保持兼容或避免对JSR-250的依赖。

本节提供了额外的背景入之间的差异 BeanFactory以及ApplicationContext如何一个可以直接通过经典单查询访问IoC容器。

7.16.1 BeanFactory或ApplicationContext?

使用一个,ApplicationContext除非你有很好的理由不这样做。
由于ApplicationContext包含所有功能BeanFactory,因此通常建议使用BeanFactory除了少数情况,例如在资源受限的设备上运行的嵌入式应用程序,其中内存消耗可能至关重要,而额外的千字节可能会有所不同。然而,对于大多数典型的企业应用程序和系统,ApplicationContext您将要使用它们。Spring使重使用的BeanPostProcessor扩展点(实现代理等)。如果您只使用一个简单的平台BeanFactory,交易和AOP的支持将不会生效,至少不需要额外的步骤。这种情况可能会令人困惑,因为配置实际上是错误的。
下表列出了提供的功能BeanFactory和 ApplicationContext接口和实现。

表7.9 特征矩阵
这里写图片描述

要明确注册一个bean后处理器与一个BeanFactory实现,你需要编写如下代码:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//使用bean定义填充工厂//现在注册任何所需的BeanPostProcessor实例 
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);//现在开始使用工厂

要BeanFactoryPostProcessor在使用BeanFactory 实现时明确注册一个,您必须编写如下代码:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));//从属性文件中引入一些属性值 
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));//现在实际上做了替换 
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都是不方便的,这是 在绝大多数Spring支持的应用程序中,特别是在使用s和s 时,各种ApplicationContext实现都优先于普通BeanFactory实现的一个原因。这些机制实现了重要的功能,如属性占位符替换和AOP。BeanFactoryPostProcessorBeanPostProcessor

7.16.2粘贴代码和邪恶单例

最好以依赖注入(DI)方式编写大多数应用程序代码,其中该代码由Spring IoC容器提供,在创建时由容器提供自己的依赖关系,并且完全不了解容器。然而,对于有时需要将其他代码绑定在一起的代码的一起粘贴,您有时需要单向(或准单例)样式访问Spring IoC容器。例如,第三方代码可能会尝试直接构造新的对象( Class.forName()样式),而无需将这些对象从Spring IoC容器中取出。如果第三方代码构造的对象是一个小存根或代理,那么然后使用单一样式访问Spring IoC容器来获取一个真正的对象来委托,那么大部分代码(容器中出现的对象)仍然实现了控制反转。因此,大多数代码仍然不知道容器或它是如何访问的,并且与其他代码保持分离,并带来所有的好处。EJB还可以使用这个存根/代理方法来委托从Spring IoC容器检索的普通Java实现对象。虽然Spring IoC容器本身理想地不一定是单例,但是在内存使用或初始化时间(在Spring IoC容器中使用Bean(如HibernateSessionFactory)时)可能不切实际,因为每个bean都使用它自己的非-singleton Spring IoC容器。

查看服务定位器样式中的应用程序上下文有时是访问共享的Spring管理组件(例如在EJB 2.1环境中)或者当您想要在一个WAR文件中共享一个ApplicationContext作为WebApplicationContexts的父应用程序的唯一选项。在这种情况下,您应该查看使用ContextSingletonBeanFactoryLocator 此Spring团队博客条目中描述的实用程序类 定位器 。

猜你喜欢

转载自blog.csdn.net/sunrainamazing/article/details/77341492