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或者查找一些值注入的问题都非常有帮助。

 

Spring中@Value标签的使用详解的更多相关文章

  1. Spring中@Component注解,@Controller注解详解

    在使用Spring的过程中,为了避免大量使用Bean注入的Xml配置文件,我们会采用Spring提供的自动扫描注入的方式,只需要添加几行自动注入的的配置,便可以完成 Service层,Controll ...

  2. Spring中的循环依赖解决详解

    前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文 ...

  3. spring中Bean的注入参数详解

    字面值    一般指可用字符串表示的值,这些值可以通过<value>元素标签进行注入.在默认情况下,基本数据类型及其封装类.String等类型都可以采取字面值注入的方式,Spring容器在 ...

  4. Spring中bean的作用域scope详解

    参考文献:http://blog.csdn.net/jacklearntech/article/details/40157861 http://www.cnblogs.com/qq78292959/p ...

  5. Spring中Controller和RequestMapping的详解

    先看一个简单的实例: @Controller @RequestMapping("/hello") public class anyTypeController{ @RequestM ...

  6. Spring中的ApplicationListener的使用详解案例

    本文链接:https://blog.csdn.net/u010963948/article/details/83507185 1.ApplicationContext Spring的核心,Contex ...

  7. Spring中配置文件applicationContext.xml配置详解

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...

  8. Python中with标签的使用详解

    1.在python DTL模板中,想要定义变量,可以通过"with"语句来实现. 2."with"语句有两种使用方式: 第一种是"with xx=xx ...

  9. HTML video 视频标签全属性详解

    HTML 5 video 视频标签全属性详解   现在如果要在页面中使用video标签,需要考虑三种情况,支持Ogg Theora或者VP8(如果这玩意儿没出事的话)的(Opera.Mozilla.C ...

随机推荐

  1. tomcat的常用配置方法

    1.tomcat配置某个站点的欢迎页面的方法 2.tomcat配置虚拟目录的方法 3.tomcat配置虚拟主机的方法

  2. [BZOJ 3319] 黑白树

    3319: 黑白树 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 557  Solved: 194[Submit][Status][Discuss] ...

  3. Python之系统交互(subprocess)

    本节内容 os与commands模块 subprocess模块 subprocess.Popen类 总结 我们几乎可以在任何操作系统上通过命令行指令与操作系统进行交互,比如Linux平台下的shell ...

  4. Git时光机穿梭之管理修改

    现在,假定你已经完全掌握了暂存区的概念.下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件. 你会问,什么是修改?比如你新增了一行,这就是一个修改 ...

  5. java8之lambda表达式(1)-基本语法

    lambda表达式,即带有参数的表达式,为更清晰地理解lambda表达式,先看如下例子: (1) class Student{ private String name; private Double ...

  6. Loadrunner 网页诊断图

    Loadrunner 11汉化后版本不包括网页诊断图,所以要想使用网页诊断图,不要汉化.12.50版本汉化后不受影响. 场景执行完,进行analysis时,才能打开网页诊断图. 网页诊断图中组件下载时 ...

  7. Luogu P2966 [USACO09DEC]牛收费路径Cow Toll Paths

    题目描述 Like everyone else, FJ is always thinking up ways to increase his revenue. To this end, he has ...

  8. CSS3基础(3)——CSS3 布局属性全接触

    一. 弹性盒模型 1.弹性盒子模型介绍 弹性盒模型(Flexible Box或Flexbox)是一个CSS3新增布局模块,官方称为CSS Flexible Box Layout Module,用于实现 ...

  9. POJ 3311 Hie with the Pie:TSP(旅行商)【节点可多次经过】

    题目链接:http://poj.org/problem?id=3311 题意: 你在0号点(pizza店),要往1到n号节点送pizza. 每个节点可以重复经过. 给你一个(n+1)*(n+1)的邻接 ...

  10. C++ 开发OCX 的方法和注意事项

    C++ 开发OCX 的方法和注意事项 前言 ActiveX控件是一种实现了一系列特定接口而使其在使用和外观上更象一个控件的COM组件.ActiveX控件这种技术涉及到了几乎所有的COM和OLE的技术精 ...