配置文件的混用
- 使用
@ImportResource
在Java Config 類中導(dǎo)入XML配置文件 - 在XML中引入Java Config
<bean class="com.me.xml.Config"/>
@Import 注解
- 使用@Import來導(dǎo)入整合Java Config
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
}
@Configuration
@Import(Config.class)
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
System.out.println(oneBean);
}
}
- 根據(jù)ImportSelector接口selectImports方法的返回值來導(dǎo)入相關(guān)配置類
//自定義的注解桐猬,用來自動(dòng)配置OneBean對(duì)象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OneBeanImportSelector.class)
public @interface EnableOneBean {
boolean value() default true;
}
//OneBean對(duì)象的Java Config
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
}
//主配置文件办铡,注意愈捅,主配置文件中并沒有導(dǎo)入OneBean對(duì)象的Java Config
@EnableOneBean
@Configuration
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
System.out.println(oneBean);
}
}
//是否需要配置OneBean對(duì)象的邏輯
public class OneBeanImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//AnnotationMetadata對(duì)象可以獲取注解上的信息,可以根據(jù)注解上的信息來判斷是否需動(dòng)態(tài)注入bean
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
if ((boolean) attributes.get("value")) {
return new String[]{Config.class.getName()};
}
return null;
}
}
- 使用ImportBeanDefinitionRegistrar接口動(dòng)態(tài)注冊(cè)bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OneBeanImportBeanDefinitionRegistrar.class)
public @interface EnableOneBean {
boolean value() default true;
}
public class OneBeanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata 對(duì)象可以獲取 注解上的信息,可以根據(jù)注解上的信息來判斷是否需動(dòng)態(tài)注入bean
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
if ((boolean) attributes.get("value")) {
//BeanDefinitionRegistry 對(duì)象可以用來 動(dòng)態(tài)的往Spring 容器中添加Bean
//使用 BeanDefinitionBuilder 來構(gòu)建 BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OneBean.class);
builder.setScope(BeanDefinition.SCOPE_SINGLETON);
// builder.addPropertyValue("name", "xxx");
registry.registerBeanDefinition("oneBean", builder.getBeanDefinition());
}
}
}
@Configuration
@EnableOneBean
public class Config {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
OneBean oneBean = context.getBean("oneBean", OneBean.class);
}
}
占位符的支持
- XML
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.me.xml.DataSourceProperties">
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
簡(jiǎn)單分析一下原理:
我們這里引入了命名空間,Spring會(huì)有相應(yīng)的類根據(jù)去解析我們引入的命名空間麦射,代碼如下
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
····
}
}
也就是說磷醋,Spring解析到我們?cè)赬ML中使用了property-placeholder
,就會(huì)自動(dòng)為我們創(chuàng)建一個(gè)PropertyPlaceholderBeanDefinitionParser
對(duì)象。該類的代碼如下
class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {
private static final String SYSTEM_PROPERTIES_MODE_ATTRIBUTE = "system-properties-mode";
private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";
@Override
protected Class<?> getBeanClass(Element element) {
// As of Spring 3.1, the default value of system-properties-mode has changed from
// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
// placeholders against system properties is a function of the Environment and
// its current set of PropertySources.
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class;
}
// The user has explicitly specified a value for system-properties-mode: revert to
// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
return PropertyPlaceholderConfigurer.class;
}
···
從代碼的注釋中吴裤,我們可以知道继蜡,在Spring3.1之后,Spring使用PropertySourcesPlaceholderConfigurer
類來支持Spring的參數(shù)占位符填充苍蔬。所以诱建,XML的另一種配置的方式為:
<bean id="propertySourcesPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location" value="db.properties"/>
</bean>
<bean id="dataSource" class="com.me.xml.DataSourceProperties">
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
- Java Config
@Configuration
@PropertySource("db.properties") // 使用該注解來引入外部資源文件
public class Config {
/**
* 使用PropertySourcesPlaceholderConfigurer 來解析占位符
* <p>
* 注意: 要確保該Bean在使用占位符之前就已經(jīng)被初始化了,使用static修飾,保存該Bean初始化的優(yōu)先級(jí)
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurerP() {
return new PropertySourcesPlaceholderConfigurer();
}
@Value("${db.username}") // 可以通過 @Value 來引入占位符
private String username;
@Value("${db.password}")
private String password;
@Value("${db.url}")
private String url;
}
profile
注意: Spring 并不是在構(gòu)建的時(shí)候去根據(jù)環(huán)境選擇是否要?jiǎng)?chuàng)建Bean,而是在等到運(yùn)行時(shí)期在確定碟绑,所以能夠適用于所有的環(huán)境俺猿,沒必要重新構(gòu)建項(xiàng)目。
配置profile
- XML
<beans profile="dev">
<bean id="devBean" class="com.me.xml.OneBean">
<constructor-arg value="dev"/>
</bean>
</beans>
- Java Config
@Profile
注解用于指定某個(gè)Bean屬于哪一個(gè)Profile格仲,該注解也可以直接在配置類上使用押袍,表明該配置類中的Bean都屬于某一個(gè)Profile。
@Configuration
public class Config {
@Profile("dev")
@Bean
public OneBean devOneBean() {
return new OneBean("devBean");
}
@Profile("test")
@Bean
public OneBean testOneBean() {
return new OneBean("testBean");
}
}
激活Profile
- 如果設(shè)置了
spring.profiles.active
屬性凯肋,那么就更他的值來確定激活哪個(gè)profile谊惭。 - 如果沒有設(shè)置,就會(huì)根據(jù)
spring.profiles.default
屬性的值來激活profile。 - 如果前面兩者都沒有確定的話圈盔,那就只會(huì)構(gòu)建沒有profile的Bean豹芯。
- 有多種方式來設(shè)置這兩個(gè)屬性的值:
- JVM的啟動(dòng)參數(shù)
- 作為DispatcherServlet的初始化參數(shù)
- 作為Web應(yīng)用的上下文
- 作為環(huán)境變量
- 在測(cè)試類中,使用@ActiveProfile
Spring的環(huán)境Environment接口
Environment 接口主要就是兩個(gè)作用:
- 獲取環(huán)境中的屬性值
- 獲取profile的激活狀態(tài)
@Conditional注解和Condition接口
-
@Conditional
可以根據(jù)滿足某一特定的條件來創(chuàng)建一個(gè)特別的bean - 可以通過實(shí)現(xiàn)
Condition
接口的matches
方法來構(gòu)造判斷條件
public class ExistOneBean implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 如果Spring 的環(huán)境中存在 OneBean 對(duì)象, 就返回true
return context.getBeanFactory().getBeansOfType(OneBean.class).size() > 0;
}
}
@Configuration
public class Config {
@Bean
public OneBean oneBean() {
return new OneBean();
}
@Bean
@Conditional(ExistOneBean.class) // 如果當(dāng)前環(huán)境中存在OneBean驱敲,才實(shí)例化該Bean
public LocalDateTime dateTime() {
return LocalDateTime.now();
}
}
@Profile的實(shí)現(xiàn)原理
在Spring4中铁蹈,@Profile也使用@Conditional注解來實(shí)現(xiàn)。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
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;
}
}