Spring Framework核心技术(Core Technologies)-(二)

1.10.7 为自动检测的组件提供作用域

与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>

1.10.8. 使用注解提供Qualifier元数据

@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在他们限定符原数据中提供变体,因为在每个实例提供该原数据,而不是每一个类。

1.11. 使用JSR 330标准注解

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>

1.11.1. 使用@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) {
		// ...
	}
}

1.11.2. @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的原型模式用于构建自定义组件注解。

1.11.3. JSR-330 标准注解的限制

当你使用标准注解时,你应该知道一些显著的特性是不可用的,正如以下表格所示:

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方法。

1.12. 基于java的容器配置

此章节涵盖在你的代码中如何使用注解来配置Spring容器。它包含以下主题:

  • 基本概念:@Bean@Configuration
  • 通过使用AnnotationConfigApplicationContext实例化Spring容器
  • 使用@Bean注解
  • 使用@Configuration注解
  • 组合基于Java的配置
  • bean定义Profiles
  • PropertySource Abstraction
  • 使用@PropertySource
  • 语句中占位符解决方案

1.12.1. 基本概念:@Bean@Configuration

在Spring的Java配置支持的核心构建是带有@Configuration注解的类和带有@Bean注解的方法。

@Bean注解是用于识别方法实例化、配置并初始化一个新对象,其被Spring IoC容器管理。跟Spring的XML配置类似,@Bean注解与元素扮演相同的角色。你可以与任何Spring @Component一起使用@Bean注解的方法。但是他们经常与@Configurationbean一起使用。

使用@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容器。

1.12.2. 通过使用AnnotationConfigApplicationContext实例化Spring容器

以下章节记录Spring的AnnotationConfigApplicationContext,从Spring 3.0引入。多种多样的ApplicationContext实现有能力不仅仅接收@Configuration类作为输入,还有普通的@Component类和JSR-330 元数据注解的类。

@Configuration类作为输入提供,@Configuration类本身被注册为bean定义并且类内部所有声明的@Bean方法也会注册为bean定义。

当提供@Component和JSR-330类时,他们注册为bean定义,并且假设在这些类中需要使用像@Autowired或者@Inject这样的元数据。

1.12.2.1. 简单的构造

使用当实例化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();
}

以上示例假设MyServiceImplDependency1,Dependency2使用依赖注入注解,例如@Autowired

1.12.2.2. 使用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元注解,所以他们是组件扫描的候选者。在前面的示例中,假设AppConfigcom.acme包内声明AppConfig(或者任何包底下),在调用scan()期间获取。在refresh(),所有它的@Bean方法被处理并在容器内部注册为bean定义。

对使用AnnotationConfigWebApplicationContext 的web应用程序的支持

AnnotationConfigApplicationContextWebApplicationContext变体和AnnotationConfigWebApplicationContext一起可用。当配置Spring ContextLoaderListener servlet监听器、Spring MVC DispatcherServlet等等时,你可以使用这个实现。以下web.xml片段配置了一个典型的Spring MVC web应用程序(注意,contextClasscontext-paraminit-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了解详情。

1.12.3. 使用@Bean注解

@Bean是方法级别注解和XML 元素的直接模拟。此注解支持提供的一些属性,例如:

  • init-method
  • destroy-method
  • autowiring
  • name

你可以在@Configuration注解或者@Component注解的类中使用@Bean注解。

声明一个bean

要声明一个bean,你可以使用@Bean注解一个方法。你可以使用这个方法在ApplicationContext内注册一个与方法返回值指定的类型一致bean定义。默认情况下,bean名称与方法名一致。以下示例展示了@Bean方法声明:

@Configuration
public class AppConfig {

	@Bean
	public TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

上面的示例完全等价于以下Spring XML:

<beans>
	<bean id="transferService" class="com.acme.TransferServiceImpl"/>
beans>

两个声明使名称为transferService的bean在ApplicationContext中可用,绑定到TransferServiceImpl类型的对象实例,正如以下文本逻辑展示:

transferService -> com.acme.TransferServiceImpl

你也可以使用默认的方法来定义bean。这允许通过在默认的方法上实现带有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注解的方法可以存在任意数量的参数,参数描述了构建该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-methoddestroy-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,你可以做你想要使用你的对象的任何事情并不总是需要依赖容器的生命周期。

指定bean作用域

Spring包含@Scope注解,所以你可以指定bean的作用域。

使用@Scope注解

你可以指定使用@Bean注解定义的bean有一个指定的作用域。你可以使用任意标准的在Bean 作用域章节指定的作用域。

默认的作用域是singleton,但是你可以使用@Scope注解覆盖,正如以下示例所示:

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}
@Scopescoped-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方法的名称作为结果bean的名称。但是,此功能可以使用name属性被覆盖,正如以下示例所示:

@Configuration
public class AppConfig {

	@Bean("myThing")
	public Thing thing() {
		return new Thing();
	}
}
bean别名

正如在命名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描述

有时,提供一个bean的更加详细的文本的描述是有帮助的。当为监控目的暴露(可能通过JMX)bean时这可能特别有用。

要对@Bean添加一个描述,你可以使用@Description注解,正如以下示例所示:

@Configuration
public class AppConfig {

	@Bean
	@Description("Provides a basic example of a bean")
	public Thing thing() {
		return new Thing();
	}
}

1.12.4. 使用@Configuration注解

@Configuration是类级别注解表明一个对象是bean定义的源。@Configuration类通过@Bean注解的方法声明bean。调用@Configuration类的@Bean方法也可以使用定义内部bean依赖。请查看基本概念:@Bean@Configuration了解常见介绍。

注入内部bean依赖

当bean依赖另外一个bean,表达依赖关系就像一个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();
		}
	}
}
关于基于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();
	}
}

clinetDao()clientService1()中调用一次,在clientService2()中调用一次。因为此方法创建一个新的ClientDaoImpl实例并返回它,你通常希望有两个实例(每一个服务一个)。这当然是有问题的:在Spring中,默认情况下初始化的bean有singleton作用域。这就是魔法的由来:所有的@Configuration类在启动时使用CGLIB是子类化。在子类中,子方法调用父方法并创建新的实例之前优先检查容器是否存在缓存(作用域的)的bean。

此行为可能会根据你的bean的作用域有一些差异。这里我们讨论的是单例。

将CGLIB包添加到你的类路径是不必要的,因为CGLIB类已经在org.springframework.cglib下重新打包并且直接包含在spring-coreJAR内。

由于CGLIB在运行时动态添加特性的事实,因此存在几个限制。特别是配置类必须不能是final的。但是在配置类上允许任何构造器,包括使用@Autowired或者默认注入单个非默认构造器声明。

如果你希望避免任何CGLIB强加的限制,考虑在非@Configuration类(例如普通的@Component类代替)声明的@Bean方法或者使用@Configuration(proxyBeanMethods = false)注解你的配置类。@Bean方法之间的跨方法调用不会被拦截,所以你必须只能依赖在构造器或者方法级别的依赖注入。

1.12.5. 组合基于Java的配置

Spring的基于Java配置特性允许你组合注解,可以减少你的配置复杂性。

使用@Import注解

比较像在Spring XML内部使用的元素以帮助多模块的配置,@Import注解允许加载来自另外一个配置类的@Bean定义,正如以下示例所示:

@Configuration
public class ConfigA {

	@Bean
	public A a() {
		return new A();
	}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

	@Bean
	public B b() {
		return new B();
	}
}

现在,当实例化上下文时,并不是需要同时指定ConfigA.classConfigB.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使用BeanPostProcessorBeanFactoryPostProcessor定义要特别小心。这些通常应该被声明为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(…​)方法。例如,以下列出来展示实际用于@ProfileCondition实现。

@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了解更多详情。

组合Java和XML配置

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引用它,并不太可能通过名称显性地从容器中抓取。类似地,DataSourcebean甚至只通过类型进行自动绑定,所以一个显性的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);
	// ...
}

1.12.6. 环境抽象

Environment接口是一个在容器中抽象集成的,它对应用程序环境两个关键方面进行建模:profiles和properties。

profile是容器将要注册的bean定义一个命名的,逻辑分组,只有给定的profile是活跃的。bean可以被分配到一个profile无论在XML定义还是注解。与profile相关的Environment对象的作用是确定哪一个profile是当前激活的,并且默认情况下,哪个profiles(如果存在)应该是活跃的。

差不多所有应用程序中,Properties扮演重要的角色并且可以有各种各样的来源:properties文件,JVM系统属性,系统环境变量,JNDI,servlet上下文属性,专门的Properties对象,Map对象等等。与properties相关的Environment对象的作用是提供用户一个方便的服务接口来配置属性源并解析属性。

1.12.6.1. bean定义Profiles

bean定义profile提供了在核心容器中的一种机制:在不同的环境中允许不同bean的注册。单词,“environment(环境)”可以意味着不同的用户对应不同的东西,此特性可以在很多场景有帮助,包含:

  • 在开发阶段使用内存数据源,而在QA或者生产阶段从JNDI查找相同的数据源。
  • 只有当部署一个应用程序到一个高性能环境时,注册监控底层架构。
  • 为客户A和客户B的部署注册自定义bean实现。

考虑第一个用例,在一个特定的应用程序中,其需要一个数据源。在测试环境中,配置可能类似于以下:

