11.使用JSR 330标准注释
从Spring 3.0开始,Spring提供对JSR-330标准注释(依赖注入)的支持。 这些注释的扫描方式与Spring注释的扫描方式相同。 要使用它们,您需要在类路径中有相关的jar。
注意:
-
如果使用Maven,则标准Maven存储库(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)中提供了javax.inject工件。 您可以将以下依赖项添加到文件pom.xml中:
-
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
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,或者通过Provider.get()
调用来懒惰访问其他bean。 以下示例提供了先前示例的变体:
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
如果您想为应该注入的依赖项使用一个限定名,那么您应该使用@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
没有必需的属性。 以下一对示例显示了如何使用@Inject
和@Nullable
:
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
11.2@Named和@ManagedBean:@Component注解的标准等效项
可以使用@ javax.inject.Named
或javax.annotation.ManagedBean
代替@Component
,如以下示例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
在不指定组件名称的情况下使用@Component
是非常常见的。 可以类似的方式使用@Named
,如以下示例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当使用@Named
或@ManagedBean
时,可以使用与使用Spring注解完全相同的方式来使用组件扫描,如以下示例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
注意:
- 与
@Component
相反,JSR-330@Named
和JSR-250ManagedBean
注解是不可组合的。 您应该使用Spring的原型模型来构建自定义组件注释。
11.3JSR-330标准注解的局限性
使用标准注解时,应该知道有些重要特性不可用,如下表所示:
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject没有“必需”属性。 可以与Java 8的Optional一起使用。 |
@Component | @Named / @ManagedBean | JSR-330没有提供可组合模型,只是提供了一种识别已命名组件的方法。 |
@Scope(“singleton”) | @Singleton | JSR-330的默认范围类似于Spring的原型。 但是,为了使其与Spring的常规默认设置保持一致,默认情况下,在Spring容器中声明的JSR-330 bean是单例的。 为了使用单例以外的其他范围,您应该使用Spring的@Scope注解。 javax.inject还提供了@Scope注解。 不过,此仅用于创建自己的注解。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier只是用于构建自定义限定符的元注解。 可以通过javax.inject.Named关联具体的字符串限定符(例如Spring的@Qualifier带有值)。 |
@Value | - | 没有等价的 |
@Required | - | 没有等价的 |
@Lazy | - | 没有等价的 |
ObjectFactory | Provider | javax.inject.Provider是Spring的ObjectFactory的直接替代方法,只不过具有较短的get()方法名称。 它也可以与Spring的@Autowired结合使用,也可以与无注解的构造函数和setter方法结合使用。 |
12.基于java的容器配置
本节介绍如何在Java代码中使用注解来配置Spring容器。 它包括以下主题:
- 基本概念:@Bean和@Configuration
- 使用AnnotationConfigApplicationContext实例化Spring容器
- 使用@Bean注解
- 使用@Configuration注解
- 编写基于java的配置
- Bean定义概要文件
- PropertySource抽象
- 使用@PropertySource
- 语句中的占位符解析
12.1基本概念:@Bean和@Configuration
Spring的新Java配置支持中的主要工件是@Configuration
注解的类和@Bean
注解的方法。
@Bean注解用于指示方法实例化,配置和初始化要由Spring IoC容器管理的新对象。 对于那些熟悉Spring的 XML配置的人来说,@ Bean注释与元素具有相同的作用。 您可以将@Bean注解的方法与任何Spring @Component一起使用。 但是,它们最常与@Configuration bean一起使用。
用@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 VS “精简” @Bean模式?
- 当
@Bean
方法在没有使用@Configuration
注解的类中声明时,它们被称为在lite模式下处理。在@Component
甚至在普通旧类中声明的Bean
方法被认为是lite
,包含类的主要目的不同,而@Bean
方法则是一种额外的好处。例如,服务组件可以通过每个适用组件类上的附加@Bean
方法向容器公开管理视图。在这种情况下,@Bean
方法是一种通用工厂方法机制。 - 与完整的
@Configuration
不同,lite@Bean
方法无法声明Bean
之间的依赖关系。 取而代之的是,它们在其包含组件的内部状态上进行操作,并且还可以根据可能声明的参数进行操作。 因此,此类@Bean
方法不应调用其他@Bean
方法。 实际上,每个此类方法仅是用于特定bean引用的工厂方法,而没有任何特殊的运行时语义。 这里的积极副作用是,不必在运行时应用CGLIB子类,因此在类设计方面没有任何限制(即,包含类可能是最终类,依此类推)。 - 在常见的场景中,
@Bean
方法要在@Configuration
类中声明,以确保始终使用full模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止通过常规Java调用意外地调用相同的@Bean
方法,这有助于减少在lite模式下操作时难以跟踪的细微错误。
下面几节将深入讨论@Bean
和@Configuration
注解。但是,首先,我们将介绍使用基于java的配置创建spring容器的各种方法。
12.2使用AnnotationConfigApplicationContext实例化Spring容器
以下各节介绍了Spring 3.0中引入的Spring的AnnotationConfigApplicationContext
。 这种通用的ApplicationContext
实现不仅可以接受@Configuration
类作为输入,还可以接受普通的@Component
类以及带有JSR-330元数据注释的类。
当提供@Configuration
类作为输入时,@Configuration
类本身将注册为Bean
定义,并且该类中所有已声明的@Bean
方法也将注册为Bean
定义。
提供@Component
和JSR-330类时,它们将注册为Bean定义,并且假定在必要时在这些类中使用了诸如@Autowired
或@Inject
之类的DI元数据。
12.21Simple Construction
与实例化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
。
12.22通过使用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();
}
12.23启用扫描组件scan(String…)
要启用组件扫描,可以按如下方式注解@Configuration
类:
@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.acme
包(或下面的任何包)中声明,则在调用scan()
时将其拾取。 根据refresh()
,其所有@Bean
方法都将被处理并注册为容器内的Bean定义。
12.24使用AnnotationConfigWebApplicationContext支持Web应用程序
AnnotationConfigWebApplicationContext
提供了AnnotationConfigApplicationContext
的WebApplicationContext
变体。 在配置Spring ContextLoaderListener
Servlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。 以下web.xml代码片段配置了典型的Spring MVC Web应用程序(请注意contextClass context-param和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>
12.3使用@Bean注解
@Bean
是方法级别的注释,是XML 元素的直接类似物。 注释支持提供的某些属性,例如:* init-method * destroy-method * autowiring * name
.
您可以在@Configuration
注解或@Component
注解的类中使用@Bean
注解。
12.31声明一个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的注入点所要求的具体类型一样)更为安全。
12.32Bean的依赖关系
带@bean
注解的方法可以有任意数量的参数,用于描述构建该bean所需的依赖关系。例如,如果我们的TransferService
需要一个AccountRepository
,我们可以用一个方法参数来具体化这个依赖关系,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造器的依赖项注入非常相似。
12.33接收生命周期回调
任何使用@Bean
注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct
和@PreDestroy
注解。
常规的Spring生命周期回调也得到了完全支持。如果一个bean实现了InitializingBean
、DisposableBean
或Lifecycle
,容器将调用它们各自的方法。
还完全支持标准的* Aware接口集(例如BeanFactoryAware
,BeanNameAware
,MessageSourceAware
,ApplicationContextAware
等)。
@Bean
注解支持指定任意的初始化和销毁回调方法,非常类似于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();
}
}
注意:
-
默认情况下,使用Java配置定义的具有公共
close
或shutdown
方法的bean将与销毁回调一起被自动征用。如果您有一个公共close
或shutdown
方法,并且不希望在容器关闭时调用它,那么您可以将@Bean(destroyMethod="")添加到bean定义中,以禁用默认(推断)模式。 -
默认情况下,您可能希望对使用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中工作时,您可以对对象做任何您喜欢的事情,而不总是需要依赖容器的生命周期。
12.34指定Bean范围
Spring包含@Scope
注解,以便您可以指定bean的范围。
使用@Scope注解
您可以指定使用@Bean注解定义的bean应该具有特定范围。 您可以使用Bean Scopes部分中指定的任何标准范围。
默认范围是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
,它类似于以下情况:
// 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;
}
12.35Customizing Bean Naming
默认情况下,配置类使用@Bean
方法的名称作为结果bean的名称。 但是,可以使用name属性覆盖此功能,如以下示例所示:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
12.36Bean Aliasing
正如在命名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...
}
}
12.37Bean Description
有时,提供有关bean的更详细的文本描述会很有帮助。 当出于监视目的而暴露(可能通过JMX)bean时,这尤其有用。
要向@Bean添加描述,可以使用@Description注解,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
12.4使用@Configuration注解
@Configuration
是类级别的注解,指示对象是Bean定义的源。 @Configuration
类通过公共@Bean
注解方法声明bean。 对@Configuration
类的@Bean
方法的调用也可以用于定义Bean之间的依赖关系。
12.41注入bean间的依赖关系
当bean相互依赖时,表示这种依赖关系就像让一个bean方法调用另一个一样简单,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的示例中,beanOne
通过构造函数注入接收到对beanTwo
的引用。
注意:
- 仅当在
@Configuration
类中声明@Bean
方法时,此声明bean间依赖关系的方法才有效。 您不能使用简单的@Component
类声明Bean间的依赖关系。
12.42查找方法注入
如前所述,查找方法注入是一项高级功能,您应该很少使用。 在单例作用域的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()
方法将被覆盖,以便它查找新的(原型)命令对象。 以下示例显示了如何执行此操作:
@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();
}
}
}
12.43有关基于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默认情况下具有单例作用域。 这就是神奇的地方:所有@Configuration
类在启动时都使用CGLIB进行了子类化。 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的bean
。
注意:
- 根据bean的范围,行为可能不同。我们在这里讨论的是单例。
- 从Spring 3.2开始,不再需要将CGLIB添加到您的类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包并直接包含在spring-core JAR中。
- 由于CGLIB在启动时动态添加特性,所以有一些限制。特别是,配置类不能是最终的。然而,在4.3中,配置类允许使用任何构造函数,包括使用
@Autowired
或为默认注入使用单一的非默认构造函数声明。 - 如果您希望避免任何cglib强加的限制,可以考虑在非
@configuration
类上声明@Bean
方法(例如,改为在纯@Component
类上)。这样,@Bean
方法之间的交叉方法调用就不会被拦截,因此您只能在构造函数或方法级别上独家依赖于依赖注入。
12.5编写基于java的配置
Spring的基于Java的配置功能使您可以撰写注解,从而降低配置的复杂性。
12.51使用@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
类。
注意:
- 从Spring Framework 4.2开始,
@Import
还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register
方法。 如果要通过使用一些配置类作为入口点来显式定义所有组件,从而避免组件扫描,则此功能特别有用。
12.52在导入的@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:这意味着它们可以利用与任何其他bean相同的@Autowired
和@Value
注入以及其他特性。
注意:
- 确保以这种方式注入的依赖项只是最简单的一种。
@Configuration
类在上下文的初始化过程中很早就被处理,并且强制以这种方式注入依赖项可能导致意外的早期初始化。 如上例所示,请尽可能使用基于参数的注入。 - 另外,通过
@Bean
使用BeanPostProcessor
和BeanFactoryPostProcessor
定义时要特别小心。 通常应将这些声明为静态@Bean
方法,而不触发其包含的配置类的实例化。 否则,@Autowired
和@Value
可能不适用于配置类本身,因为可以将其创建为比AutowiredAnnotationBeanPostProcessor
早的bean实例。
以下示例说明如何将一个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");
}
注意:
- 从Spring Framework 4.3开始,仅支持
@Configuration
类中的构造方法注入。 还要注意,如果目标bean仅定义一个构造函数,则无需指定@Autowired
。
12.53完全限定导入的bean以方便导航
在前面的场景中,使用@Autowired
可以很好地工作并提供所需的模块化,但是确切地确定在何处声明自动装配的Bean定义仍然有些模棱两可。 例如,当开发人员查看ServiceConfig
时,您如何确切知道@Autowired
AccountRepository
bean的声明位置? 它在代码中不是明确的,这可能很好。 请记住,Spring Tools for 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,而不是后者的直接依赖项所暗示的)。
12.54有条件地包含@Configuration类或@Bean方法
根据某些系统状态,有条件地启用或禁用完整的@Configuration
类甚至单个@Bean
方法通常很有用。 一个常见的示例是仅在Spring环境中启用了特定的配置文件时才使用@Profile
注解来激活Bean
@Profile
注解实际上是通过使用更灵活的称为@Conditional
的注解来实现的。 @Conditional
注解指示在注册@Bean
之前应咨询的特定org.springframework.context.annotation.Condition
实现。
Condition
接口的实现提供了一个match(…)
方法,该方法返回true
或false
。 例如,以下清单显示了用于@Profile
的实际Condition
实现:
@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;
}
12.55结合Java和XML配置
Spring的@Configuration
类支持并非旨在100%完全替代Spring XML。 某些工具(例如Spring XML名称空间)仍然是配置容器的理想方法。 在使用XML方便或有必要的情况下,您可以选择:使用“ ClassPathXmlApplicationContext
”以“ XML中心”方式实例化容器,或使用AnnotationConfigApplicationContext
和“ AnnotationConfigApplicationContext
”以“ Java中心方式”实例化容器。 @ImportResource
注解可根据需要导入XML。
使用以xml为中心的@Configuration类
最好是从XML引导Spring容器,并以特别的方式包含@Configuration
类。例如,在使用Spring XML的大型现有代码库中,更容易根据需要创建@Configuration
类并从现有XML文件中包含它们。在本节的后面,我们将介绍在这种以xml为中心的情况下使用@Configuration
类的选项。
将@Configuration
类声明为纯Spring 元素
请记住,@Configuration
类最终是容器中的bean定义。 在本系列示例中,我们创建一个名为AppConfig
的@Configuration
类,并将其作为定义包含在system-test-config.xml
中。 因为<context:annotation-config />
已打开,所以容器可以识别@Configuration
注解并正确处理AppConfig
中声明的@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只能按类型自动装配,因此也不严格要求显式bean id。
12.56使用<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>
12.57@Configuration**Class-centric Use of XML with**
@ImportResource
在@Configuration
类是配置容器的主要机制的应用程序中,仍然有必要至少使用一些XML。 在这些情况下,您可以使用@ImportResource
并仅定义所需的XML。 这样做实现了“以Java为中心”的方法来配置容器,并使XML保持在最低限度。 以下示例(包括配置类,定义Bean的XML文件,属性文件和主类)显示了如何使用@ImportResource
注解来实现按需使用XML的以Java为中心的配置:
@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);
// ...
}