1.@Value没有注入预期的值
在装配对象成员属性时,我们常常会使用@Autowired来装配,但是有时候,我们也会使用@Value
进行装配,不对这两种风格使用不同,使用@Autowired一般都不会设置属性值,而@Value必须制定一个字符串值,因为其定义了要求,其代码如下:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
/**
* The actual value expression such as #{systemProperties.myProp}
* or property placeholder such as ${my.app.myProp}
.
*/
String value();
}
另外在比较两者的区别时,我们一般都会因为@Value常用于String类型的装配而误以为@Value不能用于非内置对象的装配,实际上是一个误区,例如,我们也可以使用下面的方式来设置一个属性成员:
@Value("#{serviceImpl}")
public ServiceImpl service;
@Value典型的使用方式有如下:
@RestController
@RequestMapping
@Slf4j
public class Controller {
@Value("字符串常量")
private String text;
//获取配合文件的值
@Value("${management.server.port}")
private String port;
@Value("#{student.name}")
private String name;
@Value("#{serviceImpl}")
public ServiceImpl service;
// @Autowired
//private ServiceImpl service;
@GetMapping("/test")
public String test(){
log.info("Mingcheng ss{}",service);
log.info("text ss{}",text);
log.info("userName:{}",port);
log.info("name:{}",name);
return "hello world" +service;
}
输出内容:
那么我们在使用的时候,会遇到那些问题呢?就是可能@Value可能会注入一个不是预期的值。
我们模拟一个场景:配置文件是这样
username=admin password=pass
然后我们在controller中调用的时候:
@RestController
@RequestMapping
@Slf4j
public class Controller {
@Value("${username}")
private String username;
@Value("${password}")
private String password;
@GetMapping("/test")
public String test(){
log.info("username的值为{}",username);
log.info("password的值为{}",password);
return "测试";
}
运行结果如下:
我们可以看到username返回的不是我们设定的值,而是我们计算机的用户名。为什么会这样呢,我们揭开神秘的面纱。
我们就需要去了解@Value,Spring 是如何根据@Value来查询值的,我们可以看一下核心流程:
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
//省略掉部门代码
Class> type = descriptor.getDependencyType();
//寻找value值
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
//解析value值
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
//转化value解析的结果到装配的类型
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
}
大致可以分为三个步骤:
1.寻找 @Value
@Nullable
protected Object findValue(Annotation[] annotationsToSearch) {
if (annotationsToSearch.length > 0) { // qualifier annotations have to be local
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
//valueAnnotationType即为@Value
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
2.解析@字符串
3,将解析结果转为成要装配的对象的类型
专配对象,都会具体到某个对应的实现类继承
PropertyEditorSupport ,每个方法的setAsText
对于上述问题,很明显问题出现在第二部,解析字符串,执行过程参考下面的具体代码
String strVal = resolveEmbeddedValue((String) value);
我们找到这个代码:
@Nullable
protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
通过代码我们看到是获取配合文件this.propertySources,我们断点调式有两个
本地配置和环境配置,然后我们在点进去第一个看看细节:
我们找到了和我们配置一样的,值和我们的不一致,取了这边的。
这种问题怎么解决:
避免和环境变量冲突,也要避免和系统变量等其他变量冲突
总结:可能工作中也会遇到这种问题,也知道怎么解决,但是对于缘由不是很清楚,之前也看过spring的源码,通过这次文章,对知识有了更深的了解。
案例2:错乱的注集合
我们都知道集合类型的自动注入是spring提供的另一个强大功能。
假设有这样一种需求,存在多个学生Bean,我们需要找出来,并存储到List里面去。多个Bean的定义如下,遇到这种情况:
@Bean
public Student student(){
return new Student(1,"张三");
}
@Bean
public Student student2(){
return new Student(2,"李四");
}
@Bean
public List studentList(){
Student student3=new Student(3,"王五");
Student student4=new Student(4,"赵六");
return Arrays.asList(student3,student4);
}
controller调用
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
private List studentList;
public StudentController(ListstudentList){
this.studentList=studentList;
}
@RequestMapping(path = "students", method = RequestMethod.GET)
public String listStudents(){ return studentList.toString(); };
}
运行下来,我们想要的是4个用户,张三,李四,王五,赵六,但是运行效果只有张三,李四。
找到原因:
要了解这个错误的根本原因,你就得先清楚这两种注入风格在 Spring 中是如何实现的。对于收集装配风格,Spring 使用的是 DefaultListableBeanFactory#resolveMultipleBeans 来完成装配工作,针对本案例关键的核心代码如下:
@Nullable
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) {
Class> type = descriptor.getDependencyType();
if (descriptor instanceof StreamDependencyDescriptor) { //装配stream
Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
Stream
大体过程如下:
1.获取集合类型的元素元素类型
参考的代码:
Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
2.根据元素类型,找出所有的bean
Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
3.讲匹配所有的bean类型进行转化
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
如果满足两种装配方式时,spring如何处理
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
看源码,两种方式不能共存,使用收集装配方式来装配时,能找到任何一个对应的bean,则返回,如果一个都没找到,才会采取直接装配的方式。
问题修正:
避免2种方式共存,可以使用其中一种
总之,需要注意的是:
在对于同一个集合对象的注入上,混合多种注入方式是不可取的,这样除了错乱,别无所得。
总结:这个问题之前没遇到,自己也用过,就算是学习了,至于详细的逻辑,还需要后面在仔细看源码。