1.profile
spring会等到运行时根据环境决定需要创建哪个bean,所以一个war包可以适用于所有环境,不需要进行重新构建。
1.1配置profile
用@Profile注解指定某个bean属于哪个profile。
@Configuration
@Profile("dev")
public class DevConfig{
@Bean
public DataSource dataSource() {
return new ...
}
}
@Profile用在类级别上,表明类里面的bean只有dev profile激活时才会创建。
@Profile也可用在方法上。
XML配置如下:
<beans profile="dev">
<bean id="devBean"/>
</beans>
<beans profile="prod"
<bean id="prodBean"/>
</beans>
1.2激活profile
2个相关属性:spring.profiles.active/spring.profiles.default
有多种方式设置这2个属性:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解设置;
例如在web.xml中设置默认profile:
<!--为上下文设置默认profile-->
<context-param>
<param-name>spring.profiles.default></param-name>
<param-value>dev</param-value>
</context-param>
<!--为servlet设置默认profile-->
<init-param>
<param-name>spring.profiles.default></param-name>
<param-value>dev</param-value>
</init-parm>
使用profile进行测试
spring提供了@ActiveProfiles,指定运行测试时要激活哪个profile。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:httpclient-context.xml"})
@ActiveProfiles("dev")
public class CommandTest {
2 条件化的bean
当希望在某些特定条件下才创建bean,如某个bean只有在另外某个特定bean声明之后才创建,或者某个特定环境变量设置之后才会创建某个bean。
可以使用spring的@Conditional 注解。
@Bean
@Conditional(ExistsCondition.class)
public SgtPeppers sgtPeppers(){
return new SgtPeppers();
}
设置给@Conditional的类可以是实现了Condition接口的类型:
public class ExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment env = conditionContext.getEnvironment();
return env.containsProperty("propertyName");//检查propertyName属性
}
}
ConditionContext是一个接口:
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();//可以检查bean定义
ConfigurableListableBeanFactory getBeanFactory();//用于检查bean是否存在
Environment getEnvironment();//检查环境变量是否存在以及值是什么
ResourceLoader getResourceLoader();//读取并探查ResourceLoader加载的资源
ClassLoader getClassLoader();//用于加载并检查类是否存在
}
AnnotatedTypeMetadata也是接口,能够让我们检查带有@Bean注解的方法上还有其他注解:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1);//用于判断带有@Bean注解的方法是否还有其他特定注解
Map<String, Object> getAnnotationAttributes(String var1);
Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
3处理自动装配的歧义性
如果某个bean要装配时,有多个实现,会造成冲突:
@Autowired
public void setCompact(CompactDisc compact) {
this.cd = compact;
}
public interface CompactDisc {
void play();
}
@Component
public class CompactImpl1 implements CompactDisc {
@Component
public class CompactImpl2 implements CompactDisc {
解决办法有:
3.1 设置首选的bean
@Component
@Primary
public class CompactImpl1 implements CompactDisc {
3.2限定自动装配的bean
@Qualifier注解
@Autowired
@Qualifier("compactImpl1")
public void setCompact(CompactDisc compact) {
this.cd = compact;
}
@Component注解声明的CompactImpl1类会创建为id为compactImpl1的bean(首字母小写),
@Qualifier(“compactImpl1”)指向组件扫描是创建的bean,即compactImpl1。
也可以为bean直接设置自己的限定符
@Component //或者是@Bean
@Qualifier("impl1")
public class CompactImpl1 implements CompactDisc {
自定义注解
当不想用@Qualifier或者出现@Qualifier定义重复时,可以自定义注解:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Impl1 {
}
这样,只需要添加@impl1注解
@Component //@Bean
@Impl1
public class CompactImpl1 implements CompactDisc {
在注入时,也只需要添加@Impl1注解:
@Autowired
@Impl1
public void setCompact(CompactDisc compact) {
this.cd = compact;
}
为了创建自定义的限定符注解,可以创建一个新的注解并添加上@Qualifier注解,这种技术可以用到很多的spring注解中。
4 bean的作用域
spring有多种作用域,用@Scope注解,默认单例:
单例(Singleton):整个应用中只有1个实例;
原型(Prototype):每次注入或者spring应用上下文获取时创建1个;
会话(Session):web应用中每个会话创建1个;如购物车;
请求(Request):web应用中每个请求创建1个;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//使用@Scope("prototype")也可以,但SCOPE_PROTOTYPE常量更加安全不易出错
public class CompactImpl1
XML配置:
<bean class="chapter2.CompactImpl1" scope="prototype"/>
4.1 会话和请求作用域
会话作用域如下:
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
public Shopping shop(){}
proxyMode的作用如下:
例如想把Shopping注入到单例Service的bean中,
@Component
public class Service {
Shopping shopping;
@Autowired
public void setShopping(Shopping shopping){
this.shopping = shopping;
}
}
因为Service是单例bean,spring上下文加载时就会创建,但此时Shopping是会话作用域的,还不存在,知道某个用户进入系统时才会创建,而且系统里会有多个Shopping实例,spring应该是service的bean处理购物车功能时,使用的Shopping实例刚好是当前会话的bean。
所以spring会注入一个Shopping bean的代理,如下图,代理会暴露与Shopping bean相同的方法,当service真正调用Shopping方法时,代理会对其进行懒解析,将调用委托给会话作用域内真正的Shopping bean。
proxyMode = ScopedProxyMode.INTERFACES 表明创建的代理是基于接口的代理,代理要实现Shopping接口,Shopping需要是一个接口;
如果Shopping是一个具体类,此时,只能使用CGLib生成基于类的代理,此时proxyMode要设置成 ScopedProxyMode.TARGET_CLASS
请求作用域也是同样道理。
4.2 XML中声明作用域代理
<bean class="chapter2.Shopping" scope="session"><aop:scoped-proxy/></bean>
功能和@Scope注解的proxyMode属性功能相同,会告诉spring为bean创建一个作用域代理,默认使用CGLib创建目标类的代理,设置后会生成基于接口的代理。
5 运行时值注入
spring提供的两种运行时求值方式:
- 属性占位符
- spring表达式语言(SpEL)
5.1 注入外部的值
注入外部值:使用@PropertySource注解和Environment。
@Configuration
@PropertySource("classpath:app.properties")
public class CDplayerConfig {
@Autowired
Environment env;
@Bean
public CompactDisc disc() {
return new SgtPeppers(env.getProperty("title"));
}
}
app.properties属性文件会加载到Environment中。
spring的Environment
getProperty()有四种重载方法:
第四种可以指定返回值类型和默认值,如下:
int result = env.getProperty("db.connection", Integer.class, 10);
将属性解析为类:
如果文件中没有定义该属性,得到的值是null。
env.getRequiredProperty()可以要求属性必须定义,否则抛出IllegalStateException异常。
解析属性占位符
spring配置中使用${….}
如果用组件扫描和自动装配创建和初始化bean时,没有指定占位符的配置文件和类了,此时可以使用@Value注解
@Bean
public CompactDisc disc(@Value("${title}") String title) {
this.title = title;
}