@Bean
public DataSource dataSource() {
	return new EmbeddedDatabaseBuilder()
		.setType(EmbeddedDatabaseType.HSQL)
		.addScript("my-schema.sql")
		.addScript("my-test-data.sql")
		.build();
}

现在考虑如何将此应用程序部署到一个QA或者生产环境,假设应用程序的数据源使用生产的应用程序服务器的JNDI目录注册。我们的dataSourcebean现在看起来像以下列表:

@Bean(destroyMethod = "")
public DataSource dataSource() throws Exception {
	Context ctx = new InitialContext();
	return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

问题是如何在基于当前环境上使用这两种变体之间进行切换。随着时间的推移,Spring用户已经设计了多种方式做到这种方式,通常依赖系统环境变量和包含${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 bean定义profiles

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>

在上面的示例中,如果productionus-eastprofile同时被激活则暴露dataSource

激活一个Profile

现在,我们已经更新了我们的配置,我们仍然需要构建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不用应用。

你可以通过使用在EnvironmentsetDefaultProfiles()修改默认配置的名称,或者通过声明方式,使用spring.profiles.default属性。

1.12.6.2. PropertySource Abstraction

Spring的Environment抽象提供了在属性源配置的层级的搜索操作。考虑以下列表:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

在上面的代码片段,我们看到了一个高级别方式访问Spring,my-property属性是否在当前环境被定义。要回答这个问题,Environment对象在PropertySource对象集之上提供了搜索。PropertySource是一个在key-value对的源之上的简单的抽象,Spring的StandardEnvironment使用两个PropertySource对象配置,一个表示JVM系统属性集合(System.getProperties()),一个表示系统环境变量集合(System.getenv())。

这些默认的属性源是为StandardEnvironment提供的,用于独立的应用程序。StandardServletEnvironment使用额外的默认属性源填充,包含servlet配置,servlet上下文参数,如果JNDI可用还包含JndiPropertySource

具体来说,当你使用StandardEnvironment,调用env.containsProperty("my-property"),如果my-property系统属性或者my-property环境变量在运行时存在,则返回true。

执行的搜索是分层的。默认情况下,系统属性优先级高于环境变量。所以,如果my-property属性在两个地方设置,调用env.getProperty("my-property")期间,系统属性“获胜”并返回。注意,属性值不会合并而是完全被前一个条目覆盖。
对于一个常见的StandardServletEnvironment,以下是完整分层结构,最高优先级条目在最上面:

  1. ServletConfig参数(例如,如果适用的话,DispatcherServlet上下文的情况)
  2. ServletContent参数(web.xml context-param条目)
  3. JNDI环境变量(java:comp/env/条目)
  4. JVM系统属性(-D命令行参数)
  5. JVM系统环境(操作系统环境变量)

最重要的是,完整的机制是可配置的。假如你有一个自定义的属性源,你想要将它集成到这个搜索。要这么做,实现并实例化你自己的PropertySource并将它添加到当前的EnvironmetPropertySources集。以下示例展示如何这么做:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上述代码中,MyPropertySource已经添加到搜索中的最高优先级。如果它包含my-property属性,检测属性并返回,以支持任何其他PropertySource的任何my-property属性。MutablePropertySourcesAPI暴露多个方法,允许对属性源集进行精准操作。

1.12.6.3. 使用@PropertySource

@PropertySource注解提供一个方便的声明式机制来添加一个PropertySource到Spring的Environment
给定一个称为app.properties的文件,其包含键值对testbean.name=myTestBean,以下@Configuration类以这样的方式使用@PropertySource,调用testBean.getName()返回myTestBean:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

任何存在于@PropertySource资源定位的${…​}占位符将根据已经在环境中注册的属性源集被解析。正如以下示例所示:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

假设my.placeholder已经在注册的属性源其中一个存在(例如,系统属性或者环境变量),占位符解析为相对应的值。如果不存在,然后default/path用于默认值。如果没有默认值指定并且属性不能被解析,抛出IllegalArgumentException

@PropertySource注解是可重复的,根据Java8规范。但是所有的@PropertySource注解需要在相同级别上声明,或者直接在配置类或者做相同的自定义注解内作为元注解。将直接注解和元注解混合是不推荐的,因为直接注解有效地覆盖元注解。

1.12.6.4. 语句中占位符解决方案

历史上,元素中占位符的值只能根据JVM系统属性或者环境变量解析。现在情况已经不同了。因为Environment抽象已经在整个容器中集成,所以很容易通过它路由占位符解析。这意味着你可以以你喜欢的任何方式配置解析过程。你可以通过系统属性和环境变量修改查找优先级或者完全移除他们。你可以酌情将你自己的属性源添加到混合中。

具体地,以下语句可以工作,无论customer属性定义在哪里,只要它在Environment可用:

<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
beans>

1.13 注册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

你可能感兴趣的:(spring,java,后端)