zoukankan      html  css  js  c++  java
  • Spring中@Value标签的使用详解

    1.@Value标签

      由于Spring对通过IOC的方式对对象进行统一管理,所以对任何对象而言,其生成方法均由Spring管理。传统的方法是通过XML配置每一个Bean,并对这个Bean的所有Field进行声明式配置。

      以一个简单的学校的例子为示范。假设有两种角色,老师和班长。

    package org.kingszelda.version3.controller;
    
    /**
     * Created by shining.cui on 2017/7/30.
     */
    public class Teacher {
    
        /**
         * 姓名
         */
        private String name = "王老师";
    
        /**
         * 教授科目
         */
        private String major = "数学";
    
        /**
         * 教授课程班级的班长
         */
        private ClassMonitor classMonitor = new ClassMonitor();
    
        /**
         * 老师会上课
         */
        public void teachLesson() {
    
        }
    
        /**
         * 老师要收作业,然而老师并不亲自收,而是交给班长收
         */
        public void collectHomework() {
            classMonitor.collectHomework();
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getMajor() {
            return major;
        }
    
        public void setMajor(String major) {
            this.major = major;
        }
    
        public ClassMonitor getClassMonitor() {
            return classMonitor;
        }
    
        public void setClassMonitor(ClassMonitor classMonitor) {
            this.classMonitor = classMonitor;
        }
    }

      老师有自己的名字和教授的科目两个属性,这属于老师的静态属性。老师有很多“功能“,比如上课和收作业。上课属于老师可以自己完成的功能,而收作业则需要班长帮忙。所以老师为了完成自己的工作是不能独立存在的,需要依赖班长。

    /**
     * Created by shining.cui on 2017/7/30.
     */
    public class ClassMonitor {
    
        public void collectHomework(){
            System.out.println("开始收作业了!");
            System.out.println("收作业完毕");
        }
    }

      这里我们假设班长只有一个功能,就是收作业。

      上面的例子很好的说明了对象之间相互依赖共同合作的方法,即互相依赖。这些功能交给spring之后管理起来就方便多了,以xml的方式为例,需要如下配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="classMonitor" class="org.kingszelda.version3.controller.ClassMonitor"/>
        <bean id="teacher" class="org.kingszelda.version3.controller.Teacher">
            <property name="name" value="王老师"/>
            <property name="major" value="数学"/>
            <property name="classMonitor" ref="classMonitor"/>
        </bean>
    </beans>

      通过这种配置的方式之后,实体之间的依赖关系变得一清二楚。比如Teacher的名字,科目,所依赖的班长是哪个,只看配置文件就可以一目了然。但是,当实体变多了之后,可想而知,这个xml配置文件将庞大的不可想象,就更不要提可读性了。

      于是Spring从3.0开始推出了基于注解的形式,来简化配置。

    package org.kingszelda.version3.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * Created by shining.cui on 2017/7/30.
     */
    @Service
    public class Teacher {
    
        /**
         * 姓名
         */
        @Value("王老师")
        private String name;
    
        /**
         * 教授科目
         */
        @Value("数学")
        private String major;
    
        /**
         * 教授课程班级的班长
         */
        @Resource
        private ClassMonitor classMonitor;
    
        /**
         * 老师会上课
         */
        public void teachLesson() {
    
        }
    
        /**
         * 老师要收作业,然而老师并不亲自收,而是交给班长收
         */
        public void collectHomework() {
            classMonitor.collectHomework();
        }
    }

      通过注解的形式已经减少了大量的get、set方法,通过@Resource注入了依赖的班长,并且通过@Value注入了老师的姓名和科目。

      问题来了,当姓名与科目如何做到可配置,而不是写死的呢?对应xml与注解都有其对应的方式。

      首先声明一个config.perproties文件:

    name=张老师
    major=数学

      xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:property-placeholder location="classpath:config.properties"/>
        <bean id="classMonitor" class="org.kingszelda.version3.controller.ClassMonitor"/>
        <bean id="teacher" class="org.kingszelda.version3.controller.Teacher">
            <property name="name" value="${name}"/>
            <property name="major" value="${major}"/>
            <property name="classMonitor" ref="classMonitor"/>
        </bean>
    </beans>

    注解:

    package org.kingszelda.version3.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * Created by shining.cui on 2017/7/30.
     */
    @Service
    public class Teacher {
    
        /**
         * 姓名
         */
        @Value("${name}")
        private String name;
    
        /**
         * 教授科目
         */
        @Value("${major}")
        private String major;
    
        /**
         * 教授课程班级的班长
         */
        @Resource
        private ClassMonitor classMonitor;
    
        /**
         * 老师会上课
         */
        public void teachLesson() {
    
        }
    
        /**
         * 老师要收作业,然而老师并不亲自收,而是交给班长收
         */
        public void collectHomework() {
            classMonitor.collectHomework();
        }
        
    }

      我们看到,不论是通过xml配置,还是通过注解@Value的方式,姓名与科目都在生成Teacher对象是被赋值进去了。

    2.@Value是如何起作用的

    Spirng在生命周期里关于Bean的处理大概可以分为下面几步:

    1. 加载Bean定义(从xml或者从@Import等)
    2. 处理BeanFactoryPostProcessor
    3. 实例化Bean
    4. 处理Bean的property注入
    5. 处理BeanPostProcessor

    而当我们在声明了<context:property-placeholder location="classpath:config.properties"/>标签之后,即声明了一个配置型bean交给Spring容器进行管理,即PropertyPlaceholderConfigurer类。我们先看一下这个类的继承结构。

      蓝色的是功能性结构,实现了解析文件,处理文件的功能,黄色的是Spring的钩子功能,声明了功能性模块被调用的时机。这里PrepertyPlaceholderConfigurer实现了BeanFactoryPostProcesser接口,并实现了postProcessBeanFactory()方法即当Spring容器的BeanFactory被构造成功之后会调用这个方法。这里要注意,ApplicationContext也是BeanFactory的派生类。这时,我们先看父类的PropertyResourceConfigurer方法postProcessBeanFactory。因为这个类继承了Spring的BeanFactoryPostProcesser接口,所以这个方法一定是操作BeanFactory的。

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            try {
           //1.获取当前容器配置的所有Properties文件,可能由多个文件merge而来 Properties mergedProps
    = mergeProperties(); //2.如果需要的话,将Properties文件的内容进行转化,因为默认的Preperties都是String的key-value形式。
    //Spring提供的默认方式是不转化,保持String,String的key-value
    convertProperties(mergedProps); //3.由子类继承,对容器与Properties进行操作,即value注入。 processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } }

      这里最重要的第一步就是获得Properties文件即mergeProperties方法,这是解析资源文件最基本的方法,所以这个方法一定存在于当前功能的最基类中,即PropertiesLoaderSupport。由于xml中是这样配置的:

    <context:property-placeholder location="classpath:config.properties"/>

    这里声明了一个PropertyPlaceholderConfigurer对象,显然是调用了setLocation方法,而这个方法同样存在于该功能模块的最基本父类PropertiesLoaderSupport中。

    public abstract class PropertiesLoaderSupport {
           //……省略
    
        private Resource[] locations;
        
           //……省略
    
            public void setLocation(Resource location) {
            this.locations = new Resource[] {location};
        }
    
           //注意:晚声明的文件内容将覆盖早声明的文件内容,所以请自己保证文件之间内容不要重叠,否则以最后一个文件为准   
            public void setLocations(Resource... locations) {
            this.locations = locations;
        }
           //……省略
    }

      mergeProperties方法中进行了配置化管理,针对某些标志位进行额外的操作,这里不做过多说明,其最重要的功能是调用了下面方法:

        protected void loadProperties(Properties props) throws IOException {
            if (this.locations != null) {
           //1.遍历声明的Resource文件地址
    for (Resource location : this.locations) { if (logger.isInfoEnabled()) { logger.info("Loading properties file from " + location); } try {
                //2.获得Resource文件流,并加载内容到Properties对象中 PropertiesLoaderUtils.fillProperties( props,
    new EncodedResource(location, this.fileEncoding), this.propertiesPersister); } catch (IOException ex) { if (this.ignoreResourceNotFound) { if (logger.isWarnEnabled()) { logger.warn("Could not load properties from " + location + ": " + ex.getMessage()); } } else { throw ex; } } } } }

      回想PropertyResourceConfigurer主流程中的三个方法,第一步已经执行完毕,加载了配置的properties文件,第二步是spring自己的默认实现,将非空的key对应的value放入Properties中,第三步则该由子类各自实现了,将BeanFactory与Properties进行统一操作。这时候我们看我们直接声明的派生类PropertyPlaceholderConfigurer。

        @Override
        protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
                throws BeansException {
         //1.声明一个支持value为String类型的Resolver
            StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
            //2.将key-value注入到BeanFactory的某些bean中
    doProcessProperties(beanFactoryToProcess, valueResolver); }

      接下来就是真正的value注入环节了

    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
                StringValueResolver valueResolver) {
         //1.将key-value内容声明为BeanDefinitionVisitor对象,用来根据BeanDefinition修改即将生成的对应的Bean内容
            BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    
            String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
            for (String curName : beanNames) {
           //2.只有同一个容器内的才可以进行value注入,同时应该避免掉操作本身,避免进入循环递归
    if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); try { visitor.visitBeanDefinition(bd); } catch (Exception ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); } } } //3.处理一些拥有别名的类 beanFactoryToProcess.resolveAliases(valueResolver); //4.New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.(这一步有些不懂,以后再修正)
           beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); }

      在上述代码中,第2步已经修改了原始的BeanDefinition,我们一路跟进去看,原来核心的替换功能在PropertyPlaceholderHelper中:

    protected String parseStringValue(
                String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
         //1.对每一个key进行处理
            StringBuilder result = new StringBuilder(strVal);
         //2.首先考虑有占位符的情况,默认是${}
            int startIndex = strVal.indexOf(this.placeholderPrefix);
            while (startIndex != -1) {
                int endIndex = findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    if (!visitedPlaceholders.add(originalPlaceholder)) {
                        throw new IllegalArgumentException(
                                "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                    }
                    //3.如果有占位符,则去掉占位符递归调用本方法,即key=${abc},处理成key=abc的形式试图获取value
                    placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                    //4.真正的从key-value集合中获得key对应的value
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
              //5.如果没有找到,则试图按照${key:default}的形式解析
    if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) {
    //6.获得:之前的内容,即真正的key String actualPlaceholder
    = placeholder.substring(0, separatorIndex); //7.获得:之后的内容,即默认值
    String defaultValue
    = placeholder.substring(separatorIndex + this.valueSeparator.length());
                   //8.再次尝试从key-value集合中获得内容,因为如果真的是key-value的形式,按照全名是肯定找不到的 propVal
    = placeholderResolver.resolvePlaceholder(actualPlaceholder);
    //9.如果找到了就按照配置的走,如果没有找到则附上默认值
    if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { //10.如果找到了这个value,则再次递归调用自己,避免value也是占位符的情况 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
    //11.将获得的结果替换掉 result.replace(startIndex, endIndex
    + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in string value "" + strVal + """); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }

    3.总结

      Spring管理bean的方式大大减少了编程人员的编码复杂度,这个复杂度并没有消失,而是转嫁到Spring容器中。我们可以通过xml或者@value注解的方式注入field属性,这对多环境profiles的应用是非常关键的,掌握了Spring的这条注入路径之后,对于帮助理解Spring或者查找一些值注入的问题都非常有帮助。

     
  • 相关阅读:
    RTImageAssets 自动生成 AppIcon 和 @2x @1x 比例图片
    Git 执行 「fork 出来的仓库」和「最新版本的原仓库」内容同步更新
    自定义支持多种格式可控范围的时间选择器控件
    UIWebView 操作
    iOS 模拟器键盘弹出以及中文输入
    验证 Xcode 是否来自正规渠道
    使用 AFNetworking 进行 XML 和 JSON 数据请求
    Reveal UI 分析工具分析手机 App
    多种方式实现文件下载功能
    网站HTTP升级HTTPS完全配置手册
  • 原文地址:https://www.cnblogs.com/kingszelda/p/7261156.html
Copyright © 2011-2022 走看看