@Value注入问题&集合类型注入问题 讲到Spring的反转注入
,必然知道他的强大,当这次今天阿昌总结的两种问题,当@Value和Spring注入集合类型有可能会发生的问题如下:
一、@Value 没有注入预期的值 当使用@Value
,大部分的人可能觉得他只会用于在去注入String类型
的场景,当其实他也可以去注入对象类型。
对于对象类型的注入,大部分人都会选择去使用@Autowired
或者 @Resource
的方式去注入,而不会选择@Value。
举例@Autowired 和 @Value 的区别,前者可以在使用注解的时候不给予参数注明,而后者必须要求以表达式的方式
去指定要注入的内容的位置。
@Value注解源码 内容如下:↓
public @interface Value { 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@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注解的处理的源码如下:
&#64;Nullable public Object doResolveDependency ( DependencyDescriptor descriptor, &#64;Nullable String beanName, &#64;Nullable Set < String > autowiredBeanNames, &#64;Nullable TypeConverter typeConverter) throws BeansException { Class < ? > type &#61; descriptor. getDependencyType ( ) ; Object value &#61; getAutowireCandidateResolver ( ) . getSuggestedValue ( descriptor) ; if ( value !&#61; null ) { if ( value instanceof String ) { String strVal &#61; resolveEmbeddedValue ( ( String ) value) ; BeanDefinition bd &#61; ( beanName !&#61; null && containsBean ( beanName) ? getMergedBeanDefinition ( beanName) : null ) ; value &#61; evaluateBeanDefinitionString ( strVal, bd) ; } TypeConverter converter &#61; ( typeConverter !&#61; null ? typeConverter : getTypeConverter ( ) ) ; try { return converter. convertIfNecessary ( value, type, descriptor. getTypeDescriptor ( ) ) ; } catch ( UnsupportedOperationException ex) { } } }
主要设计三步 &#xff1a;
寻找 &#64;Value
这里就涉及到他去哪个配置源取对应的配置 解析 &#64;Value 的字符串值
将解析结果转化为要装配的对象的类型
拿到数据后&#xff0c;并赋值装配给我们指定的对象 那上面的问题我们就可以知道为什么username的取值会出现问题&#xff1a;刚好系统环境变量&#xff08;systemEnvironment&#xff09;中含有同名的配置
所以命名时&#xff0c;我们一定要注意不仅要避免和环境变量冲突&#xff0c;也要注意避免和系统变量等其他变量冲突&#xff0c;这样才能从根本上解决这个问题。
二、集合类型注入错乱问题 1、收集装配 对于Spring本身的特别强大的&#xff0c;他可以用于对于bean对象进行收集装配
合集类型的注入&#xff0c;举例如下&#xff1a;↓
&#64;Bean public Student student1 ( ) { return createStudent ( 1 , "xie" ) ; } &#64;Bean public Student student2 ( ) { return createStudent ( 2 , "fang" ) ; } private Student createStudent ( int id, String name) { Student student &#61; new Student ( ) ; student. setId ( id) ; student. setName ( name) ; return student; }
有了集合类型的自动注入后&#xff0c;我们就可以把零散的学生 Bean 收集起来了&#xff0c;代码示例如下&#xff1a;
&#64;RestController &#64;Slf4j public class StudentController { private List < Student > students; public StudentController ( List < Student > students) { this . students &#61; students; } &#64;RequestMapping ( path &#61; "students" , method &#61; RequestMethod . GET) public String listStudents ( ) { return students. toString ( ) ; } ; }
2、直接装配 当然我们也可以直接使用如下的方式直接进行直接装配
的方式&#xff1a;↓
&#64;Bean public List < Student > students ( ) { Student student3 &#61; createStudent ( 3 , "liu" ) ; Student student4 &#61; createStudent ( 4 , "fu" ) ; return Arrays . asList ( student3, student4) ; }
3、当两种方式同时存在的问题 当Spring对于集合类型装配的源码如下&#xff1a;
private Object resolveMultipleBeans ( DependencyDescriptor descriptor, &#64;Nullable String beanName, &#64;Nullable Set < String > autowiredBeanNames, &#64;Nullable TypeConverter typeConverter) { final Class < ? > type &#61; descriptor. getDependencyType ( ) ; if ( descriptor instanceof StreamDependencyDescriptor ) { return stream; } else if ( type. isArray ( ) ) { return result; } else if ( Collection . class . isAssignableFrom ( type) && type. isInterface ( ) ) { Class < ? > elementType &#61; descriptor. getResolvableType ( ) . asCollection ( ) . resolveGeneric ( ) ; if ( elementType &#61;&#61; null ) { return null ; } Map < String , Object > matchingBeans &#61; findAutowireCandidates ( beanName, elementType, new MultiElementDescriptor ( descriptor) ) ; if ( matchingBeans. isEmpty ( ) ) { return null ; } if ( autowiredBeanNames !&#61; null ) { autowiredBeanNames. addAll ( matchingBeans. keySet ( ) ) ; } TypeConverter converter &#61; ( typeConverter !&#61; null ? typeConverter : getTypeConverter ( ) ) ; Object result &#61; converter. convertIfNecessary ( matchingBeans. values ( ) , type) ; return result; } else if ( Map . class &#61;&#61; type) { return matchingBeans; } else { return null ; } }
如要的步骤如下&#xff1a;
获取集合类型的元素类型 针对本案例&#xff0c;目标类型定义为 List students&#xff0c;所以元素类型为 Student&#xff0c;获取的具体方法参考代码行&#xff1a; 根据元素类型&#xff0c;找出所有的 Bean 有了上面的元素类型&#xff0c;即可根据元素类型来找出所有的 Bean&#xff0c;关键代码行如下&#xff1a; 将匹配的所有的 Bean 按目标类型进行转化 Spring在装配对于的方式如上面两种方式的选择上的源码如下&#xff1a;
Object multipleBeans &#61; resolveMultipleBeans ( descriptor, beanName, autowiredBeanNames, typeConverter) ; if ( multipleBeans !&#61; null ) { return multipleBeans; } Map < String , Object > matchingBeans &#61; findAutowireCandidates ( beanName, type, descriptor) ;
当resolveMultipleBeans转换解析成功后就直接return了。我们看到Spring的做法是这两种装配集合的方式是不能同存
的。
结合本案例&#xff0c;当使用收集装配方式来装配时&#xff0c;能找到任何一个对应的 Bean&#xff0c;则返回&#xff0c;如果一个都没有找到&#xff0c;才会采用直接装配的方式。说到这里&#xff0c;你大概能理解为什么后期以 List 方式直接添加的 Student Bean 都不生效了吧。
那么可知解决方案 &#xff0c;就是使用一个方式
进行对集合类型方式的注入。
直接装配 &#xff1a;↓
&#64;Bean public List < Student > students ( ) { Student student1 &#61; createStudent ( 1 , "xie" ) ; Student student2 &#61; createStudent ( 2 , "fang" ) ; Student student3 &#61; createStudent ( 3 , "liu" ) ; Student student4 &#61; createStudent ( 4 , "fu" ) ; return Arrays . asList ( student1&#xff0c;student2&#xff0c;student3, student4) ; }
收集方式 &#xff1a;↓
&#64;Bean public Student student1 ( ) { return createStudent ( 1 , "xie" ) ; } &#64;Bean public Student student2 ( ) { return createStudent ( 2 , "fang" ) ; } &#64;Bean public Student student3 ( ) { return createStudent ( 3 , "liu" ) ; } &#64;Bean public Student student4 ( ) { return createStudent ( 4 , "fu" ) ; }
在对于同一个集合对象的注入上&#xff0c;混合多种注入方式是不可取的&#xff0c;这样除了错乱&#xff0c;别无所得。
那最后&#xff0c;在案例 2 中&#xff0c;我们初次运行程序获取的结果如下&#xff1a;[Student(id&#61;1, name&#61;xie), Student(id&#61;2, name&#61;fang)]那么如何做到让学生 2 优先输出呢&#xff1f;
在代码层&#xff0c;直接去改变
他们两个new出来的顺序 添加&#64;Order(number)
注解&#xff0c;number越小优先级越高&#xff0c;越靠前 &#64;DependsOn
使用它&#xff0c;可使得依赖的Bean如果未被初始化会被优先初始化。