Spring框架参考文档-核心技术-IoC容器-7.12 基于java的容器配置
原文:
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-java
7.12.1 基本概念: @Bean 和 @Configuration
在Spring新的Java配置支持中,其核心构件是
@Configuration
注解类和
@Bean
注解方法.
@Bean
注解用来表示方法实例化,配置以及初始化由Spring IoC容器管理的新对象.
对于那些熟悉Spring
<beans/>
XML配置的人来说,
@Bean
注解扮演了与
<bean/>
元素相同的角色.你可以在任何Spring
@Component
上使用
@Bean
注解方法,但通常情况下,它们经常与
@Configuration
beans一起使用.
@Configuration
注解类表示其主要目的作为bean定义的来源(source).
此外,
@Configuration
类允许在同一个类中调用其它的
@Bean
方法来定义它们之间的依赖关系.
可能最简单的
@Configuration
类可以像下面这样读取:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
AppConfig
类等价于下面的Spring
<beans/>
XML:
<beans>
<bean id = "myService" class = "com.acme.services.MyServiceImpl" />
</beans>
<bean id = "myService" class = "com.acme.services.MyServiceImpl" />
</beans>
完整(full)@Configuration vs 精简(lite) @Beans 模式?
当
@Bean
方法声明在未用
@Configuration
注解的类中时,他们被称为“精简”模式处理.
例如,bean 方法声明在
@Component
中或普通类(plain old class)中将被认为是精简的('lite').
不像完整
@Configuration
, 精简
@Bean
方法不能容易地声明bean之间的依赖关系.通常情况下,当处于精简模式时,一个
@Bean
方法不能调用另一个
@Bean
方法.
只有在
@Configuration
类中使用
@Bean
方法才是一种可确保总是处于完整模式下的推荐方法.
这可以阻止同一个@Bean方法不小心被调用多次,有助于减少细微的bug(当运行在精简模式下时,这些bug很难追查)。
@Bean
和
@Configuration
注解会在下面的章节中详细讨论. 但首先,我们要讲解基于Java配置来创建Spring 容器的各种方法.
7.12.2 使用AnnotationConfigApplicationContext来实例化Spring 容器
下面的章节记录了Spring的
AnnotationConfigApplicationContext
, 它是Spring 3.0中新引入的.
这个多用途的
ApplicationContext
实现不仅有能力接受
@Configuration
类作为它的输入,也可以接受
@Component
类和使用JSR-330 元数据注解类作为它的输入.
当
@Configuration
类作为输入时,
@Configuration
类自身也将作为bean定义进行注册,并且这个类中所有声明了
@Bean
方法也会以bean的定义进行注册.
当提供
@Component
和JSR-330时,它们也是按bean定义进行注册, 并会假设在那些类的必要地方使用DI元数据,如
@Autowired
或
@Inject
are.
简单构造
与实例化
ClassPathXmlApplicationContext时需要Spring XML文件方式相同
, 当实例化
AnnotationConfigApplicationContext时,也需要@Configuration
类作为它的输入.
这允许在Spring容器中完全自由地使用XML:
public
static
void
main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig. class );
MyService myService = ctx.getBean(MyService. class );
myService.doStuff();
}
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();
}
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();
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig. class , OtherConfig. class );
ctx.register(AdditionalConfig. class );
ctx.refresh();
MyService myService = ctx.getBean(MyService. class );
myService.doStuff();
}
使用scan(String…)来启用组件扫描
要启用组件扫描,只需要像这样下面注解你的
@Configuration
类:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig { ... }
@ComponentScan(basePackages = "com.acme")
public class AppConfig { ... }
有经验的 Spring用户非常熟悉与它等价的来自Spring context:命名空间的XML声明:
<beans>
<context:component-scan base-package= "com.acme" />
</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 );
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan( "com.acme" );
ctx.refresh();
MyService myService = ctx.getBean(MyService. class );
}
记住
@Configuration
类是带有
@Component的meta-annotated
, 因此它们是组件扫描的候选人!
在上面的例子中, 假设
AppConfig
声明在
com.acme
包(或它的子包)中,在调用scan()方法时,它也会挑选出来,并当
refresh()的时候,它所有的
@Bean
方法将被处理,并以bean的定义注册到容器中.
使用 AnnotationConfigWebApplicationContext来支持Web应用程序
AnnotationConfigApplicationContext
的
WebApplicationContext变异是AnnotationConfigWebApplicationContext
. 此实现可用于配置Spring
ContextLoaderListener
servlet 监听器, Spring MVC
DispatcherServlet
等等.
下面在web.xml中配置典型Spring MVC web application的片断.
注意
contextClass
和 init-param的使用:
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
<context-param>
<param-name> contextClass </param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning -->
<context-param>
<param-name> contextConfigLocation </param-name>
<param-value> com.acme.AppConfig </param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener </listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name> dispatcher </servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
<init-param>
<param-name> contextClass </param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes -->
<init-param>
<param-name> contextConfigLocation </param-name>
<param-value> com.acme.web.MvcConfig </param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name> dispatcher </servlet-name>
<url-pattern> /app/* </url-pattern>
</servlet-mapping>
</web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
<context-param>
<param-name> contextClass </param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning -->
<context-param>
<param-name> contextConfigLocation </param-name>
<param-value> com.acme.AppConfig </param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener </listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name> dispatcher </servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
<init-param>
<param-name> contextClass </param-name>
<param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes -->
<init-param>
<param-name> contextConfigLocation </param-name>
<param-value> com.acme.web.MvcConfig </param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name> dispatcher </servlet-name>
<url-pattern> /app/* </url-pattern>
</servlet-mapping>
</web-app>
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();
}
}
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上述的配置与下面的Spring XML配置完全等价:
<beans>
<bean id = "transferService" class = "com.acme.TransferServiceImpl" />
</beans>
<bean id = "transferService" class = "com.acme.TransferServiceImpl" />
</beans>
两种Bean声明都使名为
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);
}
}
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
这种解决机制与基于构造器的依赖注入是完全相同的, 参考
the relevant section
来了解更多细节.
接收生命周期回调
任何使用
@Bean
注解定义的类都支持正常的生命周期回调,也可以使用 JSR-250中的
@PostConstruct
和
@PreDestroy
注解, 参考
JSR-250 annotations
来了解详情.
完全支持 Spring
lifecycle
回调. 如果一个bean实现了
InitializingBean
,
DisposableBean
, 或
Lifecycle
, 它们各自的方法都会被容器调用.
标准集合的
*Aware
接口,如
BeanFactoryAware
,
BeanNameAware
,
MessageSourceAware
,
ApplicationContextAware
等等都完全支持.
@Bean
注解支持指定任意初始化和销毁回调方法, 非常类似于Spring XML bean元素中的
init-method
和
destroy-method
属性:
public
class
Foo {
public void init() { // initialization logic
}
}
public class Bar {
public void cleanup() { // destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
@Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}
}
public void init() { // initialization logic
}
}
public class Bar {
public void cleanup() { // destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
@Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}
}
默认情况下,使用Java配置的Bean都有一个 public
close
或
shutdown
方法以在销毁回调时自动调用.
如果你有一个public
close
或
shutdown
方法,并且不想在容器关闭时自动被调用,你可以简单地在Bean定义中添加
@Bean(destroyMethod="")
来禁用默认(推断)模式.
你可能很想为那些通过JNDI获取到的资源那样做,因为它们的生命周期是在应用程序外被管理的.
特别是,对于DataSource这样的资源一定要保证这样做,因为它们在Java EE应用程序服务器上可能会出现问题.
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup( "MyDS");
}
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup( "MyDS");
}
此外,使用
@Bean
方法,你通常会选择使用编程来进行JNDI查找: 要么使用Spring的
JndiTemplate
/
JndiLocatorDelegate帮助类,要么直接使用
JNDI
InitialContext
,但绝不是
JndiObjectFactoryBean,因为它会强制你声明FactoryBean
的返回类型,而不是实际目标类型, 这使得它很难在其它@Bean中跨引用调用.
当然,在上面
Foo
的情况中, 其效果与直接在构造器中调用init()方法是一样的:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
指定bean范围
使用@Scope注解
你可为
@Bean
注解的bean指定一个scope.你可以使用
Bean Scopes中
任何标准的scope.
默认scope是
singleton
,但你可以用
@Scope
注解进行覆盖:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope and scoped-proxy
通过
scoped proxies,
Spring提供了一种便利的方式来与scoped依赖一起工作. 在XML配置中,创建代理的最简单方式是使用
<aop:scoped-proxy/>
元素. 在Java中,使用@Scope注解与proxyMode属性来配置bean也可以达到同样的效果. 默认是无代理(
ScopedProxyMode.NO
),但你可以指定为
ScopedProxyMode.TARGET_CLASS
或
ScopedProxyMode.INTERFACES
.
如果你看了XML参考文档中的 scoped proxy 例子,再来看
@Bean
,它看起来就像下面这样:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
自定义bean名称
默认情况下, 配置类使用
@Bean
方法的名称作为结果bean的名称.这可以通过
name
属性来覆盖.
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
Bean 别名
如在
Section 7.3.1, “Naming beans”中讲述的
, 有时候期望单个bean上能指定多个名称,即别名.
@Bean
注解的name属性可通过接受一个数组来达到这个目的.
@Configuration
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时候,为bean提供更加详尽的文本描述通常是有帮助的. 特别是bean出于监控目的暴露(可通过JMX)时,特别有用.
To add a description to a
@Bean
the
@Description
annotation can be used:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
7.12.4 使用@Configuration注解
@Configuration
是一个类级注解,用来表示bean定义的来源.
@Configuration
类可通过public
@Bean注解方法来声明
bean.在
@Configuration中调用@Bean
方法也可用来定义bean之间的依赖. 参考 Section 7.12.1, “Basic concepts: @Bean and @Configuration"一般介绍
.
注入bean 依赖
当
@Bean
s依赖另一个bean时, 表示依赖很简单,就像一个bean的方法调用另一个:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
在上面的例子中,通过构造器注入,
foo
bean 收到了
bar的引用
.
声明bean依赖的方法只能是
@Bean
方法在
@Configuration
类中声明. 不能通过
@Component
类来声明bean依赖关系.
Lookup 方法注入
如前所述,Lookup方法注入是一个高级的功能,你应该很少使用。它在一个singleton范围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();
}
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()方法可通过查找新(prototype(原型))command 对象来覆盖
:
@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 command() overridden
// to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
@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 command() 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();
}
}
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();
}
}
注:4.2.5版本上没发现这个问题
clientDao()
在
clientService1()和clientService2()中各调用了一次
.因为这个方法会创建一个新
ClientDaoImpl
的实例,并将其返回,你可能正希望有2个实例(一个服务一个).
那肯定是有问题的:在Spring中,实例bean默认有一个
singleton
scope . 这个魔法来自: 所有
@Configuration
类在启动期间都会使用
CGLIB来子类化
. 在子类中,孩子方法在调用其父类方法创建新实例前,它会为所有缓存(scoped)bean首先检查容器.注意在 Spring 3.2中,没有必须在类路径中添加CGLIB,因为CGLIB的类已被重新打包放到了
org.springframework.cglib之下,并且直接包含在
spring-core JAR中.
根据bean的scope不同,行为也可能不同.在这里我们谈论的是singletons.
CGLIB在启动时动态添加特性有个限制 dynamically adds features at startup-time:
- Configuration 类不应该是final
- 它们必须有一个无参构造器
7.12.5 组合基于 Java的配置
使用@Import注解
非常像Spring xml文件中的
<import/>
元素,这样有助于模块化配置,
@Import
注解允许从其它配置类中加载
@Bean
定义:
@Configuration
public
class
ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@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 );
}
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
类.
Injecting dependencies on imported @Bean definitions
上述例子可以工作 ,但过于简单化。在大多数实际情况下,bean将有一个跨配置类的依赖关系。使用XML时,这本身不是一个问题 ,因为不涉及到编译器,通过一个简单声明ref =“somebean",就可以信任Spring在容器初始化就可以完成这些工作。
当然,当使用@Configuration类时,java编译器限制了 配置模型,在引用其它bean时必须是 有效的java语法。
当然,当使用@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" );
}
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: 这意味着,它可以像其它bean一样利用
@Autowired
和
@Value
注入!
确保你注入的依赖关系是最简单的一种。
@Configuration
类在上下文初始化时很早就被处理了,并强制注入依赖的资源(这种方式可能会导致意外的早期初始化). 无论何时,只要有可能,就要借助以参数为基础的注入,如上面的例子。
同时,通过@Bean来定义
BeanPostProcessor
和
BeanFactoryPostProcessor
时要特别小心.那些通常应该声明为
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() {
// 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" );
}
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() {
// 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
类中的构造器注入只在
Spring Framework 4.3
中支持
.
同时也要注意到如果目标bean只定义了一个构造器,也没有必要指定
@Autowired
;在上面的例子中,
@Autowired
不必出现在
RepositoryConfig
构造器上.
在上述场景中,
@Autowired
工作得很好并提供了预期的模块化,但要明确确定自动装配bean定义声明的位置依然有些模糊.例如,当开发者查看
ServiceConfig时
, 你如何明确的知道
@Autowired AccountRepository
bean在哪里声明的呢?在代码中虽是不明确的,但这可能正好.
记得
Spring Tool Suite
提供了可以渲染组件是如何有线连接起来的图形- 这可能正是你需要的.
同时,你的Java IDE 也能容易地找到所有声明和
AccountRepository
类型的使用, 并且可以快速向你展示
@Bean
方法返回类型的地址.
如果这种模糊性是不可接受的,你希望能在IDE中直接从一个
@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());
}
}
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" );
}
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
类及其依赖与平常导航基于接口的代码没有什么不同.
有条件地包含@Configuration 类或 @Bean 方法
基于某些系统状态有条件地启用或禁用某个
@Configuration类甚至
单个
@Bean方法,这通常是有用的
. 这方面一个常见的例子是当某个特定profile在Spring环境中启用了后,使用
@Profile
注解来激活beans (参考
Section 7.13.1, “Bean definition profiles”来了解细节
).
@Profile
注解实际上是使用更加灵活的
@Conditional
注解来实现的
.
@Conditional
注解表示特定的
org.springframework.context.annotation.Condition实现
(在@Bean注册应该先进行咨询).
Condition
接口的实现只是简单的提供了一个返回true或false的
matches(…)方法
.
下面是@Profile的实际
Condition实现
:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 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;
}
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 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
javadocs
来了解更多详情l.
结合Java和XML配置
Spring
@Configuration
类支持的目的不是100%完全代替Spring XML. 一些如Spring XML命名空间的设施仍然是容器中配置的理想方式. 在这种情况下,XML依然是便利的或必须的,你可以选择:
要么使用
ClassPathXmlApplicationContext以XML为中心的方式来实例化容器
,要么以Java为中心使用
AnnotationConfigApplicationContext,
@ImportResource注解导入必要XML的方式来实例化容器
.
"以XML为中心的”
@Configuration
的使用
从XML,并以包含
@Configuration类的
特设方式来启动Spring容器是更可取的方案.举例来说,在一个大量使用Spring XML的现有代码库中,它可以根据需要来创建
@Configuration类,并从现有XML中包含它们
.
下面你会发现在这种“XML为中心”的情况下使用@
Configuration类的操作
.
记住
@Configuration类最终只是容器中的bean定义
.在这个例子中,我们创建了名为
AppConfig的@Configuration
类,
并以<bean/>定义将其包含在了system-test-config.xml文件中
.
由于开启了
<context:annotation-config/>
,容器会识别
@Configuration
注解并会适当地处理AppConfig中声明的
@Bean方法
.
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
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>
<!-- 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=
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 );
// ...
}
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会引用它, 并且显示式通过名称来获取它也是不太可能发生的.
DataSource
bean也有类似的原因 -它只根据类型来自动装配,因此明确的bean
id
不是严格需要的.
因为
@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>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package = "com.acme" />
<context:property-placeholder location = "classpath:/com/acme/jdbc.properties" />
<bean class = "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name = "url" value = "${jdbc.url}" />
<property name = "username" value = "${jdbc.username}" />
<property name = "password" value = "${jdbc.password}" />
</bean>
</beans>
@Configuration class-centric use of XML with @ImportResource
在@Configuration类的应用程序中,其主要机制是用于配置容器
, 使用一些XML它仍然可能是必须的.在这些场景中, 只要简单地使用
@ImportResource
并定义需要的XML就可以了.这样就实现了“java为中心”的方法来配置容器,并最低限度的保留了XML。
@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);
}
}
@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>
<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=
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 );
// ...
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig. class );
TransferService transferService = ctx.getBean(TransferService. class );
// ...
}