美文网首页
Spring中资源和环境的配置

Spring中资源和环境的配置

作者: 土豆了解一下 | 来源:发表于2018-02-18 15:56 被阅读0次
    摘要

    配置文件的混用

    • 使用@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;
        }
    }
    

    相关文章

      网友评论

          本文标题:Spring中资源和环境的配置

          本文链接:https://www.haomeiwen.com/subject/cerbtftx.html