平时项目中使用时beanName是如何生成的呢?
Bean的配置方式
第一种,xml配置的方式:
<bean id="userService" class="com.busyboy.service.UserService"/>
第二种,注解的方式,@Component、@Service等
@Component
public class OrderService {
}
或者
@Component("orderService")
public class OrderService {
}
Spring源码中是如何解析的
xml方式
对于xml方式配置的bean,spring读取的源码方法是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement,方法具体内容如下方:
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 解析ID属性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 解析Name属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//别名Name属性如果有多个值,则根据 ",; " 进行分隔
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 如果id没有值,则使用别名中的第一条作为beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//调用重载方法 进行DOM元素解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
//假如未声明别名和id的bean,则生成唯一的名称作为beanName
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
//生成一个“类名”+“#数字”的名称
beanName = this.readerContext.generateBeanName(beanDefinition);
//获取类名
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
//假如类名称未使用,则将类名作为当前bean的别名
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
//将BeanDefinition封装至BeanDefinitionHolder中,并返回
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
其中如果id有值的话,beanName的值直接去id的值;
如果id没有值,存在别名aliases,则取别名中的第一条作为beanName,别名则是在xml配置是name的值,name值可以由",; " 英文状态下的逗号、分号、空格来拼接:
<bean id="userService" name="userService1,userService2;userService3 userService4" class="com.busyboy.service.UserService"/>
在下边这一步中会将name解析成数组:
public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";
//别名Name属性如果有多个值,则根据 ",; " 进行分隔
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
如果id和别名均没有配置,则按照特定方式生成beanName;
注解方式
注解方式获取beanName的流程则是判断加载的类是否存在某个注解,如果存在则判断注解中是否有值,没有值则根据默认的方式生成beanName。简单仿写了下大致流程:
//判断当前类是否存在Component注解
if (aClass.isAnnotationPresent(Component.class)){
//存在则获取其中的value值
Component component = aClass.getAnnotation(Component.class);
String beanName = component.value();
//不存在则按照默认的规则生成beanName
if ("".equals(beanName)) {
beanName = Introspector.decapitalize(aClass.getSimpleName());
}
}
这个地方需要注意的一点,默认的生成beanName的规则:如果类首字母大写,则生成的beanName是将类的首字母小写,当类名的首字母和第二个字母是大写的时候,将采用原始的类名作为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);
}
其中默认的生成beanName规则Spring官方文档 Naming Beans章节有介绍:
With component scanning in the classpath, Spring generates bean names for unnamed components, following the rules described earlier: essentially, taking the simple class name and turning its initial character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by java.beans.Introspector.decapitalize (which Spring uses here).
源码内容如下:
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// 获取注解所指定的beanName
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// 如果注解中没有定义beanName则根据默认规则生成beanName
return buildDefaultBeanName(definition, registry);
}
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return buildDefaultBeanName(definition);
}
//根据默认规则生成beanName
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);
}
总结
Spring中生成beanName的两种方式,都是基于实现BeanNameGenerator接口,AnnotationBeanNameGenerator是注解方式获取beanName,而DefaultBeanNameGenerator是xml方式获取beanName,这两种方式的不同就在于beanName的默认生成规则,XML方式生成规则是:类名+“#”+数字;而注解方式生成规则正是使用调用的上述的decapitalize方法。