配置 profile 的原因
不同的环境有不同的配置, spring 需要根据需求的不同决定哪些需要创建哪些bean, 哪些不需要
spring 在
环境与Profile
创建Profile
不同的开发环境需要不同的配置, Spring 需要根据需求的不同决定哪些 Bean 需要创建, 哪些不需要. 所以需要配置文件, 决定配置.
在 Spring 中使用 @Profile
指定某个 bean 属于哪一个 profile. 如果一个 bean 属于 dev profile, 那么只有在激活 dev profile 的时候, 这个 bean 才会创建.
- 用 @Profile 修饰类
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
@Bean
public CDPlayer player(){
return new CDPlayer(segPepper());
}
@Bean
public SegPepper segPepper(){
return new SegPepper();
}
}
在上面这个配置文件中的 bean 只有在激活了 dev profile 的时候才会创建
- 用 @Profile 修饰方法
@Configuration
public class ProductionProfileConfig{
@Bean
@Profile("prod")
public CDPlayer player(){
return new CDPlayer(segPepper());
}
}
要注意没有创建 profile 的 bean 始终会被创建.
激活Profile
激活 Profile 用的两个属性
spring.profiles.active
spring.profiles.default
这两个属性对于 profile 的影响是这样的
- 如果设置了 active属性, 那么 profile 由 active 决定
- 如果没有设置 active 属性, 那么 profile 由 default 决定
- 如果都没有, 那么生效的 bean 只有没有由 profile 管理的 bean
条件化的Bean
Spring4 支持 条件化创建 Bean. 比如我们希望某个 Bean 只有某个类存在的时候才创建, 或者要求只有某个变量存在的时候才创建某个 Bean
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean(){
return new MagicBean();
}
其中 MagicExistsCondition 实现了接口 Condition
public class MagicExistsCondition implements Condition{
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
这个 Condition 会检查是否存在属性 magic, 如果没有就不会创建 bean
matches 有两个参数 ConditionContext context
和 AnnotatedTypeMetadata metadate
-
ConditionContext
:-
getRegistry()
返回BeanDefinitionRegistry
检查 bean 定义 -
getBeanFactory()
返回ConfigurableListableBeanFactory
检查 bean 是否存在, 探查 bean 的属性 -
getResourceLoader()
返回ResourceLoader
所加载的资源 -
getClassLoader()
返回的ClassLoader
加载并检查类是否存在
-
-
AnnotatedTypeMetadata
: 能够检查带有 @Bean 注解的方法还有什么其他的注解, 也可以借助其中的isAnnotated()
方法, 判断除了 @Bean 还有什么其他的注解
首选的Bean
如果在装配 Bean 的时候有两个或以上符合条件的 Bean, 那么会产生歧义, 报错. 可以在优先使用的bean 加上 @Primary 标签, 这样产生歧义的时候会优先使用这个组件
// 可以用在组件类中
@Component
@Primary
public class Component{}
//也可以用在Bean上
@Bean
@Primary
public Component component(){
return new Component();
}
限定符修饰的Bean
如前面所讲, 如果一个自动装配会试图尽量符合目标的注入. 如果没有符合的组件, 或者有多个符合的组件产生歧义就会报错.
可以使用 @Qualifier
注解来给组件设定一个标识符
@Autowired
@Qualifier("code")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
这里, 会把一个标识符为 code 的组件注入到参数 dessert 中. 但是组件的默认标识符是它的 id, 可以用 @Qualifier 标签修改组件的标识符
@Component
// 也可以用在 @Bean 下面
@Qualifier("code")
public class IceCream implements Dessert{...}
这里推荐标识符设置为组件的某个特性, 比如"冰激凌"是"code"的, 强调"一一对应"的注入关系不是什么好事情. 那么就又产出了一个新问题. 如果有多个组件有"code"特性呢? 这样不是又产出了歧义?
也许可以给一个组件加上多个标识符
@Component
@Qualifier("code")
@Qualifier("creamy")
public class IceCream implements Dessert{...}
但是 Java 不允许把一个注解多次使用在一个目标上. 解决方法是:
@Targer({ElementType.CONSTRUCTOR, ElementType.Field,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy{}
这样我们就有了一个 @Creamy 注解, 同样声明一个 @Code 注解, 这样就可以同时使用两个标识符了
@Component
@Code
@Creamy
public class IceCream implements Dessert{...}
@Autowired
@Code
@Creamy
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
Bean 的作用域
Spring 定义了多种定义域
- 单例
- 原型
- 会话
- 请求
默认, 创建的组件都是单例, 如果要使用原型:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad{...}
//也可以使用@Scope("prototype"), 但是好像不如上面安全
使用会话
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopeProxyMode.INTERFACES)
public ShoppingCart cart(){...}
第一个参数, 告诉 Spring 对于每一个 Web 会话, 都会创建一个 cart 会话. 第二个参数解决了单例和会话的配置的问题.
假设现在有一个 StoreService 单例, 它负责结账购物车中的内容.
@Component
public class StoreService{
@Autowired
public void setShoppingCart(ShoppingCart cart){
this.ShoppingCart = cart;
}
}
但是单例在上下文加载的时候创建, 这个时候会话作用域的 cart 并不存在. 于是 cart 需要一个代理, 单例在注入 cart 的时候获得的是一个代理.
运行时注入
对于一般的组件, 我们可能会以这种方式装配
@Bean
public CompactDisc setPeppers(){
return new BlankDisc(
"set Pepper",
"The Beatles"
);
}
这里的问题就在于, 实例化 BlankDisc 过程的数据是硬编码在配置文件里的. 这也许不是很合适.
这个时候就要通过注入外部的值尽量解决硬编码
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public BlankDisc disc() {
return new BlankDisc(env.getProperty("disc.title"),
env.getProperty("disc.artist"));
}
}
在这里通过 @PropertySource()
标注导入了一个 app.properties 文件, 这个时候, 坏境会提供一个 Environment
组件. 将这个组件装配到 env 以后, 通过 getProperty
可以调用 app.properties 中的值
disc.title="Set. Peppers"
disc.artist="The Beaties"
关于 getProperty 的一些重载
-
String getProperty(String key)
-
String getProperty(String key, String defaultValue)
: 如果没有 key , 那么设置为默认值 -
T getProperty(String key, Class<T> type)
如果希望得到的是数字:env.getProperty("db.connection.count", Integer.class);
-
T getProperty(String key, Class<T> type, 12)
相似, 不过有默认值
属性占位符
除去上面的方法, 还有一种使用属性占位符的方式配置方法.
首先, 开启 PropertySourcePlaceholderConfigurer
@Bean
public
static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
然后, 组装 bean
@Bean
public BlankDisc disc(
@Value("${disc.title}") String title,
@Value("${disc.artist}")String artist
) {
return new BlankDisc(title, artist);
}
这里用到了@Value
注解, 接受一个占位符, 格式为 ${...}
, 大括号中占位符
网友评论