Day604.@Value注入问题&集合类型注入问题 -Spring编程常见错误
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day604.@Value注入问题&集合类型注入问题 -Spring编程常见错误相关的知识,希望对你有一定的参考价值。
@Value注入问题&集合类型注入问题
讲到Spring的反转注入
,必然知道他的强大,当这次今天阿昌总结的两种问题,当@Value和Spring注入集合类型有可能会发生的问题如下:
一、@Value 没有注入预期的值
当使用@Value
,大部分的人可能觉得他只会用于在去注入String类型
的场景,当其实他也可以去注入对象类型。
对于对象类型的注入,大部分人都会选择去使用@Autowired
或者 @Resource
的方式去注入,而不会选择@Value。
举例@Autowired 和 @Value 的区别,前者可以在使用注解的时候不给予参数注明,而后者必须要求以表达式的方式
去指定要注入的内容的位置。
@Value注解源码 内容如下:↓
public @interface Value
/**
* The actual value expression — for example, <code>#systemProperties.myProp</code>.
*/
String value();
我们一般都会因为 @Value 常用于 String 类型的装配而误以为 @Value 不能用于非内置对象的装配,实际上这是一个常见的误区
@Value("#student")
private Student student;
如上的方式可以在ioc容器中寻找beanid为student的bean实例进行注入
当然,使用 @Value 更多是用来装配 String,而且它支持多种强大的装配方式,典型的方式参考下面的示例:
//注册正常字符串
@Value("我是字符串")
private String text;
//注入系统参数、环境变量或者配置文件中的值
@Value("$ip")
private String ip
//注入其他Bean属性,其中student为bean的ID,name为其属性
@Value("#student.name")
private String name;
所以上面就会涉及到,他去哪个配置源去取对应的内容进行注入?
当我们在application.properties
中配置如下配置,
username=admin
password=pass
再用@Value去取的时候会出现问题!
@RestController
@Slf4j
public class ValueTestController
@Value("$username")
private String username;
@Value("$password")
private String password;
@RequestMapping(path = "user", method = RequestMethod.GET)
public String getUser()
return username + ":" + password;
;
当我们去打印上述代码中的 username 和 password 时,我们会发现 password 正确返回了,但是 username 返回的并不是配置文件中指明的 admin,而是运行这段程序的计算机用户名。
很明显,使用 @Value 装配的值没有完全符合我们的预期。
那为什么呢?
在Spring对应@Value注解的处理的源码如下:
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> 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)
//异常处理
//省略其他非关键代码
主要设计三步:
-
寻找 @Value
- 这里就涉及到他去哪个配置源取对应的配置
- 这里就涉及到他去哪个配置源取对应的配置
-
解析 @Value 的字符串值
-
将解析结果转化为要装配的对象的类型
- 拿到数据后,并赋值装配给我们指定的对象
那上面的问题我们就可以知道为什么username的取值会出现问题:刚好系统环境变量(systemEnvironment)中含有同名的配置
所以命名时,我们一定要注意不仅要避免和环境变量冲突,也要注意避免和系统变量等其他变量冲突,这样才能从根本上解决这个问题。
二、集合类型注入错乱问题
1、收集装配
对于Spring本身的特别强大的,他可以用于对于bean对象进行收集装配
合集类型的注入,举例如下:↓
@Bean
public Student student1()
return createStudent(1, "xie");
@Bean
public Student student2()
return createStudent(2, "fang");
private Student createStudent(int id, String name)
Student student = new Student();
student.setId(id);
student.setName(name);
return student;
有了集合类型的自动注入后,我们就可以把零散的学生 Bean 收集起来了,代码示例如下:
@RestController
@Slf4j
public class StudentController
private List<Student> students;//他会直接收集ioc容器中的bean,并装配到这个集合中
//我们这里使用的构造器注入装配,所以不需要使用@Autowired
public StudentController(List<Student> students)
this.students = students;
@RequestMapping(path = "students", method = RequestMethod.GET)
public String listStudents()
return students.toString();
;
2、直接装配
当然我们也可以直接使用如下的方式直接进行直接装配
的方式:↓
@Bean
public List<Student> students()
Student student3 = createStudent(3, "liu");
Student student4 = createStudent(4, "fu");
return Arrays.asList(student3, student4);
3、当两种方式同时存在的问题
当Spring对于集合类型装配的源码如下:
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter)
final Class<?> type = descriptor.getDependencyType();
if (descriptor instanceof StreamDependencyDescriptor)
//装配stream
return stream;
else if (type.isArray())
//装配数组
return result;
else if (Collection.class.isAssignableFrom(type) && type.isInterface())
//装配集合
//获取集合的元素类型
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
if (elementType == null)
return null;
//根据元素类型查找所有的bean
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty())
return null;
if (autowiredBeanNames != null)
autowiredBeanNames.addAll(matchingBeans.keySet());
//转化查到的所有bean放置到集合并返回
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
//省略非关键代码
return result;
else if (Map.class == type)
//解析map
return matchingBeans;
else
return null;
如要的步骤如下:
- 获取集合类型的元素类型
- 针对本案例,目标类型定义为 List students,所以元素类型为 Student,获取的具体方法参考代码行:
- 根据元素类型,找出所有的 Bean
- 有了上面的元素类型,即可根据元素类型来找出所有的 Bean,关键代码行如下:
- 将匹配的所有的 Bean 按目标类型进行转化
Spring在装配对于的方式如上面两种方式的选择上的源码如下:
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null)
return multipleBeans;
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
当resolveMultipleBeans转换解析成功后就直接return了。我们看到Spring的做法是这两种装配集合的方式是不能同存
的。
结合本案例,当使用收集装配方式来装配时,能找到任何一个对应的 Bean,则返回,如果一个都没有找到,才会采用直接装配的方式。说到这里,你大概能理解为什么后期以 List 方式直接添加的 Student Bean 都不生效了吧。
那么可知解决方案,就是使用一个方式
进行对集合类型方式的注入。
直接装配:↓
@Bean
public List<Student> students()
Student student1 = createStudent(1, "xie");
Student student2 = createStudent(2, "fang");
Student student3 = createStudent(3, "liu");
Student student4 = createStudent(4, "fu");
return Arrays.asList(student1,student2,student3, student4);
收集方式:↓
@Bean
public Student student1()
return createStudent(1, "xie");
@Bean
public Student student2()
return createStudent(2, "fang");
@Bean
public Student student3()
return createStudent(3, "liu");
@Bean
public Student student4()
return createStudent(4, "fu");
在对于同一个集合对象的注入上,混合多种注入方式是不可取的,这样除了错乱,别无所得。
那最后,在案例 2 中,我们初次运行程序获取的结果如下:[Student(id=1, name=xie), Student(id=2, name=fang)]那么如何做到让学生 2 优先输出呢?
- 在代码层,
直接去改变
他们两个new出来的顺序- 添加
@Order(number)
注解,number越小优先级越高,越靠前@DependsOn
使用它,可使得依赖的Bean如果未被初始化会被优先初始化。
以上是关于Day604.@Value注入问题&集合类型注入问题 -Spring编程常见错误的主要内容,如果未能解决你的问题,请参考以下文章
Day603.Bean选取问题&找不到问题 -Spring编程常见错误
JAVA零基础小白学习免费教程day13-Collection&数据结构