Spring Bean name generation rules (including source code analysis, custom Spring Bean name method)

The goal of the article: Recently, because of some problems with the name of Spring Bean, I have made some in-depth understanding, and I need to be able to help everyone.
Spring Bean name generation rules (including source code analysis, custom Spring Bean name method)
? Creator: Jay…
Personal homepage: Jay’s personal homepage
Outlook: If the content of this article is helpful to you, please give it a thumbs up, thank you.

Introduction

The popular description of the Spring container, we understand it as a Map, the key-value in the Map.

  • key: beanName
  • value: singleton bean object

So based on the above understanding, whether our beanName may be repeated, then let’s explore the generation rules of the Bean name:

BeanNameGenerator

This class defines a strategy interface for bean generation to generate bean names.
Source location

Interface implementation relationship

Rule 1 – AnnotationBeanNameGenerator (default rule)

Springboot can view the configuration of the attribute nameGenerator in @ComponentScan through the startup class, and the default is AnnotationBeanNameGenerator.

It can handle @Component and all its derived annotations, and also supports JavaEE’s javax.annotation.@ManagedBean, and JSR 330’s javax.inject.@Named annotations.

Case 1

If you do not specify a bean name, an appropriate name will be generated based on the short name of the class (lowercase first letter), and the project will fail to start when duplicates occur.

The class name is com.doc.service.impl.InstanceServiceImpl, the bean name is instanceServiceImpl

Case 2

If the bean name is specified, the custom bean name shall prevail. When duplication occurs, the project cannot be started.

The following bean name is myBean

package com.doc.service.impl;

@Service("myBean")
public class InstanceServiceImpl {<!-- -->}

Source code understanding

