Day603.Bean选取问题&找不到问题 -Spring编程常见错误

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day603.Bean选取问题&找不到问题 -Spring编程常见错误相关的知识,希望对你有一定的参考价值。

Spring编程常见错误#Bean选取问题&找不到问题

当讲到Spring,那第一会想到依赖注入、控制反转

那必然会想到一个从ioc容器拿到bean的注解@Autowired,他是用来支持依赖注入的核心利器之一。

那以下列举,在使用@Autowired注解会出现的一些小问题。

一、当Bean选择过多时

不管是Spring菜鸡还是大神,都会遇到过Spring的问题:↓

这问题就是说这个Bean是单例的,但是找到了两个。

required a single bean, but 2 were found

当如下,在ioc容器放入,DataService接口的两个Bean


public interface DataService 
    void deleteStudent(int id);


//#1
@Repository
@Slf4j
public class OracleDataService implements DataService
    @Override
    public void deleteStudent(int id) 
        log.info("delete student info maintained by oracle");
    



//#2
@Repository
@Slf4j
public class CassandraDataService implements DataService
    @Override
    public void deleteStudent(int id) 
        log.info("delete student info maintained by cassandra");
    

那在启动spring的时注入初始化的时候就会报上面的错误

当这种情况时,Spring无法默认的给出在两个bean选哪个作为DataService的实现Bean放入ioc容器中

那我们如果不给出方案策略,那就会报required a single bean, but 2 were found的错。

如下是源码如何觉得选择哪个进行注入的方案:

  • 判断是否有@Primary,进行主要选择策略
  • 判断是否@Priority,进行优先级策略
  • 判断根据 Bean 名字的严格匹配来决策
  • 最后返回 null,告知无法决策出哪种最合适
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) 
   Class<?> requiredType = descriptor.getDependencyType();
   
   //@Primary
   String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
   if (primaryCandidate != null) 
      return primaryCandidate;
   
   
   //@Priority
   String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
   if (priorityCandidate != null) 
      return priorityCandidate;
   
   
   //Bean 名字
   // Fallback
   for (Map.Entry<String, Object> entry : candidates.entrySet()) 
      String candidateName = entry.getKey();
      Object beanInstance = entry.getValue();
      if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
            matchesBeanName(candidateName, descriptor.getDependencyName())) 
         return candidateName;
      
   
   return null;

至此,我们自然也知道了解决方案

  • @Primary,进行主要选择策略
  • @Priority,进行优先级策略
  • 使用默认名字匹配,或使用@Qualifier指定选择哪个

二、选取Bean时首字母大小写问题

若我们在上面的问题中使用了@Qualifier的方式去解决问题,那么就会有可能出 选取Bean时首字母大小写问题

@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;

但是我们在给写上CassandraDataService的情况就会出现找不到bean的问题

@Autowired
@Qualifier("CassandraDataService")
DataService dataService;

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘studentController’: Unsatisfied dependency expressed through field ‘dataService’; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.spring.puzzle.class2.example2.DataService’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: @org.springframework.beans.factory.annotation.Autowired(required=true),@org.springframework.beans.factory.annotation.Qualifier(value=CassandraDataService)

那对于一般的类来说的默认结论是:对于 Bean 的名字,如果没有显式指明,就应该是类名,不过首字母应该小写

但是这个类是有两个大写开头以上,那么策略就会改变,若针对SQLiteDataService类来说使用这种规则sQLiteDataService就会出错

@Autowired
@Qualifier("sQLiteDataService")
DataService dataService;

那为什么吗?Spring在生成对应具体类的beanname的方式源码如下

public static String decapitalize(String name) 
    if (name == null || name.length() == 0) 
        return name;
    
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0)))
        return name;
    
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);

我们看到了,首先他会判断name的长度是否大于1,且第一个&第二个字母是否是大写,如果是就直接返回。不然就走下面将首字母改为小写返回

总结如果一个类名是以两个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写。

那自然有了解决方案

  • 根据如上的规则使用@Qualifier正确的指定,如@Qualifier("cassandraDataService")@Qualifier("SQLiteDataService")
  • 显示的在类头上的@Repository上去显示的指明,如@Repository("CassandraDataService")

总结:如果你不太了解源码,不想纠结于首字母到底是大写还是小写,建议你用第二种方法去避免困扰。


三、内部类的 Bean 指定问题

当要一引用一个类中的类,也就是内部类的情况下,又会有不一样的问题

public class StudentController 
	//StudentController的内部类
    @Repository
    public static class InnerClassDataService implements DataService
        @Override
        public void deleteStudent(int id) 
          //空实现
        
    
 

那如果在别处的指定bean名使用如下,也会出现找不到bean,那为什么呢?

@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;

当时我们只关注了首字母是否小写的代码片段,而在最后变换首字母之前,有一行语句是对 class 名字的处理,代码如下:

String shortClassName = ClassUtils.getShortName(beanClassName);

我们可以看下它的实现,参考 ClassUtils#getShortName 方法:

public static String getShortName(String className) 
   Assert.hasLength(className, "Class name must not be empty");
   int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
   int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
   if (nameEndIndex == -1) 
      nameEndIndex = className.length();
   
   String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
   shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
   return shortName;

那如上的类,就会经历三次的改变

  • com.spring.puzzle.class2.example3.StudentController.InnerClassDataService
  • StudentController.InnerClassDataService
  • studentController$InnerClassDataService

那最后就是studentController$InnerClassDataService,所以就应该写如下,如果要指定一个类的内部类的情况

@Autowired
@Qualifier("studentController$InnerClassDataService")
DataService innerClassDataService;

那最后,如果我想要直接用@Autowired,不去组合@Qualifier,直接来个一步到位???

去明确指定一个内部类,如下写法ok吗?

@Autowired
DataService studentController.InnerClassDataService;

答案是不行的,编译都会过不了

以上是关于Day603.Bean选取问题&找不到问题 -Spring编程常见错误的主要内容,如果未能解决你的问题,请参考以下文章

day09面向对象-

getline:找不到标识符

长沙雅礼中学集训-------------------day1(内含day0)

docker-compose 中的 Yarn 命令找不到 && 命令

找不到文件 iOS

Swift & ObjC 桥 - 找不到“WKNavigationDelegate”的协议声明