Bean的高级装配
配置profile bean
针对不同的环境需要配置不同的bean,Spring提供的解决方案是bean profile功能。首先将所有不同的bean定义到一个或多个profile中,然后根据激活的profile来决定创建哪个bean。
@Profile注解指定bean属于哪一个profile,只有该profile激活时,该bean才能被创建。
@Profile可以被用在类级别上,也可以用在方法级别上。
@Configuration
public class DataSourceConfig {
@Profile("dev")
public DataSource embeddedDataSource() {
}
@Profile("prod")
public DataSource jndiDataSource() {
}
}
可以将不同环境的bean定义在一个配置类中,使用@Profile("")指定其profile,表明只有在相应的profile激活时才会创建该bean。
在XML配置文件中一样可以这样配置。
没有声明在任一profile中的bean始终都会创建,与激活哪个profile没关系。
激活profile
Spring在确定激活那个profile时,依赖两个属性:spring.profiles.active和spring.profiles.default。
如果设置了active属性,它的值就是来确定激活哪个profile
如果没有设置active,Spring会查找default的值来确定激活哪个profile
如果active和default都没设置,Spring只会创建没有定义profile的bean
设置两个属性的方式:
- 作为DispatcherServlet的初始化参数
- 作为Wen应用的上下文参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 集成测试类上,使用@ActiveProfiles设置
系统优先使用spring.profiles.active的值。
可以同时激活多个profile,用,隔开。
条件化的bean
@Conditional注解用来条件化配置bean,可以用到带@Bean的方法上,如果给定的条件结果为true,就会创建这个bean,否则忽略这个bean。
@Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean() { return new MagicBean(); }
MagicExistsCondition这个类指明了条件,需要实现Condition接口。如果matches()返回true,bean会被创建,返回false,bean不会被创建。
public class MagicExistsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); return environment.containsProperty("magicBean"); } }
Condition接口简单但功能强大,matches()方法的参数ConditionContext和AnnotatedTypeMetadata提供了很多的功能。
@Profile在Spring4中进行了重构,使用了@Conditional和Condition接口。
处理自动装配的歧义性
只要一个bean匹配所需的结果时,自动装配才是有效的。如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性、构造参数或方法参数。可以使用以下方法来解决这个问题。
用首选标识bean
使用@Primary注解将其中一个可选的bean设置为首选,可以和@Component组合使用,可以和@Bean组合使用,告诉Spring在遇到歧义性时要选择的bean。
@Primary @Component public class IceCream implements Dessert { }
@Primary @Bean public Dessert iceCream() { return new IceCream(); }
限定自动装配的bean
Spring中的限定符能够在可选的bean上缩小范围,直到只有一个bean满足这些范围。@Qualifier注解是限定符的主要方式,可以与@Autowired和@Inject使用。
以下是简单使用@Qualifier的例子,参数是想要注入bean的限定符,默认和bean id一致。
@Qualifier("iceCream") public void setDessert(Dessert dessert) { this.dessert = dessert; }
为了方便修改类名,可以自定义限定符,这样限定符和类名不耦合。
@Component @Qualifier("cold") public class IceCream implements Dessert { }
@Qualifier也可以和@Bean一起使用。
@Bean @Qualifier("cold") public Dessert iceCream() { return new IceCream(); }
如果需要在一个bean上添加个限定符(@Qualifier),在Java8前的是不允许在单个条目上使用同一注解多次,这时需要自定义限定符注解,如下,然后再把这些注解添加到bean上,需要注入的方法或字段上。
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { }
bean的作用域
默认情况下,Spring上下文中的Bean都是单例的。Spring定义了多种作用域。
- 单例(singleton),整个应用中,只创建bean的一个实例
- 原型(prototype),每次注入或者通过Spring上下文获取时,都会创建一个新的实例
- 会话(session),在Web应用中,为每个会话创建一个bean实例
- 请求(request),在Web应用中,为每个请求创建一个bean实例
使用@Scope来声明
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class IceCream implements Dessert { }
如下是创建会话或请求作用域bean的声明。proxyMode的作用是解决将会话或请求作用域的bean注入到单例bean中遇到的问题。遇到单例的bean实例,Spring注入的不是特定的ShoppingCart实例,而是一个代理,这个代理会暴露与ShoppingCart相同的接口,随后调用真正ShoppingCart的方法。proxyMode指定的就是为接口创建代理(ScopedProxyMode.INTERFACES)还是为类创建代理(ScopedProxyMode.TARGET_CLASS)。
@Component @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES) public class ShoppingCart { }
运行时注入值
Spring提供了两种运行时求值的方法:
属性占位符(property placeholder)
Spring表达式语言(SpEL)
属性占位符
Spring支持将属性定义到外部的属性文件中,然后在程序中引用。
下面的代码首先声明了属性源,并通过Environment来检索属性。
@Configuration @PropertySource("classpath:/com/soundsystem/app.properties") public class DemoConfig { @Autowired Environment environment; public BlankDisc blankDisc() { return new BlankDisc(environment.getProperty("disc.title"), environment.getProperty("disc.artist")); } }
另一种方式是用占位符将值插入到bean中。占位符的形式是用${}包装属性名,使用@Value注解来插入。
public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }