当前位置 博文首页 > 好好学习天天向上:Spring编程常见错误--Spring Core篇-03 |Spri

    好好学习天天向上:Spring编程常见错误--Spring Core篇-03 |Spri

    作者:[db:作者] 时间:2021-07-15 21:57

    三、Spring Bean 依赖注入常见错误(下)

    Spring 的自动注入

    一.@Value 没有注入预期的值:@Value查找值的范围不局限于application.property 文件

    1、代码

    1.application.properties

    username=admin
    password=pass
    

    2.Controller

    @RestController
    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;
        }
    }
    

    2、案例解析

    password 正确返回了,但是 username 返回的并不是配置文件中指明的 admin,而是运行这段程序的计算机用户名。很明显,使用 @Value 装配的值没有完全符合我们的预期。

    3、@Value 的工作的三个核心步骤

    1.寻找 @Value

    在这步中,主要是判断这个属性字段是否标记为 @Value,依据的方法参考 QualifierAnnotationAutowireCandidateResolver.findValue

    @Nullable
    protected Object findValue(Annotation[] annotationsToSearch) {
       if (annotationsToSearch.length > 0) {  
          AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
                AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
          //valueAnnotationType即为@Value
          if (attr != null) {
             return extractValue(attr);
          }
       }
       return null;
    }
    

    2.解析 @Value 的字符串值

    如果一个字段标记了 @Value,则可以拿到对应的字符串值,然后就可以根据字符串值去做解析,最终解析的结果可能是一个字符串,也可能是一个对象,这取决于字符串怎么写。

    3.将解析结果转化为要装配的对象的类型

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
       if (this.propertySources != null) {
          for (PropertySource<?> propertySource : this.propertySources) {
             Object value = propertySource.getProperty(key);
             if (value != null) {
             //查到value即退出  
             return convertValueIfNecessary(value, targetValueType);
             }
          }
       }
       return null;
    }
    

    4、@Value读取值的文件

    
    [ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, 
    StubPropertySource {name='servletConfigInitParams'}, ServletContextPropertySource {name='servletContextInitParams'}, PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'},
    OriginTrackedMapPropertySource {name='applicationConfig: classpath:/application.properties]'},
    MapPropertySource {name='devtools'}]
    

    5、问题修正

    不仅要避免和环境变量冲突,也要注意避免和系统变量等其他变量冲突,这样才能从根本上解决这个问题。

    spring.application.username=admin
    spring.application.password=pass
    

    二.错乱的注入集合:零散注入和集中注入不能共存。

    1、代码

    1.Controller

    @RestController
    public
    class StudentController {
        private List<Student> students;
        public StudentController(List<Student> students) {
            this.students = students;
        }
        @RequestMapping(path = "students", method = RequestMethod.GET)
        public String listStudents() {
            return students.toString();
        }
    }
    

    2.domain

    public class Student {
        private Integer id;
        private String name;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    

    3.零散注入

    @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;
    }
    

    4.集中注入

     private Student createStudent(int id, String name) {
            Student student = new Student();
            student.setId(id);
            student.setName(name);
            return student;
        }
        @Bean
        public List<Student> students(){
            Student student3 = createStudent(3, "liu");
            Student student4 = createStudent(4, "fu");
            return Arrays.asList(student3, student4);
        }
    

    2、案例解析

    零散注入和集中注入,两种方式分别执行的时候,都可以生效,但是当一起的时候,集中注入无法生效。

    Spring 使用的是 DefaultListableBeanFactory.resolveMultipleBeans 来完成装配工作

    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放置到集合并返回