在Spring中每一个注册到容器中的Bean都有自己的名字(至少一个),可能不止一个(别名)。对于未明确指定name的Bean,Spring会自动为其生成一个名字。而对于在xml中配置的Bean和使用诸如Service、Component等注解标识的Bean,Spring为其生成名字的方式并不相同,下面我们一一分析。

核心接口

核心接口.png

BeanNameGenerator接口定义如下

public interface BeanNameGenerator {

    /**
     * Generate a bean name for the given bean definition.
     * @param definition the bean definition to generate a name for
     * @param registry the bean definition registry that the given definition
     * is supposed to be registered with
     * @return the generated bean name
     */
    String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

BeanNameGenerator是生成beanName的顶级接口,而它有两个实现类,图中左侧的DefaultBeanNameGenerator是给XML配置中Bean使用的,图中右侧的AnnotationBeanNameGenerator则是给通过注解定义的Bean使用的。

XML配置

在此不赘述XML文件中Bean的解析过程,直接来看DefaultBeanNameGenerator,其调用链路为

DefaultBeanNameGenerator#generateBeanName—>BeanDefinitionReaderUtils#generateBeanName

最后这个方法的定义如下

public static String generateBeanName(
            BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
            throws BeanDefinitionStoreException {
        // 先拿类名赋值
        String generatedBeanName = definition.getBeanClassName();
        if (generatedBeanName == null) {
            if (definition.getParentName() != null) {
                generatedBeanName = definition.getParentName() + "$child";
            }
            else if (definition.getFactoryBeanName() != null) {
                generatedBeanName = definition.getFactoryBeanName() + "$created";
            }
        }
        if (!StringUtils.hasText(generatedBeanName)) {
            throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
                    "'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
        }

        String id = generatedBeanName;
        if (isInnerBean) {
            // 内部bean,在少数情况下走该分支,例如使用key-ref等标签时
            // Inner bean: generate identity hashcode suffix.
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
        }
        else {
            // Top-level bean: use plain class name.
            // Increase counter until the id is unique.
            // 为了保证id唯一,在其后加数字
            int counter = -1;
            while (counter == -1 || registry.containsBeanDefinition(id)) {
                counter++;
                id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
            }
        }
        return id;
    }

注释都写在了上面,逻辑很简单:类名+“#”+数字

注解配置
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        if (definition instanceof AnnotatedBeanDefinition) {
            // 如果注解的value指定了beanName,则使用该值
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        // 如果没有指定value,则为其生成beanName
        return buildDefaultBeanName(definition, registry);
    }

继续追踪

protected String buildDefaultBeanName(BeanDefinition definition) {
        String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
        return Introspector.decapitalize(shortClassName);
    }

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. 取短类名,即不包含包路径的类名,例如com.test.Student的短类名为Student,这点跟XML配置中取全类名不一样
  2. 如果短类名长度大于1,且第一个和第二个字符为大写,则直接返回短类名,也就是说假设类为com.test.STudent,则beanName为STudent
  3. 其他情况下将短类名首字符小写后返回,假设类为com.test.Student,则beanName为student
验证

由于只为了验证beanName,简单起见,Bean类中都为空

People类

@Component
public class Pepole {
}

TNtt类

@Service
public class TNttt {
}

TestPepole类

public class TestPepole {
}

TNTt类

public class TNTt {
}

其中TestPepole和TNTt通过XML配置

<bean class="com.hust.TestPepole"></bean>
    <bean class="com.hust.TNTt"></bean>

测试主类

public class App {
    public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Pepole.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TNttt.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TestPepole.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TNTt.class)));
    }
}

输出结果

["pepole"]
["TNttt"]
["com.hust.TestPepole#0"]
["com.hust.TNTt#0"]
总结
  • 在不指定beanName的情况下,Spring会自动为注册的Bean生成一个唯一的beanName
  • 通过注解注册的Bean和XML注册的Bean,Spring为其生成默认beanName的机制不一样
  • 不要盲目觉得通过注解注册的Bean,Spring为其生成beanName就是将短类名的首字母小写,当短类名的首字符和第二个字符均大写时,beanName就是短类名
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