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> <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.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 MVC的DispatcherServlet像往常一样- >
<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定义 - >
<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保持在最低限度。
@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> known.spammer@example.org </value>
<value> known.hacker@example.org </value>
<value> john.doe@example.org </value>
</list>
</property> </bean>
<bean id = "blackListNotifier" class = "example.BlackListNotifier" >
<property name = "notificationAddress" value = "blacklist@example.org" /> </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团队博客条目中描述的实用程序类 定位器 。