作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
CSDN:https://blog.csdn.net/liyong1028826685
微信公众号:

[wshop_paid post_id=”2216″ show_buy_btn=”true”]
1.11 使用JSR330标准注解
Spring3.0开始,Spring提供对JSR-330标准注解的支持(依赖注入)。这些注解和Spring注解一样的方式被扫描。去使用它们,你需要在类路径中依赖相关的jar包。
如果你使用Maven,javax.inject组件在标准的Maven仓库中(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)是有效的。你可以添加下面的依赖到你的pom.xml:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
1.11.1 @Inject
和@Named
依赖注入
你可以使用@javax.inject.Inject
替代@Autowired
,类似下面例子:
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
类似@Autowired
注解,你可以在字段级别、方法级别、构造参数级别使用@Inject
。此外,你可以将注入点声明为Provider
,从而允许按需访问范围较短的bean,或者通过提供程序get()
调用对其他bean进行延迟访问。下面例子提供前面子例子变体:
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
如果你喜欢为应该被注入的依赖使用限定名称,你应该使用@Named
注解,类似下面的例子显示:
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
类似@Autowired
,@Inject
也能使用java.util.Optional
或@Nullable
。这在这里更适用,因为@Inject
没有required
属性。下面两个例子展示怎样去使用@Inject
和@Nullable
:
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
参考代码:
com.liyong.ioccontainer.starter.XmlInjectAndNamedIocContainer
1.11.2 @Named
和@ManagedBean
:等价于@Component
代替@Component
,你可以使用@javax.inject.Named
或javax.annotation.ManagedBean
,类似下面的例子:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
在没有指定组件名称的情况下使用@Component
是非常常见的。@Named
可以类似的方式使用,类似下面例子:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
使用@Named
或@ManagedBean
时,可以使用与使用Spring注解完全相同的方式来使用组件件扫描。类似下面例子展示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
与
@Component
相比,JSR330@Named
和JSR-250ManagedBean
注解是不能被组合的。你应该使用Spring的构造型模型来构建自定义组件注解。参考代码:
com.liyong.ioccontainer.starter.XmlNamedAndManagerBeanIocContainer
1.11.3 JSR-330标准注解的限制
当你使用标准注解时,你应该知道一些重要特性不可用,类似下面表格展示:
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject 没有 required 属性. 能够使用在Java8的 Optional 。 |
@Component | @Named / @ManagedBean | JSR-330不提供可组合的模型,仅提供一种识别命名组件的方法。 |
@Scope("singleton") | @Singleton | JSR-330 默认作用域是 Spring的 prototype . 然而, 为了保存与Spring的一般默认一致性, 在Spring容器中JSR-330bean默认被声明为一个singleton 。为了使用其他的作用域,你应该使用Spring的@Scope 注解。javax.inject也提供一个@Scope 注解。不过,此仅用于创建自己的注解。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier 是构建自定义限定符元注解. 具体的 String 限定通过javax.inject.Named 关联。 (类似Spring的 @Qualifier 值) |
@Value | – | no equivalent |
@Required | – | no equivalent |
@Lazy | – | no equivalent |
ObjectFactory | Provider | javax.inject.Provider 是Spring的ObjectFactory的直接替代,仅仅有一个get() 方法名,它也可以与Spring的@Autowired 结合使用,也可以与无注释的构造函数和setter方法结合使用。 |
1.12 基于Java容器配置
这个部分涵盖了在你的Java代码中怎样去使用注解配置Spring容器。它包含下面的主题:
- 基本概念:
@Bean
和@Configuration
- 使用
AnnotationConfigApplicationContext
实例化Spring容器 - 使用
@Bean
注解 - 使用
@Configuration
注解 - 基于Java的配置组合
- Bean定义配置文件
PropertySource
抽象- 使用
@PropertySource
- 占位符解析
1.12.1 基本概念:@Bean和@Configuration
Spring的新Java配置支持中的主要构件是@Configuration
注解的类和@Bean
注解的方法。
@Bean
注解使用表示一个方法实例、配置和实例化Spring IoC容器管理的新对象。这些类似Spring的 XML配置,@Bean
注解扮演了元素相同的角色。你可以使用@Bean
注解方法替换任何Spring中@Component
组件。然而,它们最常与@Configuration一起使用。注解类@Configuration
注解表示它的主要目的是bean定义的源。此外,@Configuration
类允许通过调用同一类中的其他@Bean
方法来定义Bean间的依赖关系。下面最简单的@Configuration
例子:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的AppConfig类是与下面的Spring XML定义相等:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
完整的
@Configuration
与“精简”@Bean
模式?当
@Bean
方法在类中被声明时,这些类没有被注解@Configuration
,它们被称为以“精简”模式进行处理。在@Component
或在简单的旧类中声明的Bean方法被认为是“精简版”,其中包含类的主要目的不同,而@Bean
方法在那里具有某种优势。例如,服务组件在每个可应用的组件类上通过增加一个附加的@Bean方法暴露管理视图到容器。在这种场景下,@Bean
方法是一种通用的工厂方法机制。不像完整的
@Configuration
,精简@Bean
方法不能声明bean之间的依赖关系。相反,它们对其包含的组件的内部状态以及可能声明的参数(可选)进行操作。因此,此类@Bean
方法不应调用其他@Bean
方法。每个这样的方法实际上只是一个特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是在运行时不需要应用CGLIB子类,所以在类设计方面没有限制(也就是,包含类可能是final)。在常见的场景中,
@Bean
方法是在@Configuration类中声明的,确保总是使用完整模式,因此交叉方法引用被重定向到容器的生命周期管理。这可以防止通过常规Java调用意外调用相同的@Bean
方法,这有助于减少在lite模式下操作时难以跟踪的细微错误。备注:在@Configuration
类中调用其他@Bean
方法会定向到容器的生命周期管理。
@Bean
和@Configuration
在下面的部分深入讨论。首先,我们会覆盖基于Java注解创建Spring容器的各种方式。
1.12.2 通过使用AnnotationConfigApplicationContext
初始化Spring容器
以下各节介绍了Spring 3.0中引入的Spring的AnnotationConfigApplicationContext
。这个通用的ApplicationContext实现不仅能够接收@Configuration
类作为输入,还能够接收普通的@Component
类和使用JSR-330元数据注解的类
当@Configuration
类作为输入被提供,@Configuration
类自身作为一个bean定义被注册并且所有在类中被@Bean
声明的方法也作为bean的定义被注册到容器。
当提供@Component
和JSR-330类时,它们被注册为bean定义,并假设DI元数据(如@Autowired
或@Inject
)在这些类中使用。
简单构造
与实例化ClassPathXmlApplicationContext
时将Spring XML文件用作输入的方式几乎相同,实例化AnnotationConfigApplicationContext
时可以将@Configuration
类用作输入。如下面的示例所示,这允许完全不使用XML来使用Spring容器:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
像前面提到的,AnnotationConfigApplicationContext
不限于与@Configuration一起使用。任何@Component
或JSR-330注解的类作为输入构造是被支持,类似下面例子:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前面的例子假设MyServiceImpl
、Dependency1
、和Dependency2
使用Spring依赖注入注解例如:@Autowired
。
通过使用register(Class<?>…)
编程式的构建容器
你可以通过使用无参构造函数实例化AnnotationConfigApplicationContext
并且通过使用register()
方法配置。当编程地构建AnnotationConfigApplicationContext
时,这个方法是特别地有用。下面类中展示怎样去使用:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
通过scan(String…)
扫描激活组件
扫描激活组件,你可以注解你的@Configuration
类:
@Configuration
@ComponentScan(basePackages = "com.acme") //1
public class AppConfig {
...
}
- 这个注解激活组件扫描
经验丰富的Spring用户可能熟悉Spring context 中等效的XML声明:命名空间,类似下面例子:
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
在前面的例子中,com.acm
包被扫描去查找被注解@Component
的类,并且这些类作为Spring bean定义被注册在容器中。AnnotationConfigApplicationContext
暴露scan(String…)
方法提供相同的组件扫描功能,类似下面的例子:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
记住
@Configuration
类是使用@Component
元注解的,因此它们是组件扫描的候选者。在前面的例子中,假设AppConfig被声明在com.acme
包中(或任何com.acme
子包),它会在调用scan()
期间被选出来。在refresh()
后,其所有@Bean
方法都将被处理并注册为容器内的Bean定义。
通过AnnotationConfigWebApplicationContext
支持Web应用程序
AnnotationConfigWebApplicationContext
提供了AnnotationConfigApplicationContext
的WebApplicationContext
变体。当配置Spring的ContextLoaderListener
servlet监听器、Spring MVC DispatcherServlet
等等的时候,你可以使用这个实现,下面的web.xml
片段配置一个典型的Spring MVC web应用程序(注意:contextClass
使用context-param
和init-param
):
<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>dispatcher</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>
1.12.3 使用@Bean注解
@Bean
是一个方法级别注解并且是XML 元素的直接类比物。这个注解支持一些通过提供的属性,例如: init-method 、destroy-method 、 autowiring
你可以在@Configuration
和@Component
注解的类上使用@Bean
注解。
声明Bean
去声明一个bean,你可以为一个方法注解@Bean
。你使用这个方法在ApplicationContext
中去注册一个方法返回值指定类型的bean定义。默认情况,bean名称是相同方法名称。下面的例子显示一个@Bean
方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
前面的配置与下面的Spring XML完全等效:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两者声明都使一个名为transferService
的bean在ApplicationContext
中可用,并绑定一个TransferServiceImpl
类型的对象实例,类似下面文本图片显示:
transferService -> com.acme.TransferServiceImpl
你也可以声明你的@Bean
方法为一个接口(或者基类)返回类型,类似下面例子展示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,这将高级类型预测的可见性限制为指定的接口类型(TransferService
)。然而,全类型(TransferServiceImpl
)在容器只有一个,受影响的单例bean被初始化。非懒加载单例bean根据它们的声明顺序获取已经初始化实例,当其他组件尝试通过非声明类型去匹配时,你可能看到不同类型匹配结果依赖(例如,@Autowired
TransferServiceImpl
,仅在实例化transferService
bean之后才解析)。
如果你始终通过声明的服务接口引用你的类型,那么你的
@Bean
返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或由其实现类型潜在引用的组件,声明最具体的返回类型(至少与引用你的bean的注入点所要求的具体类型一样)更为安全。 备注:意思是如果通过实现类型引用组件时,在定义@Bean
方法时返回类型要是具体的实现类型。
Bean依赖
一个被@Bean
注解的方法可以有任意个参数,这些参数描述了需要的依赖去构建bean。例如,如果我们的TransferService
需要一个AccountRepository
,我们可以通过方法参数来实现这种依赖关系,类似下面例子:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入几乎相同。查看更详细相关部分。
接受生命周期回调
任何被声明@Bean
注解的类支持普通的生命周期回调并且可以使用 JSR-250的@PostConstruct
和@PreDestroy注解。查看JSR-250注解更多的详情。
常规的Spring生命周期回调是被完全的支持。如果bean实现InitializingBean
、DisposableBean
或Lifecycle
,它们各自的方法被容器回调。
标准的set*Aware
接口(例如,BeanFactoryAware,、BeanNameAware、MessageSourceAware,、ApplicationContextAware等等)也是完全的被支持。
@Bean
注解支持任意的初始化和销毁回调方法,非常类似Spring 中XML元素的init-method和destroy-method
属性,类似下面例子显示:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
默认情况下,这些bean通过Java配置定义它们有公共的
close
或shutdown
方法自定地加入到销毁回调中。如果你有一个公共的close
或shutdown
方法并且当容器被关闭时不想被回调,你应该增加@Bean(destroyMethod="")
到你的bean定义中去禁止默认的(inferred
)模式。默认情况下,你可能要对通过JNDI获取的资源执行此操作,因为其生命周期在应用程序外部进行管理的。特别是,要确保始终为数据源执行此操作,因为在Java EE应用服务器上这是有问题的。
下面的例子展示怎样去阻止DataSource自动销毁回调。
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
另外,对于
@Bean
方法,通常使用编程式地JNDI查找,方法是使用Spring的JndiTemplate
或JndiLocatorDelegate
帮助器,或者直接使用JNDIInitialContext
用法,而不使用JndiObjectFactoryBean
变体(这将迫使你将返回类型声明为FactoryBean
类型,而不是实际的类型。目标类型,因此很难在打算引用此处提供的资源的其他@Bean方法中用于交叉引用调用。
对于前面示例中的BeanOne,它等效于在构造期间只接调用init()
方法,类似下面例子:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
当你直接地工作在Java中,你可以对你的对象做任何事而不需要依赖容器生命周期。
指定Bean作用域
Spring包括@Scope
注解,因此你可以使用bean的作用域。
使用@Scope
注解
你可以指定你的bean定义通过@Bean
注解同时也可以指定一个作用域。你可以使用在Bean作用域部分中任何标准的作用域指定。
默认作用域是singleton
,但是你可以覆盖这个通过@Scope
注解,类似下面例子显示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope
和scoped-proxy
Spring提供通过作用域代理处理作用域依赖的便捷方式。当使用XML配置< aop:scoped-proxy/>
是最简单创建一个代理方式。使用@Scope
注解在Java中配置bean,可以通过proxyMode
属性提供同等的支持。默认是没有代理(ScopedProxyMode.NO
),但是你可以指定ScopedProxyMode.TARGET_CLASS
或ScopedProxyMode.INTERFACES
如果你使用Java,将XML参考文档中的作用域代理示例(请参阅作用域代理)移植到我们的@Bean
,它类似于以下内容:
// Http session作用域bean 暴露为代理
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// 引用UserPreferences
service.setUserPreferences(userPreferences());
return service;
}
自定义Bean名称
默认情况下,配置类使用@Bean
方法的名称作为bean名称。这个功能可以被覆盖,通过@Bean
的name属性,类似下面例子:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean别名
类似在bean的命名中讨论,有时候给一个简单bean多个名称,也称为Bean别名。@Bean
注解的name属性接受一个字符串数组为这个别名。下面例子展示怎样去设置bean的别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Bean描述
有时候,有助于提供有关bean的更详细的文本描述。当这些bean被暴露为监控目的时,是非常有用的。
去增加一个描述到@Bean
,你可以使用 @Description
注解,类似下面例子展示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4 使用@Configuration
注解
@Configuration
是一个类级别注解,它表示对象是bean定义的源。@Configuration
类声明bean通过公共@Bean
注解方法。在@Configuration
类上调用@Bean
方法能被使用去定义Bean之间依赖关系。查看基础概念:@Bean和@Configuration
bean间注入的依赖关系
当Bean彼此依赖时,表达这种依赖就像让一个bean方法调用另一个一样简单,类似下面例子展示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的例子中,beanOne
通过构造函数注入引用beanTwo
。
仅仅当
@Bean
方法被声明在@Configuration
类中时,这方法声明bean间的依赖关系有效。你不能通过使用普通的@Component
声明bean间的依赖关系。
查找方法注入
如前所述,查找方法注入是一个高级特性,它很少地使用。在一些单例作用域bean依赖原型作用域bean场景,它是非常有用的。使用Java为配置类型实现这个模式提供一种自然方法。下面例子展示怎样去查找方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
通过使用Java配置,在该子类中,你可以创建一个CommandManager
子类,抽象的createCommand()
方法将被覆盖,以使其查找新的(原型)command对象。通过使用Java配置。下面例子展示怎样去做:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
有关基于Java的配置在内部如何工作的更多信息
考虑下面例子,它展示了一个@Bean
注解的方法被调用两次。
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao(
)在clientService1()
被调用一次并且在clientService2()
也调用一次。因为这个方法创建一个新的ClientDaoImpl
实例并且返回它,你通常希望有两个实例(每个服务一个)。那肯定是有问题的:在Spring中,默认情况实例化bean作用域是singleton
。这就是神奇之处:所有@Configuration
类在运行时是CGLIB的子类。在子类中,在调用父方法创建实例之前,子类方法首先检查容器缓存bean。
这种行为根据你的bean作用域可能不同。我们在这里讨论的单例bean。
在Spring3.2以后,不在需要添加CGLIB到你的类路径下,因为CGLIB类已经被重新打包在
org.springframework.cglib
下并且直接地包含在spring-core
jar中。由于CGLIB在启动时会动态添加功能,因此存在一些限制。特别地,配置类不能是
final
。然而,在Spring4.3以后,任何构造函数在配置类上是被允许的,包括@Autowired
或没有默认参数的构造函数申明默认注入的使用。如果你想避免CGLIB的限制,考虑在你非
@Configuration
类(例如,一个普通的@Component
替换)上声明你的@Bean
方法。在@Bean
方法之间跨方法调用不会被拦截,因此你必在须构造函数或方法级别只依赖需要注入的。代码示例:
com.liyong.ioccontainer.starter.BaseJavaConfigIocContainer
1.12.5 编写基于Java的配置
Spring的基于Java配置特性允许你编写注解,这样可以降低你的配置复杂性。
使用@Import注解
类似元素被使用在Spring XML文件中去帮助模块化配置,@Import
注解允许去其他配置类中加载@Bean
定义,类似下面的例子展示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
当初始化上下文时,不需要去指定ConfigA.class
和ConfigB.class
,仅仅需要去显示地提供ConfigB
,类似下面例子展示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这个方式简化容器的实例化,仅仅一个类需要去处理,而不是在构造期间潜在地记住大量的@Configuration
类。
在Spring4.2以后,
@Import
也支持引用常规的组件类,类似AnnotationConfigApplicationContext.register
方法。如果你想避免组件扫描,这是非常有用地,通过使用一些配置类作为入口点显示定义所有组件。
在被导入的@Bean
定义中注入依赖
前面的示例有效,但过于简单。在大多数时间场景中,Bean在配置类之间相互依赖。当使用XML时,这不是问题,因为不涉及编译器并且你可以声明 ref="someBean"
并信任Spring在容器初始化期间进行处理。当使用@Configuration
类时,Java编译器在配置模型上约束,因为对其他bean引用必须是有效的Java语法。
幸好地,解决这个问题时非常简单的。像我们以前讨论过的,@Bean
方法可以有任意数量的参数,这些参数描述了bean的依赖。考虑下面更真实的场景对于这些@Configuration
类,每一个bean在其他类中被定义:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
这里有其他的方式去实现相同的结果。记住@Configuration
类最终只是容器中的另一个bean:这意味着它们可以利用@Autowired
和@Value
注入以及其他与其他bean相同的特性。
确保以这种方式注入的依赖项只是最简单的一种。
@Configuration
类是在上下文初始化期间非常早地处理的,并且强制以这种方式注入依赖项可能导致意外的早期初始化。如上例所示,尽可能使用基于参数的注入。另外,通过
@Bean
定义BeanPostProcessor
和BeanFactoryPostProcessor时要特别小心。这些应该通常地被声明为static
@Bean
方法,不要触发它们包含的配置类初始化。除此之外,@Autowired
和@Value
可能在配置类上不能工作,因为它作为bean实例被创建早于AutowiredAnnotationBeanPostProcessor
。
下面例子展示一个bean怎样被装配到其他bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
在
@Configuration
类中构造方法注入仅支持在Spring4.3以后。注意:如果目标bean定义仅有一个构造函数,那么不需要去指定@Autowired
。代码示例:
com.liyong.ioccontainer.starter.BeanAndConfigurationImportContainer
全限定导入bean以更容易导航
在前面的场景中,使用@Autowired
工作很好并且提供期望的模块化,但是确定自动装配 bean的定义是在哪里声明的仍然有些模棱两可。例如,作为一个开发者看到ServiceConfig
,怎样确切的知道@Autowired
AccountRepository
bean在哪里定义的?它在代码中不是明确的,这可能很好。记住, Spring Tools 为Eclipse提供可以渲染图形的工具,这些图形显示了所有的对象是怎样连接的,这可能是你需要的。你的Java IDE能更容易地找到所有声明和使用AccountRepository
类型并且快速地显示@Bean
方法的路径以及返回类型。
如果这种歧义是不可接受的,并且你希望从IDE内部直接从一个@Configuration
类导航到另一个@Configuration
类,请考虑自动装配配置类本身。下面例子显示怎样去做:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在前面情况中,AccountRepository
完整的显示定义。但是,ServiceConfig
现在紧紧的耦合到RepositoryConfig
。这就是需要权衡的。通过使用基于接口或基于抽象类的@Configuration
类,可以在某种程度上缓解这种紧密耦合。考虑下面例子:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
现在,ServiceConfig
与具体的DefaultRepositoryConfig
松散耦合,并且内建IDE工具仍然非常有用:你可以容易地获取RepositoryConfig
实现的层级。通过这种方式,导航@Configuration
类及其依赖项与导航基于接口的代码的通常过程没有什么不同。
如果你想流畅启动创建这些bean的顺序,考虑声明它们为
@Lazy
(用于首次访问而不是启动时创建)或像@DependsOn
其他bean(确保其他指定bean在当前bean被创建之前 ,而不受后者直接依赖关系的影响)。
条件地包含@Configuration
类或@Bean
方法
根据某些系统状态,有条件地启用或禁用完整的@Configuration
类甚至单个@Bean
方法,通常很有用。说明:可以启用/禁止@Configuration
类或者@Configuration
类中的@Bean
方法。一个常用的例子是去使用@Profile
注解去激活bean,仅仅当在Spring中的Environment
指定的profile
被激活时(查看Bean定义 Profiles 详情)。
@Profile
注解通过使用一个非常灵活的注解叫做 @Conditional
实现的。@Conditional
注解指示在注册@Bean
之前应咨询特定org.springframework.context.annotation.Condition
实现。
Condition
接口的实现接口提供一个matches(…)
方法,它会返回true或false。例如,下面的清单显示Condition
对@Profile
真实实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
查看 @Conditional
文档更多详细。
结合Java和XML配置
Spring的@Configuration
类不能100%
的完成替换Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在使用XML方便或有必要的情况下,你可以选择:使用“ClassPathXmlApplicationContext
以“ XML中心”方式实例化容器,或通过使用AnnotationConfigApplicationContext
和“ Java配置中心”方式实例化容器。 @ImportResource
注解可根据需要导入XML。
以XML为中心的@Configuration
类的使用
从XML引导Spring容器并以特别的方式包含@Configuration
类可能更好。例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration
类并且现有XML文件中将它们包含在内变得更加容易。在本节的后面,我们将介绍在这种“以XML为中心”的情况下使用@Configuration
类的选项。
- 声明
@Configuration
类似普通的Spring元素 记住@Configuration
类最终在容器中是bean定义。在这一系列例子中,我们创建@Configuration
类命名为AppConfig
并且包含它在system-test-config.xml中类似定义。因为<context:annotation-config/>
已经打开,容器在AppConfig
中识别@Configuration
注解和处理@Bean方法声明。 下面例子显示在Java中普通的配置:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
下面例子显示system-test-config.xml
文件一部分:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="{jdbc.url}"/>
<property name="username" value="{jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
下面栗例子显示可能的jdbc.properties
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在
system-test-config.xml
文件中,AppConfig 没有声明id元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他bean引用它,而且不太可能通过名称显式地从容器中获取它。同样,DataSource
bean只能按类型自动装配,因此也不严格要求显式beanid
。
- 使用
<context:component-scan/>
选择@Configuration
类 因为@Configuration
元注解是@Component
,被@Configuration
注解的类是组件自动地扫描的候选者。使用相同的场景在前面例子已经描述,我们可以重定义system-test-config.xml
去利用组件扫描。注意,在这个场景中,我们不需要显示的声明<context:annotation-config/>
,因为<context:component-scan/>
激活相同的功能。 下面例子显示修改后的system-test-config.xml
文件:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="{jdbc.url}"/>
<property name="username" value="{jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
以类为中心的XML与@ImportResource
的结合使用
在应用中,@Configuration
类是配置容器的主要机制,但是仍然可能需要至少使用一些XML。在这个场景中,你可以使用@ImportResource
和定义你需要的XML。这样做实现了“以Java为中心”的方法来配置容器,并使XML保持在最低限度。下面例子展示了怎样去使用@ImportResource
注解去实现“以Java为中心”配置并在需要的时候使用XML:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("{jdbc.url}")
private String url;
@Value("{jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
代码示例:
com.liyong.ioccontainer.starter.BaseJavaConfigAndXmlIocContainer
1.13 Environment抽象
Environment
接口是在容器中抽象集成,应用环境包括两个重要的方面:profiles 和 properties。配置文件是一个命名的bean定义逻辑组,只有在给定的配置文件处于激活状态时才在容器中注册。可以将bean分配给配置文件,不管它是用XML定义的还是用注解定义的。与profile
文件相关的环境对象的角色是确定哪些profile
文件(如果有的话)当前是激活的,以及哪些profile
文件(如果有的话)在默认情况下应该是激活的。
Properties
几乎在所有应用中扮演一个重要的角色并且可能来自于多个源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、Properties对象、Map对象等等。Environment
对象的角色与properties
关联去提供给用户一个便利的服务接口去配置属性源和解析属性。
1.13.1 Bean定义Profiles
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目录。我们的dataSource
bean看起来类似下面清单:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的流逝,Spring用户已经设计出多种方法来完成此任务,通常依赖系统环境变量和包含$ {placeholder}
占位符的XML 语句的组合,这些语句根据值解析为正确的配置文件路径环境变量。Bean定义配置文件是一项核心容器功能,可解决此问题。
如果我们概括前面环境特定的bean定义示例中所示的用例,我们最终需要在特定上下文中注册特定的bean定义,而不是在其他上下文中注册。可以这样说,你希望在情形A中注册bean定义的某个配置文件,而在情形B中注册另一个配置文件。我们开始更新配置以反映这种需求。
使用@Profile
@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/JndiLocatorDelegate
帮助类或直接使用前面展示的JNDIInitialContext
,而不是使用JndiObjectFactoryBean
变体,这将迫使你将返回类型声明为FactoryBean
类型。
配置文件字符串可能包含一个简单的配置名称(例如,production
)或一个配置表达式。一个配置表达式允许更复杂的配置逻辑去表达(例如,production & us-east
),下面的操作符在profile
表达式中是被支持的:
- !:逻辑非
- &:逻辑与
- |:逻辑或
你不能混合
&
和!
操作符而不使用括号。例如,production & us-east | eu-central
是无效表达式。它必须被表达类似production & (us-east | eu-central)
。
你可以使用@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") //1
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") //2
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
standaloneDataSource
方法仅仅在development
配置中有效jndiDataSource
方法仅仅在production
配置中有效
对于
@Bean
方法上的@Profile
,可以应用一个特殊的场景:在重载相同Java方法名的@Bean
方法(类似于构造函数重载)的情况下,需要一致地在所有重载方法上声明@Profile
条件。如果条件不一致,则只有重载方法中第一个声明的条件重要。因此,@Profile
不能被使用去选择具有特定参数签名重载方法。在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析方法。如果你想去定义不同配置条件的bean,使用不同的Java方法名称,通过使用
@Bean
name属性指向相同名称的bean,类似前面展示例子。如果参数前面都相同(例如,所有的变体有无参构造函数),这是在一个有效的Java类中表示这种安排的唯一方法(因为只能有一个具有特定名称和参数签名的方法)。
XML bean定义配置文件
XML对应项是元素的profile
属性。我们前面的相同配置能被重写在两个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 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="...">
<!-- other bean definitions -->
<beans profile="development">
<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">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd
已被限制为仅允许这些元素作为文件中的最后一个。这应该有助于提供灵活性,而不会在XML文件中引起混乱。
XML对应项不支持前面描述的配置文件表达式:然而,它可能通过!
操作符否定一个配置文件。它也可能通过嵌入配置文件应用逻辑and
,类型下面例子显示:
<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="...">
<!-- other bean definitions -->
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
在前面的例子中,如果production
和us-east
配置都被激活,则dataSource
bean被暴露。
激活profile
现在我们已经更新了配置文件,我们仍然需要指示Spring哪一个配置文件激活。如果我们已经启动了我们的应用程序,我们将看到一个NoSuchBeanDefinitionException
抛出,因为容器不能找到名称为dataSource
的bean。
可以通过多种方式来激活配置文件,但最直接的方法是可通过ApplicationContext
获得的Environment
API以编程方式进行配置。下面例子展示怎样去做:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,你也可以声明式地激活配置文件通过spring.profiles.active
属性,也可通过系统环境变量、JVM属性、在web.xml中servlet上下文参数,甚至作为JNDI中的条目(查看PropertySource
抽象)。在集成测试中,激活配置文件可以通过使用@ActiveProfiles
声明在spring-test
模块(查看上下文配置通过环境配置文件)。
请注意,配置文件不是非此即彼
的命题。你可以一次性激活多个配置文件。编程式地,你可以提供多个配置文件名称给setActiveProfiles()
方法,它可以接受String…
可变参数。下面例子激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明式地,spring.profiles.active
可以接收配置名称逗号分隔列表,类似下面例子展示:
-Dspring.profiles.active="profile1,profile2"
默认profile
默认配置文件表示默认情况下启用的配置文件。考虑下面例子:
@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();
}
}
如果没有profile
被激活,dataSource
实例被创建。你可以看到这种方式提供一个默认的定义为一个或多个bean。如果任何profile
被激活,这个默认profile不被使用。
你可以通过在Environment
上使用setDefaultProfiles()
改变默认profile
名称或者,声明式地,通过使用spring.profiles.default
属性。
1.13.2 PropertySource
抽象
Spring的Environment
抽象提供了可配置属性源层次结构上的搜索操作。考虑下面清单:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在前面的片段中,我们看到一个高级别的询问Spring的方式,是否my-property
属性在当前环境中被定义:去回答这个问题,Environment
对象执行搜索PropertySource
集合对象。PropertySource
是一个简单key-value
对源的抽象,并且Spring的StandardEnvironment
被配置两个PropertySource
对象,一个描述JVM系统(System.getProperties()
)属性集合另一个描述系统环境变量集合(System.getenv()
)。
这些默认的属性源适用
StandardEnvironment
,为在独立的应用中使用。StandardServletEnvironment
是通过附加默认属性源填充,包括servlet配置和servlet上下文参数。它可以选择启用JndiPropertySource
。查看javadock详情。
具体地说,当你使用StandardEnvironment
时,如果my-property
系统属性或my-property
环境变量在运行时被描述,调用env.containsProperty("my-property")
方法将返回true。
执行的搜索是分层的,默认情况,系统属性优先级高于环境变量。因此,如果
my-property
属性在两个地方被设置,在调用env.getProperty("my-property")
时,系统属性值将被返回。请注意,属性值不会合并,而是会被前面的值完全覆盖。对于通用的
StandardServletEnvironment
,完整的层次结构如下,优先级最高的条目位于顶部:
1.ServletConfig
参数(如果使用的-例如,如果是DispatcherServlet
上下文)
2.ServletContext
参数(web.xml 上下文参数)3.JNDI 环境变量(
java:comp/env/
)4.JVM系统参数(-D 命令行参数)
5.JVM系统变量(操作系统环境变量)
最重要地,整个机制是可配置的。也许你有一个自定义的属性源,你想整合到此搜索中。这样做,实现和实例化你的PropertySource
并且添加到当前的Environment
PropertySources
集合中。下面例子展示怎样去做:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在前面的代码中,MyPropertySource
被添加到最高索引优先级中。如果它包含my-property
属性,则会检测到并返回该属性,从而支持任何其他PropertySource
中的my-property
属性。MutablePropertySources
API暴露了一些方法,这些方法允许去精确操作属性源集合。
1.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/${my.placeholder:default/path}/app.properties")
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
被抛出。
根据Java8约定,
@PropertySource
注解是可以重复的。然而,所有的@PropertySource
注解需要在相同等级被声明,要么直接地在配置类上,要么作为元数据注解在相同自定义注解中。不建议将直接注释和元注释混合使用,因为直接注释会有效地覆盖元注释。参考代码:
com.liyong.ioccontainer.starter.EnvironmentIocContainer
1.13.4 语句中的占位符解析
在以前,元素中占位符的值只能根据JVM系统属性或环境变量来解析。现在情况已经不同了。因为Environment
抽象已经集成到容器,很容易通过它来路由占位符的解析。这意味着你可以按照自己喜欢的任何方式配置解析过程。你可以更改搜索系统属性和环境变量的优先级,也可以完全删除它们。你还可以适当地将你自己的属性源添加到组合中。
具体地说,不论在何处定义customer属性,只要在环境中可用,以下语句就可以工作:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14 注册LoadTimeWeaver
Spring使用LoadTimeWeaver
在将类加载到Java虚拟机(JVM)中时对其进行动态转换。
去激活 load-time
编织,你可以添加@EnableLoadTimeWeaving
在你的@Configuration
类其中之一,类似下面例子展示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或者,对应XML配置,你可以使用context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
</beans>
为ApplicationContext
配置一次,任何在ApplicationContext
的bean实现LoadTimeWeaverAware
接口,从而接受一个load-time
编织实例的引用。与Spring的JPA支持结合使用时,该功能特别有用,因为在进行JPA类转换时可能需要进行加载时编织。有关更多详细信息,请查阅LocalContainerEntityManagerFactoryBean javadoc。有关AspectJ加载时编织的更多信息,查看在Spring框架中的Aspectj加载时编织。
1.15 ApplicationContext
的其它功能
像在这个章节讨论的,org.springframework.beans.factory
包提供基本的管理和操作bean的功能,包含编程式方式。org.springframework.context
包添加ApplicationContext
接口,它拓展BeanFactory
接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。许多人使用ApplicationContext
以完全声明的方式,甚至没有以编程方式创建它,但是取而代之的是依靠诸如ContextLoader
之类的支持类来自动实例化ApplicationContext
,这是Java EE Web应用程序正常启动过程的一部分。
为了以更加面向框架的方式增强BeanFactory
的功能,上下文包还提供以下功能:
- 通过
MessageSource
接口获取,在i18n
获取消息 - 通过
ResourceLoader
接口,获取资源,例如URL和文件 - 通过使用
ApplicationEventPublisher
接口,将事件发布到实现ApplicationListener
接口的bean - 加载多个(分层)上下文,通过
HierarchicalBeanFactory
接口将每个上下文集中在一个特定层上,例如应用程序的Web层
1.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
被加载时,它会自动地在上下文中搜索bean定义为MessageSource
的类。这个bean必须有个messageSource
名字。如果bean没有找到,所有调用前面的方法被代理到消息源。如果没有找到消息源,ApplicationContext
尝试去父容器查找相同名称的bean。如果找到,则使用它作为MessageSource
。如果ApplicationContext
没有找到任何消息源,一个空的DelegatingMessageSource
被实例化去接受前面定义的方法调用。
Spring提供两个MessageSource
实现,ResourceBundleMessageSource
和StaticMessageSource
。两者都实现HierarchicalMessageSource
以便进行嵌套消息传递。StaticMessageSource
很少被使用,但是提供编程式的方式去添加消息源。下面例子展示:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
这个例子假设在你的类路径定义有三个资源包分别是format
、exceptions
和windows
。任何解析消息的请求都以jdk标准的方式处理,即通过ResourceBundle
对象解析消息。为了这个例子的目的,假设上面两个资源包文件内容分布如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下面例子展示一个程序去执行MessageSource
功能。记住,所有的ApplicationContext
实现也是MessageSources
实现并且能够转换为MessageSource
接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
上面的程序输出结果是:
Alligators rock!
总而言之,MessageSource
被定义在叫做beans.xml
的文件中,它存在于你的类路径root下。messageSource
bean定义通过它的basenames属性引用一些资源包。列表中传递给basenames属性的三个文件在类路径的根目录下以文件形式存在并且分别称为format.properties
,exceptions.properties
和windows.properties
。
下一个示例显示传递给消息查找的参数。这些参数被转换为String对象并且在查找消息中插入占位符。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.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", Locale.ENGLISH);
System.out.println(message);
}
}
调用execute()
方法输出结果:
The userDao argument is required.
关于国际化(i18n
), Spring的各种MessageSource
实现遵循与标准JDK ResourceBundle
相同的语言环境解析和后备规则。简而言之,并继续前面定义的示例messageSource
,如果要针对英国(en-GB
)语言环境解析消息,则可以分别创建名为format_en_GB.properties
,exceptions_en_GB.properties
和windows_en_GB.properties
的文件。
通常,语言环境解析由应用程序的周围环境管理。在以下示例中,手动指定了针对其解析(英国)消息的语言环境:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
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(message);
}
上面的程序输出结果如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
你也可以使用MessageSourceAware
接口去获取一个引用任意已经被定义的MessageSource
。任何在ApplicationContext
中定义的bean,当MessageSource
bean被创建且被配置时,实现MessageSourceAware
接口将被注入应用上下文的MessageSource
。
作为
ResourceBundleMessageSource
的替代方法,Spring提供一个ReloadableResourceBundleMessageSource
类。这个变体支持相同包文件格式,但是比基于标准JDK的ResourceBundleMessageSource
实现更灵活。特别是,它允许从任何Spring资源为主读取文件并且支持包属性文件的热加载(同时在它们之间有效地缓存它们)。查看ReloadableResourceBundleMessageSource
javadoc详细信息。代码示例:
com.liyong.ioccontainer.starter.MessageSourceIocContainer
1.15.2 标准和自定义事件
在ApplicationContext
中事件处理是通过ApplicationEvent
和ApplicationListener
接口提供的。如果bean实现ApplicationListener
接口并且部署到上下文中,则每次将ApplicationEvent
发布到ApplicationContext
时,都会通知该bean。实质上,这是一个标准的观察者模式。
从Spring4.2开始,事件基础设施已经被显著地改善并且提供基于注解的模式以及去发布任意事件的能力(也就是说,对象没有必须要从
ApplicationEvent
拓展)。当发布一个对象时,我们包装为事件。
下面表格描述Spring提供的标准事件:
Event | Explanation |
---|---|
ContextRefreshedEvent | 当ApplicationContext 被初始化或刷新(例如,通过在ConfigurableApplicationContext 接口上使用refresh()方法)时发送。这里的“initialized ”意思是所有的bean被加载、post-processor (后置处理器)bean被检查和激活、单例bean前置实例化和ApplicationContext 对象已经准备好使用。只要上下文没有被关闭,刷新能够在多个时间触发,前提是所选的ApplicationContext 实际上支持这种“热 ”刷新。例如,XmlWebApplicationContext 支持热刷新,但是GenericApplicationContext 不支持。 |
ContextStartedEvent | 当ApplicationContext 通过在ConfigurableApplicationContext 接口上使用start() 方法启动时发送。这里的“started ”意思是所有Lifecyclebean 接收一个真实的开始信号。通常地,这个信号在显示停止之后重启bean使用,但也可以用于启动尚未配置自动启动的组件(例如,尚未在初始化时启动的组件) |
ContextStoppedEvent | 当ApplicationContext 通过在ConfigurableApplicationContext 上接口上使用stop() 方法停止时发布。这里的“stopped ”意思是所有Lifecycle 的bean接受一个显示停止信号。停止上下文可以通过调用start() 重启。 |
ContextClosedEvent | 当ApplicationContext 通过在ConfigurableApplicationContext 接口上使用close() 关闭或者通过JVM关闭钩子时发布。这里的”closed “意思是所有单例bean将被销毁。一旦上下文被关闭,它已达到使用寿命,无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于Web的事件,告诉所有Bean HTTP请求已得到服务。这个时间在请求完成之后被发布。这个事件仅仅能应用到使用Spring的DispatcherServlet 的web应用中。 |
ServletRequestHandledEvent | RequestHandledEvent 的子类,用于添加特定于Servlet 的上下文信息。 |
你也可以创建和发布你自己的自定义事件。下面例子展示了一个简单类,它拓展了Spring的ApplicationEvent
基础类:
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
去发布自定义ApplicationEvent
,在ApplicationEventPublisher
上调用publishEvent()
方法。通常地,通过创建一个类实现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 content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
在配置时,Spring容器检测EmailService
实现ApplicationEventPublisherAware
并且自动地调用setApplicationEventPublisher()
方法。事实上,传入的参数是Spring容器本身。你正在通过其ApplicationEventPublisher
接口与应用程序上下文进行交互。
去接受自定义ApplicationEvent
,你可以创建一个类实现ApplicationListener
并且注册它作为Spirng的bean。下面例子展示以一个类:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意,ApplicationListener
通常是用定制事件的类型参数化的(在前面的例子中是BlackListEvent
)。也就是说onApplicationEvent()
方法能保持类型安全,避免任何转换。你可以注册许多你希望的事件监听,但是注意,默认情况,事件监听接受事件时同步地。也就是说publishEvent()
方法阻塞直到所有监听器完成事件处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中进行操作。
如果有必要采用其他发布事件的策略,查看javadoc对应Spring的ApplicationEventMulticaster
接口和SimpleApplicationEventMulticaster
实现配置。
下面例子显示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>
放到一起,当emailService
bean的sendEmail()
方法被调用时,如果这里任何邮件信息需要被例入黑名单,一个类型为BlackListEvent
自定义事件被发布。blackListNotifier
bean作为ApplicationListener
和接受BlackListEvent
被注册,在这一点上,它可以通知有关各方。
Spring的事件机制被设计为在同一个应用上下文中Spring bean之间的简单通信/交流。然而,对于更复杂的企业集成需求,单独维护的
Spring integration
项目提供了对构建轻量级、面向模式、事件驱动的体系结构的完整支持,这些体系结构构建于著名的Spring编程模型之上。
基于注解事件监听器
从Spring4.2后,你可以在任何通过使用@EventListener
注解的bean,公共方法注册一个事件监听器。BlackListNotifier
可以被重写,像下面例子:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
这个方法签名再次声明它监听的事件类型,但是,在这里一个灵活的名字和不需要实现特定监听器接口。只要实际事件类型解析了实现层次结构中的泛型参数,就可以通过泛型缩小事件类型。
如果你的方法需要监听一些事件或者如果你想去定义它为无参数,事件类型也可以在注解自身上指定。下面例子展示怎样去做:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
也可以通过使用定义SpEL表达式的注释的condition属性来添加其他运行时过滤器,该注释应匹配以针对特定事件实际调用该方法。
以下示例显示了仅当事件的content属性等于my-event
时,才可以重写我们的通知程序以进行调用:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式都会根据专用上下文进行评估。下表列出了上下文可用的项,以便你可以将它们用于条件事件处理。
Name | Location | Description | Example |
---|---|---|---|
Event | root object | 实际的 ApplicationEvent . | #root.event 或 event |
Arguments array | root object | 这些参数(对象数组)被使用去调用方法 | #root.args 或args ; args[0] 获取第一个元素. |
Argument name | evaluation context | 任何方法参数名。如,因为一些原因,这些名称无效(例如,因为在编译字节码中没有debug信息),使用#a <#arg> 语法也可以使用单个参数,其中<#arg> 代表参数索引(从0开始) | #blEvent 或 #a0 (你可以使用 #p0 或#p<#arg>`参数符号作为别名) |
请注意,即使你的方法签名实际上引用了已发布的任意对象,#root.event也允许你可以访问底层事件。
如果你需要发布一个事件作为处理其它事件结果,你可以改变方法签名去返回事件,类似下面例子:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
这个特性对于异步监听是不支持的。
这个新方式通过上面的方法为每个BlackListEvent
处理发布一个新ListUpdateEvent
。如果你需要去发布一些事件,你可以返回一个事件Collection
。
异步事件监听器
如果你想一个特定监听器去处理异步事件,你可以重用常规的@Async
支持。下面例子展示怎样使用:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
- 如果一个异步事件监听器抛出
Exception
,它不会被传递到调用者。查看AsyncUncaughtExceptionHandler
更多详情。 - 异步事件监听器方法不能通过返回一个值发布一个后续事件。如果你需要发布处理结果的其它事件,注入
ApplicationEventPublisher
去手动发布事件。
监听器顺序
如果你需要一个监听器调用在另外一个监听器之前,你可以添加@Order
注解到方法声明上,类似下面例子:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
你可以使用泛型去进一步定义你的事件结构。考虑使用EntityCreatedEvent<T>
,其中T
是已创建的实际实体的类型。例如,你可以创建下面的监听器定义,为Person
去接受EntityCreatedEvent
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
由于类型擦除,只有在触发的事件解析事件监听器过滤器所基于的通用参数(即,类似于类PersonCreatedEvent
扩展EntityCreatedEvent<Person>{})
的情况下才可以工作。
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。在这种情况下,你可以实现ResolvableTypeProvider
来指导框架,使其超出运行时环境提供的范围(备注:通过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
,而且适用于你作为事件发送的任何任意对象。
1.15.3 便捷地访问低级别资源
为了更好的使用和理解应用上下文,你应该熟悉Spring的Resource
抽象,在Resources中描述。
应用上下文是ResourceLoader
,它可以被使用加载Resource
对象。Resource
本质上是JDK java.net.URL
类的功能更丰富的版本。事实上,Resource
实现包装一个java.net.URL实例,Resource
可以透明的方式从几乎任何位置获取低级资源,包含从类路径、文件系统路径、任何描述一个标准URL、以及其他的变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。
你可以配置部署到应用程序上下文中的Bean,以实现特殊的回调接口ResourceLoaderAware
,以便在初始化时自动回调,并将应用程序上下文本身作为ResourceLoader
传入。你也可以暴露Resource
类型属性,被使用获取静态资源。它们像其他属性一样注入其中。当bean被部署时,你可以指定这些Resource
属性作为简单String路径并且依靠这些文本字符串自动转换真实Resource
对象。
提供给ApplicationContext
构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现进行适当的处理。例如,ClassPathXmlApplicationContext
将简单的位置路径视为类路径位置。你也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。
1.15.4 Web应用的ApplicationContext实例化
你可以通过声明式地创建ApplicationContext
实例,例如:ApplicationContext
。当然,你也可以通过使用ApplicationContext
实现之一编程式地创建ApplicationContext
实例。
你可以通过使用ContextLoaderListener
注册一个ApplicationContext
,类似下面例子:
<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
的任何子目录中)。
1.15.5 部署SpringApplicationContext
作为Java EE rar文件
可以将Spring ApplicationContext
部署为RAR文件,并将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这等效于引导独立的ApplicationContext
(仅托管在Java EE环境中)能够访问Java EE服务器功能。RAR部署是部署无头WAR文件的方案的一种更自然的选择,实际上,这种WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext
。RAR部署非常适合于不需要HTTP入口点,而只由消息端点和调度任务组成的应用程序上下文。在上下文中的这些bean能使用应用服务资源,例如,JTA事物管理、JNDI绑定JDBC DataSource
实例、JMS ConnectionFactory
实例和注册平台的JMX服务-通过所有Spring的标准事物管理、JNDI、JMX支持的设施。应用组件也可以通过Spring的TaskExecutor
抽象与应用server的JCA WorkManager
相互交互 。
有关RAR部署中涉及的配置详细信息,请参见SpringContextResourceAdapter类的javadoc。
对于将Spring ApplicationContext
作为Java EE RAR文件的简单部署:
- 打包应用所有类到RAR文件中(这是具有不同文件扩展名的标准JAR文件)。将所有必需的库JAR添加到RAR归档文件的根目录中。添加一个
META-INF/ra.xml
部署描述符(如SpringContextResourceAdapter
的javadoc中所示和相应的Spring XML bean定义文件(通常为META-INF/applicationContext.xml
)。 - 将生成的RAR文件拖放到应用程序服务器的部署目录中
此类RAR部署单位通常是独立的。它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的
ApplicationContext
的交互通常是通过与其他模块共享的JMS目标进行的。例如,基于rar的ApplicationContext
还可以调度一些作业或对文件系统中的新文件(或类似的东西)作出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这些端点可以由同一台机器上的其他应用程序模块使用。
1.16 BeanFactory
BeanFactory
API为Spring IoC工厂提供底层基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的DefaultListableBeanFactory
实现是更高级别的GenericApplicationContext
容器中的一个关键代理。
BeanFactory
和相关的接口(例如:BeanFactoryAware
、InitializingBean
、DisposableBean
)是为其他框架组件非常重要的集成点。通过不需要任何注解,甚至不需要反射,它们可以在容器及其组件之间进行非常有效的交互。应用级别bean可能使用相同回调接口,但通常更喜欢通过注释或通过编程配置进行声明式依赖注入。
请注意,核心BeanFactory
API级别及其DefaultListableBeanFactory
实现不对配置格式或要使用的任何组件注释进行假设。所有这些风格都通过扩展(比如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)实现,并在共享的BeanDefinition
对象上进行操作,作为核心元数据表示。这是使Spring的容器如此灵活和可扩展的本质所在。
1.16.1 BeanFactory
或ApplicationContext
本节说明BeanFactory
和ApplicationContext
容器级别之间的区别以及对引导的影响。
除非有充分的理由,否则应使用ApplicationContext
,除非将GenericApplicationContext
和其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现,否则应使用ApplicationContext
。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件、触发类路径扫描、以编程方式注册Bean定义和带注解的类,以及(从5.0版本开始)注册功能性Bean定义。
因为ApplicationContext
包含BeanFactory
的所有功能,所以通常建议在普通BeanFactory中使用,除非需要完全控制Bean处理的方案。在ApplicationContext
(例如GenericApplicationContext
实现)中,按照约定(即,按bean名称或按bean类型(尤其是后处理器))检测到几种bean,而普通的DefaultListableBeanFactory
则与任何特殊bean无关。
对于许多扩展的容器功能,例如注解处理和AOP代理,BeanPostProcessor
扩展点是必不可少的。如果仅使用普通的DefaultListableBeanFactory
,则默认情况下不会检测到此类后处理器并将其激活。这种情况可能会造成混淆,因为你的bean配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。
下表列出了BeanFactory
和ApplicationContext
接口和实现提供的功能。
Feature | BeanFactory | ApplicationContext |
---|---|---|
Bean 实例化/连接 | Yes | Yes |
集成生命周期管理 | No | Yes |
BeanPostProcessor 自动注册 | No | Yes |
BeanFactoryPostProcessor 自动注册 | No | Yes |
便捷的MessageSource 获取(国际化) | No | Yes |
内建ApplicationEvent 发布机制 | No | Yes |
要向DefaultListableBeanFactory
显式注册Bean后处理器,需要以编程方式调用addBeanPostProcessor
,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
要将BeanFactoryPostProcessor
应用于普通的DefaultListableBeanFactory
,你需要调用其postProcessBeanFactory
方法,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式的注册步骤都不方便,这就是为什么在Spring支持的应用程序中,各种ApplicationContext
变量比普通的DefaultListableBeanFactory
更为可取的原因,尤其是在典型企业设置中依赖BeanFactoryPostProcessor
和BeanPostProcessor
实例来扩展容器功能时。
AnnotationConfigApplicationContext
已注册了所有常见的注解后处理器,并且可以通过配置注解(例如@EnableTransactionManagement
)在幕后引入其他处理器。在Spring基于注解的配置模型的抽象级别上,bean后处理器的概念仅是内部容器详细信息。
[/wshop_paid]