// @since 2.5 Its appearance is accompanied by the appearance of @Component
public class AnnotationBeanNameGenerator implements BeanNameGenerator {<!-- -->

// The most basic annotations supported (including its derived annotations)
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {<!-- -->
\t\t
// //Judge whether it is a subclass of AnnotatedBeanDefinition, AnnotatedBeanDefinition is a subclass of BeanDefinition
// Obviously this generator only automatically generates names for AnnotatedBeanDefinition
if (definition instanceof AnnotatedBeanDefinition) {<!-- -->
\t
// The determineBeanNameFromAnnotation method, in short, is to see if your annotation has a value marked, if specified, the specified one shall prevail
// All annotations supported: as explained above~~~
// If multiple annotations are configured here and value values are specified, but the value values are found to be different, an exception will be thrown~~~~~
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {<!-- -->
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
// If not specified, let the generator generate it here~~~
return buildDefaultBeanName(definition, registry);
}

// Its method is protected. It can be seen that if we want to customize the generator, we can inherit it and override it
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {<!-- -->
return buildDefaultBeanName(definition);
}
// Here is to get the short name of ClassUtils.getShortName first
protected String buildDefaultBeanName(BeanDefinition definition) {<!-- -->
String beanClassName = definition. getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils. getShortName(beanClassName);
\t
// Call the method of java.beans.Introspector, the first letter is lowercase
return Introspector.decapitalize(shortClassName);
}

}

Logical steps

  • Read all annotation types
  • Traverse all annotation types and find all annotations with non-empty value attributes supported by Component
  • fallback to generate beanName by yourself

Rule 2 – DefaultBeanNameGenerator

It is a Bean name generator used to process xml resource files

Case 1

This method is suitable for the case where the bean name is repeated. According to the following case, you can see that the bean name is class path#N, and when the class path is repeated, then N increment.

The following bean name is com.doc.service.impl.InstanceServiceImpl#0

package com.doc.service.impl;

@Service
public class InstanceServiceImpl {<!-- -->}

Explanation of source code

// @since 2.0.3
public class DefaultBeanNameGenerator implements BeanNameGenerator {<!-- -->

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {<!-- -->
// isInnerBean If it is an inner class, it means true, and this tool class can also handle it
return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}

}

The specific processing method is entrusted to the BeanDefinitionReaderUtils.generateBeanName method for processing

public abstract class BeanDefinitionReaderUtils {<!-- -->

// unique, "#1", "#2" etc will be appended, until the name becomes
public static final String GENERATED_BEAN_NAME_SEPARATOR = BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR;

// isInnerBean: to distinguish between inner bean (innerBean) and top-level bean (top-level bean).
public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException {<!-- -->

// Get the full class name of BeanClassName in the Bean definition information
// Note that this is not necessary, because if it is an inheritance relationship, it will still work with the parent class
String generatedBeanName = definition. getBeanClassName();
if (generatedBeanName == null) {<!-- -->
\t\t\t\t
// If the full class name of this class is not configured, get the full class name of the parent class + $child" to represent yourself
if (definition. getParentName() != null) {<!-- -->
generatedBeanName = definition. getParentName() + "$child";
}
// Factory Bean uses the name of the method + "$created"
else if (definition. getFactoryBeanName() != null) {<!-- -->
generatedBeanName = definition.getFactoryBeanName() + "$created";
}
}
// If none is found, throw an error~
if (!StringUtils.hasText(generatedBeanName)) {<!-- -->
throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
}

//isInnerBean=true means that if you are an inner class, the name has been added as follows
String id = generatedBeanName;
if (isInnerBean) {<!-- -->
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
\t
// if not an inner class (which is true in most cases)
// Note for this method: You must be able to guarantee that your BeanName is unique~~~~
else {<!-- -->
// Top-level bean: use plain class name with unique suffix if necessary.
// Top-level represents the outermost Bean, that is to say, non-internal classes generate an absolutely unique BeanName here~~~~
return uniqueBeanName(generatedBeanName, registry);
}
return id;
}
public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {<!-- -->
String id = beanName;
int counter = -1;

// Increase counter until the id is unique.
while (counter == -1 || registry. containsBeanDefinition(id)) {<!-- -->
counter + + ;
id = beanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
return id;
}

}

Logical steps:

  1. Read the class name of the bean instance to be generated (not necessarily the actual type at runtime).
  2. If the type is empty, determine whether there is a parent bean, and if so, read parent bean’s name + “$child”.
  3. If the parent bean is empty, then judge whether there is a factory bean, if it exists, the factory bean name + “$created”. The prefix is generated here
  4. If the prefix is empty, an exception is thrown directly, and there is no basis for defining this bean.
  5. If the prefix exists, determine whether it is an internal bean (innerBean, the default here is false), if so, finally prefix + separator + hexadecimal hashcode
  6. If it is a top-level bean (top-level bean), judge whether the bean with the prefix + number already exists, and loop through the query until an unused id is found. The processing is completed (So this generator can guarantee the uniqueness of the Bean definition, and there will be no Bean name coverage problem)

It should be noted that DefaultBeanNameGenerator is almost in a deprecated state in Spring

Custom build rules

Write implementation class code

Implement BeanNameGenerator, rewrite the generateBeanName method, and write rules according to actual needs.

/**
 * @author : Jay
 * @description : Custom Bean
 * @date : 2023-03-24 17:55
 **/
@SuppressWarnings("all")
public class MyBeanNameGenerator implements BeanNameGenerator {<!-- -->
    @Override
    public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {<!-- -->
        String beanClassName = beanDefinition. getBeanClassName();
        //
        int index = beanClassName. lastIndexOf(".");
        beanClassName = beanClassName. substring(index + 1);
        String customizedBeanName = Introspector. decapitalize(beanClassName);
        System.out.println(customizedBeanName);
        return customizedBeanName;
    }
}

Configurator nameGenerator is a custom class

@SpringBootApplication
@ComponentScan(nameGenerator = MyBeanNameGenerator.class)
public class SpringbootDemoApplication {<!-- -->

    public static void main(String[] args) {<!-- -->
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }

}

Results

Result of custom bean name: jayDemoConfig

/**
 * @author : Jay
 * @description:
 * @date : 2022-10-14 13:42
 **/
@Configuration
public class DemoConfig {<!-- -->}

Summary

If you encounter a problem one day and finally locate the cause because of repeated BeanName, then I dare to guess that you should have spent a lot of time in order to find this problem. Understanding the principle of Spring Bean generation will help us solve more similar problems.