• Sping bean 的默认名称


    先来看一个例子,我定义一个Service , IDataService 它有2个实现类:OracleServiceSQLService
    代码如下:

    public interface IDataService {
        void showMsg(String msg) ;
    }
    
    • 1
    • 2
    • 3
    @Repository
    public class OracleService implements IDataService{
        @Override
        public void showMsg(String msg) {
            System.out.println("oracle >>> " + msg);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @Repository
    public class SQLService implements IDataService{
        @Override
        public void showMsg(String msg) {
            System.out.println("SQL >>>> " +msg);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试调用代码如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("classpath:demo_sacn.xml")
    public class TestService {
    
        @Autowired
        IDataService dataService;
    
        @Test
        public void dd() {
            dataService.showMsg("oracle service");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行,结果报错了,如下:

    No qualifying bean of type 'com.example.demo.service.IDataService' available: expected single matching bean but found 2: oracleService,sqlService
    
    • 1

    为啥会报错,原因很简单,我这里有2个IDataService的实现类。那么如何解决呢?
    通用的解法是加上限定符的注解 @Qualifier,如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("classpath:demo_sacn.xml")
    public class TestService {
    
        @Qualifier("oracleService")
        @Autowired
        IDataService dataService;
    
        @Test
        public void dd() {
            dataService.showMsg("oracle service");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这样就能正常执行。@Qualifier("oracleService") 中的 oracleService 就是 Bean 的默认名称。

    这里我们很容易得出一个结论:对于 Bean 的名字,如果没有显式指明,就应该是类名,不过首字母应该小写。但是这个轻松得出的结论成立么?

    我们进行一个测试,修改@Qualifier("oracleService")中的值为 @Qualifier("sQLService"),结果发现报错了。

    No qualifying bean of type 'com.example.demo.service.IDataService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=sQLService), @org.springframework.beans.factory.annotation.Autowired(required=true)}
    
    • 1

    满怀信心运行完上面的程序,依然会出现之前的错误,而如果改成 SQLiteDataService,则运行通过了。这和之前的结论又矛盾了。所以,显式引用 Bean 时,首字母到底是大写还是小写呢

    看案例的话,当我们启动基于 Spring Boot 的应用程序时,会自动扫描我们的 Package,以找出直接或间接标记了 @Component 的 Bean 的定义(即 BeanDefinition)。例如 OracleServiceSQLService 都被标记了 @Repository,而 Repository 本身被 @Component 标记,所以它们都是间接标记了 @Component

    一旦找出这些 Bean 的信息,就可以生成这些 Bean 的名字,然后组合成一个个 BeanDefinitionHolder 返回给上层。

    基本匹配我们前面描述的过程,其中方法调用 BeanNameGenerator#generateBeanName 即用来产生 Bean 的名字,它有两种实现方式。因为 DataService 的实现都是使用注解标记的,所以 Bean 名称的生成逻辑最终调用的其实是 AnnotationBeanNameGenerator#generateBeanName 这种实现方式,我们可以看下它的具体实现,代码如下:

     public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
            if (definition instanceof AnnotatedBeanDefinition) {
                String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
                if (StringUtils.hasText(beanName)) {
                    return beanName;
                }
            }
            // Fallback: generate a unique default bean name.                
            return buildDefaultBeanName(definition, registry);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    大体流程只有两步:看 Bean 有没有显式指明名称,如果有则用显式名称,如果没有则产生一个默认名称。很明显,在我们的案例中,是没有给 Bean 指定名字的,所以产生的 Bean 的名称就是生成的默认名称,查看默认名的产生方法 buildDefaultBeanName,其实现如下:

        protected String buildDefaultBeanName(BeanDefinition definition) {
            String beanClassName = definition.getBeanClassName();
            Assert.state(beanClassName != null, "No bean class name set");
            String shortClassName = ClassUtils.getShortName(beanClassName);
            return Introspector.decapitalize(shortClassName);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    首先,获取一个简短的 ClassName,然后调用 Introspector#decapitalize 方法,设置首字母大写或小写,具体参考下面的代码实现:

        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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    到这,我们很轻松地明白了前面两个问题出现的原因:如果一个类名是以两个大写字母开头的,那么这个Bean的默认名称就是类名,其它情况下默认就是类名的首字母变成小写。结合我们之前的案例,SQLServiceBean,其名称应该就是类名本身,而 OracleService 的 Bean 名称则变成了首字母小写(oracleService)。

    知道了 Bean 默认名称的规则是不是就意味着我们能搞定所有 Bean 的显式引用,不再犯错了呢?天真了。我们可以沿用上面的案例,稍微再添加点别的需求,例如我们需要定义一个内部类来实现一种新的 IDataService,我在之前的测试类里面添加了内部类,代码如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("classpath:demo_sacn.xml")
    public class TestService {
    
        @Qualifier("oracleService") 
        @Autowired
        IDataService dataService;
    
        @Test
        public void dd() {
            dataService.showMsg("oracle service");
        }
    
    
        @Repository
        public static class InnerDataService implements IDataService {
    
            @Override
            public void showMsg(String msg) {
                System.out.println("我是内部类的service >> " + msg);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    然后我修改引用的地方,类名首字母小写,@Qualifier("innerDataService"),结果已运行还是报错。
    上面贴出了如何产生默认 Bean 名的方法(即 AnnotationBeanNameGenerator#buildDefaultBeanName),当时我们只关注了首字母是否小写的代码片段,而在最后变换首字母之前,有一行语句是对 class 名字的处理,代码如下:

    String shortClassName = ClassUtils.getShortName(beanClassName);
    
    • 1
    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    假如,我们上面的内部类的 classNamecom.example.demo.service.TestService.InnerDataService,那么调用 getShortName 方法后,得出的 Bean 名称就是 TestService.InnerDataService,我们所以我们在案例程序中,直接使用 innerDataService自然找不到想要的 Bean的,改成 **@Qualifier(“testService.InnerDataService”)**就可以了。
    所以源码的理解和解读还是蛮重要的。

    其实,如果不想这么麻烦还是可以在定义 InnerDataService 的时候加上显式的名称@Repository("innerService"),如下:

        @Repository("innerService")
        public static class InnerDataService implements IDataService {
    
            @Override
            public void showMsg(String msg) {
                System.out.println("我是内部类的service >> " + msg);
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样,引用地方的限定符就可以这样改了 @Qualifier("innerService")。所以,如果搞不清楚 Bean 默认的名称,就可以在定义的时候加上显式的 Bean 名称。

  • 相关阅读:
    JavaScript+Jquery知识点速查
    PLL锁相环倍频原理
    MarkDown实现文章内跳转
    uni-app集成uni-simple-router,报错:Uncaught ReferenceError: ROUTES is not defined
    关于利用python进行文本读取的技巧
    数论基础2
    SpringBoot项目基础设施搭建
    用于原发性进行性失语症分类的可解释性机器学习影像组学模型
    软考 - 05 信息物理系统(Cyber Physical Systems, CPS)
    Hystirx限流:信号量隔离和线程池隔离
  • 原文地址:https://blog.csdn.net/zhujiangtaotaise/article/details/127870510