第二篇开始啦!因为字数限制,本来想把第一章写在一篇文章里面的(*~*)
出现的晦涩词汇的理解
Aware:Aware系的接口,带有入侵性,可以让实现它的Bean拥有从容器获取Bean基本信息(比如,名字,所属类)的能力,类似于给这个Bean开了VIP会员特权。
基于Java的容器配置
本节主要介绍如何在你的Java代码里面使用注解来配置Spring容器。
基本概念:@Bean和@Configuration
Spring的新的Java配置的支持的核心是基于@Configuration类和基于@Bean的方法。
@Bean注解表明这个方法会实例化,配置,并初始化一个由Spring IOC容器管理的Bean。这个注解通常和@Configuration搭配使用,虽然也可以和任意的@Component一起使用。
@Configuration标注的类表明此类的主要作用作为Bean定义的一个源。它还可以通过调用类里面的其他@Bean方法来定义Bean之间的依赖关系。最简单的用法如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
完整的@Configuration模式和“精简的”@Bean模式的对比:
当一个@Bean方法未在@Configuration类里面声明时,那么可以称这个@Bean方法的模式是精简的模式,声明在@Component组件类或普通的类里面的@Bean方法都可以被称作“精简”模式。某些服务组件可能会在每个可用的组件类上,通过一个附加的@Bean方法把管理视图暴露给容器,在这种情况下,@Bean方法更像是工厂方法。
不同于@Configuration,精简模式的@Bean没法定义Bean之间的依赖关系。取而代之的是,它们在其包含组件的内部状态上进行操作,或者是它们声明的参数。这样的@Bean方法不用当调用其他任何的@Bean方法,这样的方法,从字面上来看,应该只是特定Bean引用的工厂方法。而没有任何运行时的语义状态。负面影响就是,无法在运行时应用CGLIB代理,所以类的设计也就没有限制(比方说,定义为final)。
通常情况下,@Bean方法应该在@Configuration类里面定义,确保可以使用"完整的"模式。这样的话,跨方法引用可以被重新导向至容器的生命周期管理。这样可以防止不经意间地常规Java调用方法,对于同一@Bean方法的调用,这有助于减少在“精简”模式下运行时难以跟踪的细微bug。
使用AnnotationConfigApplicationContext实例化Spring容器
AnnotationConfigApplicationContext在Spring3.0之后引入,这个丰富的类,既可以把@Configuration作为输入源,也可以把普通的@component类作为输入,或者用JSR-330注解标注的类也可以。
当@Configuration作为输入时,@Configuration类自己会作为Bean定义注册,而且类里面的所有@Bean方法也会作为Bean定义注册。
当@Component和JSR-330标准注解标注的类作为输入时,这些类会作为Bean定义注册,并且假定类的内部会在必要的时候使用诸如@Autowired和@Inject的注解。
- 简单构造
正如XML那样,你可以使用@Configuration标注的类作为输入来实例化AnnotationConfigApplicationContext,比如:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
AnnotationConfigApplicationContext的部分源码:
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
/**
* Create a new AnnotationConfigApplicationContext that needs to be populated
* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
// ...
}
/**
* Create a new AnnotationConfigApplicationContext with the given DefaultListableBeanFactory.
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
*/
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
// ...
}
/**
* Create a new AnnotationConfigApplicationContext, deriving bean definitions
* from the given component classes and automatically refreshing the context.
* @param componentClasses one or more component classes — for example,
* {@link Configuration @Configuration} classes
*/
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
// ...
}
/**
* Create a new AnnotationConfigApplicationContext, scanning for components
* in the given packages, registering bean definitions for those components,
* and automatically refreshing the context.
* @param basePackages the packages to scan for component classes
*/
public AnnotationConfigApplicationContext(String... basePackages) {
// ...
}
}
- 使用register(Class<?> ...)程序化地构建容器
你还可以使用AnnotationConfigApplicationContext的无参构造器,构造一个实例,然后调用其register(class<?> ...)方法,如下所示:
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 ...)启用组件扫描
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方法都会被注册到容器里面去。
- AnnotationConfigApplicationContext对Web应用的支持
AnnotationConfigApplicationContext是WebApplicationContext的一个变种,它的实现类——AnnotationConfigWebApplicationContext可以用于操作Web应用,比如配置SpringContextLoaderListener和Spring MVC DispatcherServlet等。
使用@Bean注解
废话不多说,看看源码先:
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
- 声明一个Bean
通过在一个方法体前面注上@Bean注解,你可以使这个方法向ApplicationContext里面注入一个Bean定义,Bean类型为方法返回值类型,Bean名称默认为方法名。如下所示:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
也可以把返回类型设置为接口类型,而返回的实际值是接口的实现类:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
这么做的话,结果就是隐藏了实际类型,可能会在自动织入或获取Bean时产生一些冲突。如果实现类实现了不止一个接口,那么可能会产生不安全隐患,所以最好还是指出特定的返回类型。
- Bean依赖
对于@Bean注解的方法,可以通过方法参数的形式指明构建此Bean所需要的依赖。类似于基于构造器的依赖注入。
- 接收生命周期回调
所有使用@Bean定义的类都可以使用普通的生命周期回调,也可以使用,JSR-250注解——@PostContruct和@PreDestroy。
同时也支持标准的Spring生命周期回调,比如:InitializingBean,DisposableBean,Lifecycle。
标准的*Aware接口(比如:BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)也被很好地支持。
@Bean也支持指定任意方法为初始化方法或销毁方法:
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配置的,且拥有public的close()或shutdown()方法的Bean,会自动导入一个销毁方法回调,如果你不希望在容器关闭时,这个销毁方法被调用的话,请这样设置:@Bean(destroyMethod="")。这种措施对于数据库数据源很有用。
上面例子的初始化方法可以这么使用:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
当你直接使用Java形式来设计你的代码时,你可以在不依赖于容器生命周期的情况下编写任意你想要的代码。
- 限定Bean作用域
直接使用@Scope注解就行。比如指定是单例还是原型:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
使用代理的形式:
// 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属性,就能替换掉默认为方法名的Bean名字:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
- Bean别名
@Bean的name属性接收一个字符数组的时候,就算设置了Bean的别名了。
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
- Bean描述
有时,为一个Bean添加描述是很有意义的,比方说对于具有监视意义的Bean,仅需简单地添加@Description注解:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
使用@Configuration注解
@Configuration定义了它所标注的类为一个Bean定义源。里面的@Bean方法定义了具体的Bean定义,同时,不同@Bean方法之间的引用也定义了Bean之间的依赖关系。
- 注入内部Bean依赖
当Bean依赖于其他的某个Bean时,表达依赖关系仅需简单地引用其他@Bean方法就行:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
注意:
这种定义依赖关系的方式,仅适用于在@Configuration定义的类内部使用,而不适用于@Component标注的普通类。
- 查找方法注入
查找方法注入是一种高级特性,我们建议尽可能少的使用。在单例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();
}
然后某个类继承了CommandManager,重写了createCommand()方法,比方说这个方法会查找一个新的command对象。如下所示:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with 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();
}
}
Spring容器管理的Bean默认为单例的,所以这里可能有问题,因为任何@Configuration类都会被CGLIB子类化,子类在构造Bean之前,会先检查容器有没有需要的Bean缓存(作用域),然后创建一个新的实例。
当然,如果你的Bean作用域不同的话,这又是另一种结果了。
组合基于Java的配置
- 使用@Import注解
@Import允许导入其他的含有@Bean方法的@Configuration类。如下所示:
@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();
}
}
这样,在实例化ApplicationContext时,仅需ConfigB.class就行了:
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);
}
在Spring Framework4.2之后,可以直接Import普通组件类,这个作用类似于AnnotationConfigApplicationContext.register()方法,这样就不用扫描基础包了,还是挺有用的。
P.s. 个人建议,不妨试着写一个总配置类,导入所有其他的配置类,然后用这个总配置类实例化ApplicationContext就行。
把依赖注入到被导入的@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作为组件@Autowired注入到另一个@Configuration类里面去。
警告:
尽可能的以简单化地方法进行注入依赖,因为@Configuration会在上下文初始化时尽可能早地被处理。所以,就像上面的例子那样,尽可能使用基于参数的注入(因为基于参数不会那么早地就要求注入依赖)。
同样,在通过@Bean处理BeanPostProcessor和BeanFactoryPostProcessor时,你应当相当小心。这些@Bean方法应当被声明为static @Bean方法,这样就不会触发他们所在的@Configuration类的实例化。否则的话,@Autowired和@Value可能会不顶用,因为@Configuration类自身可能会比AutowiredAnnotationBeanPostProcessor更早地被创建为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类和@Bean方法
有时基于系统状态,启用或禁用某一个配置类或@Bean方法是很有用的,一个很常见的例子就是@Profile注解,用来指定需要激活的环境。
@Profile注解是通过更加灵活的@Conditional注解实现的,@Conditional注解指明,在注册一个Bean之前,应该参考org.springframework.context.annotation.Condition的实现。
然后org.springframework.context.annotation.Condition提供了matches()方法,以供进行匹配项处理,看一个例子:
@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;
}
- 组合Java配置和XML配置
有两种方式,以XML为中心的和以配置类为中心的,直接看示例趴!
- 以XML为中心
配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
XML文件
<!--使用普通的Bean注入来引入配置类-->
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
<!--使用包扫描来引入配置类-->
<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>
使用:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
- 以配置类为中心
XML文件:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
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);
}
}
用法:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
环境抽象
Environment接口是容器内的一个集成抽象,它对应用环境的两个关键层面建立模型:profile和property(配置描述和配置属性)。
profiles是一组逻辑化的,命名了的Bean定义,如果所给的profile被激活(或处于活跃状态),那么这个profile所包含的Bean定义会注册到容器里面。profile在XML和注解里均可设置,Environment的作为profile的决定者出现的,它会决定哪个profile会被使用,默认的profile是哪个。
properties在几乎所有应用里都扮演着很重要的角色,property可能从各种源里生成,比如:配置文件,JVM系统参数,系统环境变量,JNDI,servlet上下文参数,ad-hoc配置对象,Map对象,等等。Environment之于property的意义在于,提供了一个接口,通过这个接口,可以操作配置源,包括修改和读取。
Bean定义配置文件
Bean定义描述提供了一个包含在核心容器的机制,通过这个机制,允许在不同的环境注册不同的Bean,这在开发时是很有用的,考虑如下场景:
a) 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。
b) 仅在性能模式下注册资源监视组件。
c) 针对部署时和开发时不同Bean实现的切换。
- 使用@Profile
@Profile注解会在它所指定的profile被激活时向容器内注册相应的Bean:
@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");
}
}
@Profile的值可以包含表达式,比如,'!'代表“非”;'&'代表“且”;'|'代表“或”。在组合使用时,记得加小括号"()"。当然,你也可以使用@Profile作为元注解来编写你自己的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果一个配置类被@Profile标注了,那么它所包含的@Bean方法和与其关联的@Import注解都不会发生作用,直到profile处于激活状态。
当然,也可以标注在方法级别,用来指定某一特定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");
}
}
注意:
对于使用@Profile注解的@Bean方法,如果两个方法名一样,仅根据profile的不同而返回同一类型的Bean的话,请记得,方法参数必须一致(都是无参或只有n个类型顺序均一致的参数)。否则会出错,如果你想完成profile不同,且参数也不同的话,记得使用@Bean的name属性加不一样的方法名。
- 在XML文件中使用profile
暂略不表,原文在这。
- 激活一个profile
言简意赅地说,用Environment接口的setActiveProfiles(String... profiles)就能指定需要激活的profile。看看码:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
- 默认profile
指定当没有profile被选中时,默认使用的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();
}
}
也可以使用setDefaultProfiles(String... profiles)方法来设置默认profile。
配置源抽象
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的StandardEnvironment由两个配置源对象组成,一个是JVM系统配置(System.getProperties()),一个是应用系统变量(System.getenv())。
对于Web应用,还有一个StandardServletEnvironment可以使用,通过它,可以获取你需要的servlet config和servlet context parameter。
对于配置源的搜索的层次化的。默认情况下,系统级配置拥有比环境配置更高的优先级,还有,同名属性不会合并,系统级会覆盖掉环境级的。对于一个普通的StandardServletEnvironment,它的搜索层次大致如下所示:
ServletConfig参数
ServletContext参数
JNDI环境变量
JVM系统参数
JVM系统环境
更重要的是,整个机制都是可配置的。如果你想添加一个自定义的配置源,可以试着向下面这么做:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
此时MyPropertySource拥有最高优先级。MutablePropertySources提供了一些方法,用来精确操纵配置源集合。
使用@PropertySource注解
@PropertySource提供了和通过上面的add()方法向Spring Environment添加数据源的等价的方法。看一个例子,其中app.properties是一个包含键值对的配置源:
@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;
}
}
有一个需要注意的,就是,@PropertySource是可以重复的,但是,仅限同一层面,什么意思呢?就是,要么仅作为单一注解使用,要么仅作为你自定义注解的元注解使用,不能既是元注解还是单一注解。
语句中的占位符
介于历史原因,语句中的占位符仅限JVM系统参数和环境变量,但是,现在来说,么得这个问题了!只要属性是注册在Environment里面就够了,Spring会自动搜寻并把占位符替换成属性值。
注册一个LoadTimeWeaver
LoadTimeWeaver是用于当类被加载进JVM时,Spring动态地进行类转换时使用的。为了启用它,可以在你的任何一个配置类前面加上@EnableLoadTimeWeaving:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或XML文件:
<beans>
<context:load-time-weaver/>
</beans>