Spring中资源和环境的配置

摘要

配置文件的混用

  • 使用@ImportResource 在Java Config 类中导入XML配置文件
  • 在XML中引入Java Config
<bean class="com.me.xml.Config"/>

@Import 注解

  • 使用@Import来导入整合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);
    }
}
  • 根据ImportSelector接口selectImports方法的返回值来导入相关配置类
//自定义的注解,用来自动配置OneBean对象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OneBeanImportSelector.class)
public @interface EnableOneBean {
    boolean value() default true;
}
//OneBean对象的Java Config
@Configuration
public class Config {
    @Bean
    public OneBean oneBean() {
        return new OneBean();
    }
}
//主配置文件,注意,主配置文件中并没有导入OneBean对象的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对象的逻辑
public class OneBeanImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //AnnotationMetadata对象可以获取注解上的信息,可以根据注解上的信息来判断是否需动态注入bean
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
        if ((boolean) attributes.get("value")) {
            return new String[]{Config.class.getName()};
        }
        return null;
    }
}
  • 使用ImportBeanDefinitionRegistrar接口动态注册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 对象可以获取 注解上的信息,可以根据注解上的信息来判断是否需动态注入bean
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableOneBean.class.getName());
        if ((boolean) attributes.get("value")) {
            //BeanDefinitionRegistry 对象可以用来 动态的往Spring 容器中添加Bean
            //使用 BeanDefinitionBuilder 来构建 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>

简单分析一下原理:
我们这里引入了命名空间,Spring会有相应的类根据去解析我们引入的命名空间,代码如下

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        ····
    }
}

也就是说,Spring解析到我们在XML中使用了property-placeholder,就会自动为我们创建一个PropertyPlaceholderBeanDefinitionParser对象。该类的代码如下

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的参数占位符填充。所以,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在使用占位符之前就已经被初始化了,使用static修饰,保存该Bean初始化的优先级
     */
    @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 并不是在构建的时候去根据环境选择是否要创建Bean,而是在等到运行时期在确定,所以能够适用于所有的环境,没必要重新构建项目。

配置profile

  • XML
<beans profile="dev">
    <bean id="devBean" class="com.me.xml.OneBean">
        <constructor-arg value="dev"/>
    </bean>
</beans>
  • Java Config

@Profile注解用于指定某个Bean属于哪一个Profile,该注解也可以直接在配置类上使用,表明该配置类中的Bean都属于某一个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

  1. 如果设置了spring.profiles.active属性,那么就更他的值来确定激活哪个profile。
  2. 如果没有设置,就会根据spring.profiles.default属性的值来激活profile。
  3. 如果前面两者都没有确定的话,那就只会构建没有profile的Bean。
  4. 有多种方式来设置这两个属性的值:
  • JVM的启动参数
  • 作为DispatcherServlet的初始化参数
  • 作为Web应用的上下文
  • 作为环境变量
  • 在测试类中,使用@ActiveProfile

Spring的环境Environment接口

Environment 接口主要就是两个作用:

  • 获取环境中的属性值
  • 获取profile的激活状态

@Conditional注解和Condition接口

  • @Conditional可以根据满足某一特定的条件来创建一个特别的bean
  • 可以通过实现Condition接口的matches方法来构造判断条件
public class ExistOneBean implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 如果Spring 的环境中存在 OneBean 对象, 就返回true
        return context.getBeanFactory().getBeansOfType(OneBean.class).size() > 0;
    }
}
@Configuration
public class Config {

    @Bean
    public OneBean oneBean() {
        return new OneBean();
    }

    @Bean
    @Conditional(ExistOneBean.class) // 如果当前环境中存在OneBean,才实例化该Bean
    public LocalDateTime dateTime() {
        return LocalDateTime.now();
    }
}

@Profile的实现原理

在Spring4中,@Profile也使用@Conditional注解来实现。

@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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341