《Spring 编程常见错误50例》-学习笔记-Day3

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 编程常见错误50例》-学习笔记-Day3_第1张图片

 我们找到了和我们配置一样的,值和我们的不一致,取了这边的。

这种问题怎么解决:

避免和环境变量冲突,也要避免和系统变量等其他变量冲突

总结:可能工作中也会遇到这种问题,也知道怎么解决,但是对于缘由不是很清楚,之前也看过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 stream = matchingBeans.keySet().stream()
					.map(name -> descriptor.resolveCandidate(name, type, this))
					.filter(bean -> !(bean instanceof NullBean));
			if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
				stream = stream.sorted(adaptOrderComparator(matchingBeans));
			}
			return stream;
		}
		else if (type.isArray()) { //装配数组
			Class componentType = type.getComponentType();
			ResolvableType resolvableType = descriptor.getResolvableType();
			Class resolvedArrayType = resolvableType.resolve(type);
			if (resolvedArrayType != type) {
				componentType = resolvableType.getComponentType().resolve();
			}
			if (componentType == null) {
				return null;
			}
			Map matchingBeans = findAutowireCandidates(beanName, componentType,
					new MultiElementDescriptor(descriptor));
			if (matchingBeans.isEmpty()) {
				return null;
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
			Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
			if (result instanceof Object[]) {
				Comparator comparator = adaptDependencyComparator(matchingBeans);
				if (comparator != null) {
					Arrays.sort((Object[]) result, comparator);
				}
			}
			return result;
		}
		else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {//装配集合
             //获取集合的元素类型

			Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
			if (elementType == null) {
				return null;

			}
//根据元素类型查找装配所有的bean

			Map 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);
			if (result instanceof List) {
				if (((List) result).size() > 1) {
					Comparator comparator = adaptDependencyComparator(matchingBeans);
					if (comparator != null) {
						((List) result).sort(comparator);
					}
				}
			}
			return result;
		}
		else if (Map.class == type) {//解析map
			ResolvableType mapType = descriptor.getResolvableType().asMap();
			Class keyType = mapType.resolveGeneric(0);
			if (String.class != keyType) {
				return null;
			}
			Class valueType = mapType.resolveGeneric(1);
			if (valueType == null) {
				return null;
			}
			Map matchingBeans = findAutowireCandidates(beanName, valueType,
					new MultiElementDescriptor(descriptor));
			if (matchingBeans.isEmpty()) {
				return null;
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.addAll(matchingBeans.keySet());
			}
			return matchingBeans;
		}
		else {
			return null;
		}
	}

大体过程如下:

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种方式共存,可以使用其中一种

总之,需要注意的是:

在对于同一个集合对象的注入上,混合多种注入方式是不可取的,这样除了错乱,别无所得。

总结:这个问题之前没遇到,自己也用过,就算是学习了,至于详细的逻辑,还需要后面在仔细看源码。

你可能感兴趣的:(spring,学习,java,后端)