与Spring管理的组件一样,自动检测的组件的默认的和最常用的作用域是singleton
。但是,有时候你需要一个不同的作用域,可以通过@Scope
注解指定。你可以在注解内提供作用域的名称,正如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
注解只有在具体的bean类(对应注解的组件)或者工厂方法(对应@Bean
方法)才会自省。与XML bean定义不同,没有bean定义继承的概念,在类级别的继承层次与元数据目的不相关。
Spring上下文中关于web作用域(例如,request,session)相关的细节,请查看 Request, Session, Application, and WebSocket Scopes。与为这些作用域预构建的注解一样,你也要通过使用Spring源注解的方式组合你自己的作用域注解:
例如,使用@Scope("prototype")
进行元数据注解的自定义注解,可能还声明一个自定义作用域代理模式:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
beans>
@Qualifier
注解是在使用限定符调整基于注解的自动绑定中讨论。在该章节的示例中演示了当你解析自动绑定候选者时,@Qualifier
注解的使用和自定义限定符注解来提供详细控制。因为那些示例是基于XML bean定义,通过使用在XML中bean
元素的qualifier
或者meta
子元素在候选者bean定义上提供限定符原数据。当依赖类路径扫描来自动检测组件时,你可以在候选者类上使用类型级别的注解来提供限定符原数据。以下三个示例演示了此技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
跟大多数基于注解替代品一样,注意注解原数据时绑定到类定义本身,而且使用XML允许多个相同类型的bean在他们限定符原数据中提供变体,因为在每个实例提供该原数据,而不是每一个类。
Spring提供对JSR-330标准注解(DI)的支持。这些注解与Spring注解一样的方式被扫描。要使用他们,你需要在你的类路径中存在相关的jar包。
如果你使用Maven,
jakarta.inject
artifact 在标准的maven仓库(https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/)是可用的。你可以将以下依赖添加到文件pom.xml中:<dependency> <groupId>jakarta.injectgroupId> <artifactId>jakarta.inject-apiartifactId> <version>2.0.0version> dependency>
@Named
和@Inject
依赖注入代替@Autowired
,你可以使用@jakarta.inject.Inject
,如下:
import jakarta.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 jakarta.inject.Inject;
import jakarta.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 jakarta.inject.Inject;
import jakarta.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) {
// ...
}
}
@Named
和@ManagedBean
: 标准的等价于@Component
注解代替@Component
,你可以使用@jakarta.inject.Named
或者jakarta.annotation.ManagedBean
,正如以下示例所示:
@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 jakarta.inject.Inject;
import jakarta.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当你使用@Named
或者@ManagedBean
,你可以和你与使用Spirng注解时一样的方式使用组件扫描,正如以下示例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
与
@Component
相反,JSR-330的@Named
和JSR-250的@ManagedBean
注解是不能组合的。你应该使用Spring的原型模式用于构建自定义组件注解。
当你使用标准注解时,你应该知道一些显著的特性是不可用的,正如以下表格所示:
Spring | jakarta.inject.* | jakarta.inject 限制条件 / 说明 |
---|---|---|
@Autowired | @Inject | @Inject 没有‘required’属性。可以使用Java8的Optional 代替 |
@Component | @Named / @ManagedBean | JSR-330没有提供组合模式,识别命名组件只有一种方式 |
@Scope(“singleton”) | @Singleton | JSR-330默认的作用域像Spring的prototype 。但是,为了与Spirng的默认保持一致,在Spring容器中声明的JSR-330bean默认是singleton 。为了使用不同于singleton 的作用域,你应该使用Spring的@Scope 注解。jakarta.inject 也提供了jakarta.inject.Scope 注解:但是,这只是打算用于创建自定义注解。 |
@Qualifier | @Qualifier / @Named | jakarta.inject.Qualifier 只是一个用于构建自定义限定符的元注解。具体的String 限定符(像Spring带有值的@Qualifier )可以通过jakarta.inject.Named 关联。 |
@Value |
- | 没有等价的 |
@Lazy | - | 没有等价的 |
ObjectFactory | Provider | jakarta.inject.Provider 是Spring的ObjectFactory 的直接替代物,仅使用较短的get() 方法名称。它也可以和Spring的@Autowired 组合使用或者使用非注解的构造器和setter方法。 |
此章节涵盖在你的代码中如何使用注解来配置Spring容器。它包含以下主题:
@Bean
和@Configuration
AnnotationConfigApplicationContext
实例化Spring容器@Bean
注解@Configuration
注解PropertySource
Abstraction@Bean
和@Configuration
在Spring的Java配置支持的核心构建是带有@Configuration
注解的类和带有@Bean
注解的方法。
@Bean
注解是用于识别方法实例化、配置并初始化一个新对象,其被Spring IoC容器管理。跟Spring的
XML配置类似,@Bean
注解与
元素扮演相同的角色。你可以与任何Spring @Component
一起使用@Bean
注解的方法。但是他们经常与@Configuration
bean一起使用。
使用@Configuration
注解一个类表明他的核心目的是作为bean定义的源。此外,@Configuration
类通过调用在相同类中的其他@Bean
方法来定义内部bean依赖。最简单的@Configuration
类如下:
@Configuration
public class AppConfig {
@Bean
public MyServiceImpl myService() {
return new MyServiceImpl();
}
}
前面的AppConfig
类等价于以下Spring
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
beans>
全量@Configuration vs “精简”@Bean模式? 当在没有使用
@Configuration
注解的类内部声明@Bean
方法,他们被称为以精简模式处理。在@Component
中或者甚至在普通的旧的类中声明的bean方法被认为是“精简的”,包含类的主要目的不同,而@Bean
方法在那里有额外的好处。例如,服务组件可以通过在每一个合适的组件类上额外的@Bean
方法暴露容器的管理视图。在这样的场景中,@Bean
方法是通用的工厂方法机制。
不像全量@Configuration
,精简@Bean
方法不能声明内部bean依赖。相反,他们操作的是包含他们组件内部的状态,以及他们可能声明的参数(可选)。因此,这样的@Bean
方法应该不会调用其他@Bean
方法。每一个这样的方法实际只是特定bean引用的工厂方法,没有人话特殊的运行时语义。负面影响是没有CGLIB子类必须在运行时应用,所以在类设计方面没有限制(即:包含的类可以是final,等等)。
在常见场景中,@Configuration
类内部将声明的@Bean
方法,以确保始终使用“全量”模式并且方法之间引用因此被重定向到容器的生命周期管理。这就避免了通过常规Java调用意外地调用相同的@Bean
方法,这帮助减少难以察觉的bug,在“精简”模式操作时,这些bug是难以追踪的。
@Bean
和@Configuration
注解在随后的章节中会深入讨论。但是,首先我们介绍通过多种方法使用基于Java配置创建spring容器。
AnnotationConfigApplicationContext
实例化Spring容器以下章节记录Spring的AnnotationConfigApplicationContext
,从Spring 3.0引入。多种多样的ApplicationContext
实现有能力不仅仅接收@Configuration
类作为输入,还有普通的@Component
类和JSR-330 元数据注解的类。
当@Configuration
类作为输入提供,@Configuration
类本身被注册为bean定义并且类内部所有声明的@Bean
方法也会注册为bean定义。
当提供@Component
和JSR-330类时,他们注册为bean定义,并且假设在这些类中需要使用像@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
使用依赖注入注解,例如@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") ①
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
包内声明AppConfig
(或者任何包底下),在调用scan()
期间获取。在refresh()
,所有它的@Bean
方法被处理并在容器内部注册为bean定义。
AnnotationConfigWebApplicationContext
的web应用程序的支持AnnotationConfigApplicationContext
的WebApplicationContext
变体和AnnotationConfigWebApplicationContext
一起可用。当配置Spring ContextLoaderListener
servlet监听器、Spring MVC DispatcherServlet
等等时,你可以使用这个实现。以下web.xml
片段配置了一个典型的Spring MVC web应用程序(注意,contextClass
的context-param
和init-param
的使用):
<web-app>
<context-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.acme.AppConfigparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.acme.web.MvcConfigparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/app/*url-pattern>
servlet-mapping>
web-app>
对于编程用例,
GenericWebApplicationContext
也可以用于AnnotationConfigWebApplicationContext
的替代方案。请查看GenericWebApplicationContext
javajoc了解详情。
@Bean
注解@Bean
是方法级别注解和XML
元素的直接模拟。此注解支持
提供的一些属性,例如:
name
你可以在@Configuration
注解或者@Component
注解的类中使用@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。这允许通过在默认的方法上实现带有bean定义接口组成bean配置。
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
你也可以使用一个接口(或者基类)返回类型声明你的@Bean
,正如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,限制了对指定接口类型(TransferService
)的预先类型预测的可见性。然后,只有受影响的单例bean实例化之后,才使用容器已知的完整类型(TransferServiceImpl
)。非延迟单例bean根据他们声明的顺序进行实例化,所以你可以看到不同的类型匹配结果,取决于什么时候另外一个组件尝试通过非声明类型(例如@Autowired TransferServiceImpl
)匹配,只有在transferService
已经实例化后解析。
如果你总是通过声明服务接口引用你的类型,你的
@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
,通过容器调用他们各自的方法。
标准的*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();
}
}
默认情况下,使用具有公共的
close
或者shutdown
方法的Java配置定义的bean将自动使用销毁回调调用。如果你有公共的close
或者shutdown
方法并且当容器关闭的时候,你不希望它被调用,你可以添加@Bean(destroyMethod = "")
到你的bean定义来禁用默认的(inferred)
模式。
对于使用JNDI获取到的资源,你可能希望默认情况下这么做,因为它的生命周期在应用程序之外被管理。特别是,对于DataSource
确保总是这样做,因为众所周知,在Jakarta 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,你可以做你想要使用你的对象的任何事情并不总是需要依赖容器的生命周期。
Spring包含@Scope
注解,所以你可以指定bean的作用域。
@Scope
注解你可以指定使用@Bean
注解定义的bean有一个指定的作用域。你可以使用任意标准的在Bean 作用域章节指定的作用域。
默认的作用域是singleton
,但是你可以使用@Scope
注解覆盖,正如以下示例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope
和scoped-proxy
Spring提供了通过作用域代理处理作用域的依赖的一种方便的方式。当使用XML配置时创建这样的代理最容易的方式是
元素。使用@Scope
以Java方式配置你的bean使用proxyMode
属性提供等价支持。默认值是ScopedProxyMode.DEFAULT
,其通常表明不应该创建作用域代理,除非不同的默认值已经在组件扫描指令级别配置。你可以指定ScopedProxyMode.TARGET_CLASS
,ScopedProxyMode.INTERFACES
,ScopedProxyMode.NO
。
如果你将从XML引用文档的作用域代理实例转为我们的使用Java的@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
方法的名称作为结果bean的名称。但是,此功能可以使用name
属性被覆盖,正如以下示例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
正如在命名bean所描述的,一个单例的bean赋予多个名称是可取的,不同的是被称为别名。@Bean
注解name
属性为此接收String数组。以下示例展示如何为bean设置多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
有时,提供一个bean的更加详细的文本的描述是有帮助的。当为监控目的暴露(可能通过JMX)bean时这可能特别有用。
要对@Bean
添加一个描述,你可以使用@Description
注解,正如以下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@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依赖的方法只有当
@Bean
方法在@Configuration
类内部才会生效。你不能通过使用普通的@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()
方法,抽象方法查找一个新的(原生的)命名行对象。以下示例展示了如何这样做:
@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();
}
}
}
考虑以下示例,展示了@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();
}
}
clinetDao()
在clientService1()
中调用一次,在clientService2()
中调用一次。因为此方法创建一个新的ClientDaoImpl
实例并返回它,你通常希望有两个实例(每一个服务一个)。这当然是有问题的:在Spring中,默认情况下初始化的bean有singleton
作用域。这就是魔法的由来:所有的@Configuration
类在启动时使用CGLIB
是子类化。在子类中,子方法调用父方法并创建新的实例之前优先检查容器是否存在缓存(作用域的)的bean。
此行为可能会根据你的bean的作用域有一些差异。这里我们讨论的是单例。
将CGLIB包添加到你的类路径是不必要的,因为CGLIB类已经在
org.springframework.cglib
下重新打包并且直接包含在spring-core
JAR内。
由于CGLIB在运行时动态添加特性的事实,因此存在几个限制。特别是配置类必须不能是final的。但是在配置类上允许任何构造器,包括使用
@Autowired
或者默认注入单个非默认构造器声明。如果你希望避免任何CGLIB强加的限制,考虑在非
@Configuration
类(例如普通的@Component
类代替)声明的@Bean
方法或者使用@Configuration(proxyBeanMethods = false)
注解你的配置类。@Bean
方法之间的跨方法调用不会被拦截,所以你必须只能依赖在构造器或者方法级别的依赖注入。
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
类。
从Spring Framework 4.2开始,
@Import
也支持引用常规的component类,类似于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
定义要特别小心。这些通常应该被声明为statice @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
类中的构造器注入只有从Spring Framework 4.3开始支持。而且请注意如果目标bean只定义了一个构造器,则不需要指定@Autowired
。
为了方便导航使用了全限定引用的bean。
在前面的场景中,使用@Autowired
效果很好并且提供了所需的模块化,但是完全确定声明的自动装配bean定义的位置仍然是有些模棱两可。例如,作为一个开发查看ServiceConfig
,你如何知道@Autowired AccountRepository
在哪里声明?在代码中是不显示的,但可能很好。记住Eclipse的Spring工具提供了可以呈现展示每一个东西如何被绑定的图表,这可能正是你所需要的。而且你的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());
}
}
在前面的情况中,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之前创建,而不是当前bean直接依赖关系)。
@Configuration
类或者@Bean
基于一些任意系统状态,有条件地禁用和启用一个完整的@Configuration
类或者甚至个别@Bean
方法是非常有用的。这种一个常见的示例是使用@Profile
注解只有当在Spring的Environment
中(请查看bean定义profile了解详情)特定的profile已经启用来激活bean。
@Profile
注解通过使用更加灵活的称为@Conditional
注解实现的。@Conditional
注解表明在注册@Bean
之前应该咨询特定的org.springframework.context.annotation.Condition
实现。
Condition
接口的实现提供了返回true
或者false的matches(…)
方法。例如,以下列出来展示实际用于@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;
}
请查看@Conditional
javadoc了解更多详情。
Spring的@Configuraiton
类支持并不打算100%完全替代Spring XML。一些工具,例如Spring XML命名空间,仍然是配置了容器的理想方法。这种情况下XML是方便的而且必须的,你有一种选择:要么使用以XML
为中心的方式实例化容器,例如使用ClassPathXmlApplicationContext
,要么使用以Java
为中心的方式,例如使用AnnotationConfigApplicationContext
,然后按需根据@ImportResource
注解来引入XML。
@Configuration
类以XML为中心的使用从XML启动Spring容器可能更合适,并以特别的方式包含@Configuration
类。例如,在使用Spring XML的大的已存在的代码库中,根据需要创建@Configuration
类更容易,并从现有的XML文件包含他们。稍后在本章节中,我们涵盖在此种以“XML为中心”场景使用@Configuration
类的选项。
声明@Configuration
类作为普通的String 元素
记住在容器中@Configuration
最终是bean定义。在一系列示例中,我们创建了命名为AppConfig
的@Configuration
类并在system-test-config.xml
内部作为
定义包含它。因为
已经开启,容器识别@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>
<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甚至只通过类型进行自动绑定,所以一个显性的id
不强制要求。
使用来获取
@Configuration
类。
因为@Configuration
是带有@@Component
的元注解,@Configuration
注解的类是组件扫描自动是候选者。与前面示例中所描述相同的场景一样使用,我们可以重定义system-test-config.xml
来利用组件扫描。注意,这种情况,我们不需要显性声明
,因为
启用了相同的功能。
以下示例展示了修改的system-test-config.xml
文件:
<beans>
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
beans>
@Configuration
类为中心的带有@ImportResource
的XML使用。在应用程序中,@Configuration
类是配置容器的核心机制,但可能仍然有必要至少使用一些XML。在这些场景中,你可以使用@ImportResource
并定义你需要的XML。这样做实现“以Java为中心”方式来配置容器并将XML保持最低限度。以下示例(其包含一个配置类,XML文件定义了一个bean,属性文件然后是main
类)展示了如何使用@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
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);
// ...
}
Environment
接口是一个在容器中抽象集成的,它对应用程序环境两个关键方面进行建模:profiles和properties。
profile是容器将要注册的bean定义一个命名的,逻辑分组,只有给定的profile是活跃的。bean可以被分配到一个profile无论在XML定义还是注解。与profile相关的Environment
对象的作用是确定哪一个profile是当前激活的,并且默认情况下,哪个profiles(如果存在)应该是活跃的。
差不多所有应用程序中,Properties扮演重要的角色并且可以有各种各样的来源:properties文件,JVM系统属性,系统环境变量,JNDI,servlet上下文属性,专门的Properties
对象,Map
对象等等。与properties相关的Environment
对象的作用是提供用户一个方便的服务接口来配置属性源并解析属性。
bean定义profile提供了在核心容器中的一种机制:在不同的环境中允许不同bean的注册。单词,“environment(环境)”可以意味着不同的用户对应不同的东西,此特性可以在很多场景有帮助,包含:
考虑第一个用例,在一个特定的应用程序中,其需要一个数据源。在测试环境中,配置可能类似于以下:
@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定义的profile是核心容器特性所提供的此问题的一种解决方案。
如果我们总结在前面特定环境bean定义的示例的用例,我们最终需要在某上下文中注册bean定义,而不是在其他上下文中。你可以说你希望在场景A中注册某一个bean定义的profile,在场景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(destroyMethod = “”)禁用默认的销毁方法推理。
正如早前提到的,使用
@Bean
方法,你通常选择使用程序化的JNDI查找,通过使用Spring的JndiTemplate
/JndiLocatorDelegate
助手或者早前展示的直接的JNDI的InitialContext
用法,并不是JndiObjectFactoryBean
变体,它会强制你声明FactoryBean
类型的返回类型。
profile字符串可能包含一个简单的profile名称(例如,production
)或者profile表达式。profile表达式允许更加复杂的profile逻辑来表示(例如,production & us-east
)。在profile表达式中支持以下操作:
!
: profile的逻辑NOT
.&
: profile的逻辑AND
.|
: profile的逻辑OR
.在没有使用括号的情况下,你不能将
&
和|
操作混合。例如,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
注解是跳过的,除非一个或者多个指定的profile被激活。如果@Component
或者@Configuration
类使用@Profile({"p1","p2"})
,该类不会被注册或者被处理,除非proflies ‘p1’或者’p2’已经激活。如果给定的profile以NOT操作(!
)开头,只有profile没有激活的才会注册注解元素。例如给定的@Profile({"p1", "!p2"})
,如果profile ‘p1’激活或者profile ‘p2’没有激活则注册将会发生。
@Profile
也可以在方法级别声明只包含配置类特有的bean(例如,用于特定bean的可替换变体。),正如以下示例所示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") ①
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") ②
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
①standaloneDataSource
方法只有在development
配置文件中有效
②jndiDataSource
方法只有在production
配置文件中有效
在
@Bean
方法上使用@Profile
,一种特殊的场景可能适用:在相同的Java方法名称的重载的@Bean
方法这种情况(类似于构造器重载),@Profile
条件需要在所有重载方法上一致声明。如果条件是不一致的,重载的方法中只有第一个声明的条件有关系。因此,@Profile
不能用于选择一个具有特定参数签名的重载的方法。在创建时,对于相同bean的所有工厂方法之间的解决方案按照Spring的构造器解决方案算法。
如果你想要使用不同的profile条件定义可替换的bean,使用不同的Java方法名称,通过使用@Bean
名称属性指向相同的bean名称,正如以上示例所示。如果参数签名都是相同的(例如,所有的变体存在无参工厂方法),首先在有效的Java类中表示这样的安排的唯一方式(因为只能存在一个特定名称和参数签名的方法)。
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所对应的配置不支持profile表达式。但是,它可以使用
!
操作来否定一个配置。通过内嵌的profile应用一个逻辑的"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="..."> <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
profile同时被激活则暴露dataSource
。
现在,我们已经更新了我们的配置,我们仍然需要构建Spring哪个配置文件需要激活。此刻,如果我们启动了我们简单的应用程序,我们将看到NoSuchBeanDefinitionException
抛出,因为容器不能找到名称为dataSource
的Spring bean。
有几种方式可以做到激活profile,但是最简单的是根据Environment
API 程序化做到它,通过ApplicationContext
它是可用的。以下示例展示了如何做到:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
除此之外,你也可以通过spring.profiles.active
属性声明式地激活profile,其通过系统环境变量指定。JVM系统属性,在web.xml
中servlet 上下文参数,甚至作为JNDI的一个入口(请查看PropertySource Abstraction)。在集成测试中,在spring-test
模块中可以通过@ActiveProfiles
注解声明激活profile(请查看带有环境profiles的上下文配置)。
注意:profiles不是一个“或者-或者”的观点。你可以一次激活多个profiles。编程方式中,你可以为setActiveProfiles()
方法提供多个profile名称,其接受String...
可变参数。以下示例激活多个profiles:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明式,spring.profiles.active
可以接收一个逗号分隔的profile名称列表,正如以下示例所示:
-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提供默认定义的方式。如果任意profiles启用,默认的profile不用应用。
你可以通过使用在Environment
的setDefaultProfiles()
修改默认配置的名称,或者通过声明方式,使用spring.profiles.default
属性。
PropertySource
AbstractionSpring的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上下文参数,如果JNDI可用还包含JndiPropertySource
。
具体来说,当你使用StandardEnvironment
,调用env.containsProperty("my-property")
,如果my-property
系统属性或者my-property
环境变量在运行时存在,则返回true。
执行的搜索是分层的。默认情况下,系统属性优先级高于环境变量。所以,如果
my-property
属性在两个地方设置,调用env.getProperty("my-property")
期间,系统属性“获胜”并返回。注意,属性值不会合并而是完全被前一个条目覆盖。
对于一个常见的StandardServletEnvironment
,以下是完整分层结构,最高优先级条目在最上面:
- ServletConfig参数(例如,如果适用的话,
DispatcherServlet
上下文的情况)- ServletContent参数(web.xml context-param条目)
- JNDI环境变量(
java:comp/env/
条目)- JVM系统属性(
-D
命令行参数)- JVM系统环境(操作系统环境变量)
最重要的是,完整的机制是可配置的。假如你有一个自定义的属性源,你想要将它集成到这个搜索。要这么做,实现并实例化你自己的PropertySource
并将它添加到当前的Environmet
的PropertySources
集。以下示例展示如何这么做:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上述代码中,MyPropertySource
已经添加到搜索中的最高优先级。如果它包含my-property
属性,检测属性并返回,以支持任何其他PropertySource
的任何my-property
属性。MutablePropertySources
API暴露多个方法,允许对属性源集进行精准操作。
@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
。
@PropertySource
注解是可重复的,根据Java8规范。但是所有的@PropertySource
注解需要在相同级别上声明,或者直接在配置类或者做相同的自定义注解内作为元注解。将直接注解和元注解混合是不推荐的,因为直接注解有效地覆盖元注解。
历史上,元素中占位符的值只能根据JVM系统属性或者环境变量解析。现在情况已经不同了。因为Environment
抽象已经在整个容器中集成,所以很容易通过它路由占位符解析。这意味着你可以以你喜欢的任何方式配置解析过程。你可以通过系统属性和环境变量修改查找优先级或者完全移除他们。你可以酌情将你自己的属性源添加到混合中。
具体地,以下语句可以工作,无论customer
属性定义在哪里,只要它在Environment
可用:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
beans>
LoadTimeWeaver
LoadTimeWeaver
用于Spring在类加载到虚拟机时,将他们进行动态转换。
要启用载入时改造,你可以将@EnableLoadTimeWeaving
添加到你的@Configuration
类中的一个,正如以下示例所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或者,对于XML 配置,你可以使用context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
beans>
一旦对ApplicationContext
配置了,任何在ApplicationContext
内的bean可能实现LoadTimeWeaverAware
,因此接收了一个加载时改造实例的引用。在与Spring的JPA支持结合时比较有用,加载时改造对于JPA类转换是有必要的。可以查阅LocalContainerEntityManagerFactoryBean
javadoc了解更多详情。对于更多关于AspectJ加载时改造,请查看Load-time Weaving with AspectJ in the Spring Framework
。