Chapter 2 Section 1 a detailed explanation of the bean name generator of spring beans

Preface

Bean name generator is a very important component of beans system. Its main function is to calculate bean name from certain conditions. If there is a problem, it can be avoided. It can also be rewritten.

/** Map of bean definition objects, keyed by bean name */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

	/** Map of singleton and non-singleton bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

	/** Map of singleton-only bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

	/** List of bean definition names, in registration order */
	private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);

	/** List of names of manually registered singletons, in registration order */
	private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);

	/** Cached array of bean definition names in case of frozen configuration */
	private volatile String[] frozenBeanDefinitionNames;

As can be seen from the above data, bean management is basically based on bean name. So how to get the beanName is an important key. Therefore, it is very important to have a deep understanding of the BeanNameGenerator system

Source code interpretation

BeanNameGenerator

public interface BeanNameGenerator {
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}

BeanNameGenerator is a method declaration. The declaration of generateBeanName is not complicated. Passing BeanDefinition and BeanDefinitionRegistry returns a beanname of string type. So this section is relatively simple to read in depth.

DefaultBeanNameGenerator

public class DefaultBeanNameGenerator implements BeanNameGenerator {

	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
	}

}
public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
	return generateBeanName(beanDefinition, registry, false);
}

public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException {
	// ((class <? > beanclassobject). Getname() returns the fully qualified name of class
	// It could also be a class name
	String generatedBeanName = definition.getBeanClassName();
	if (generatedBeanName == null) {
		if (definition.getParentName() != null) {
			//When generatedBeanName is null,parentName is not empty. Named parentName+"$child"
			generatedBeanName = definition.getParentName() + "$child";
		}else if (definition.getFactoryBeanName() != null) {
			//When generatedBeanName is null,FactoryBeanName is not empty. The name is FactoryBeanName+"$child"
			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;
	// generatedBeanName + "#" + value
	// isInnerBean is true. Use the system identityHashCode as the value, and false use the self increasing method as the value
	if (isInnerBean) {
		// Inner bean: generate identity hashcode suffix.
		id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
	}else {
		int counter = -1;
		// Go to the container to see if there is a bean definition with the same name
		while (counter == -1 || registry.containsBeanDefinition(id)) {
			counter++;
			id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
		}
	}
	return id;
}
beanName generation rule

AnnotationBeanNameGenerator

public class AnnotationBeanNameGenerator implements BeanNameGenerator {

	private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";

	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		// Judgment is the implementation of annotated bean definition, which is obtained from annotation.
		if (definition instanceof AnnotatedBeanDefinition) {
			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
			// If it is text, it will return the beanName, but it is also possible that the value of annotation is null, and then it will be obtained from buildDefaultBeanName
			if (StringUtils.hasText(beanName)) {
				return beanName;
			}
		}
		return buildDefaultBeanName(definition, registry);
	}

	protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
		// Get all annotations on a class or method
		AnnotationMetadata amd = annotatedDef.getMetadata();
		// Get the class names of all annotation s
		Set<String> types = amd.getAnnotationTypes();
		String beanName = null;
		for (String type : types) {
			// Interpret the field and value in the annotation as a map. The field name is key and value is value
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
			// Determine whether the annotation is valid or not, and whether there is a value in the field as beanName
			if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) {
				// Get the value of the value field from the annotation,
				Object value = attributes.get("value");
				if (value instanceof String) {
					String strVal = (String) value;
					if (StringUtils.hasLength(strVal)) {
						if (beanName != null && !strVal.equals(beanName)) {
							throw new IllegalStateException("Stereotype annotations suggest inconsistent " +"component names: '" + beanName + "' versus '" + strVal + "'");
						}
						beanName = strVal;
					}
				}
			}
		}
		return beanName;
	}

	
	protected boolean isStereotypeWithNameValue(String annotationType,Set<String> metaAnnotationTypes, Map<String, Object> attributes) {
		// Judge whether the type of annotation is these three
		// org.springframework.stereotype.Component
		// javax.annotation.ManagedBean
		// javax.inject.Named
		
		boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
				(metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) ||
				annotationType.equals("javax.annotation.ManagedBean") ||
				annotationType.equals("javax.inject.Named");
		// And value has a value. Will return true
		return (isStereotype && attributes != null && attributes.containsKey("value"));
	}

	
	protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return buildDefaultBeanName(definition);
	}

	
	protected String buildDefaultBeanName(BeanDefinition definition) {
		// Get class name
		String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
		// Change the first letter of class name from uppercase to lowercase
		return Introspector.decapitalize(shortClassName);
	}

}

The scanned notes are

org.springframework.stereotype.Component#value() org.springframework.stereotype.Repository#value() org.springframework.stereotype.Service#value() org.springframework.stereotype.Controller#value() javax.inject.Named#value() javax.annotation.ManagedBean#value()

When there is no value in the value field of the annotation, the first lowercase class name will be the beanName by default

Detailed explanation

Register beannamegenerator object to ApplicationContext

AnnotationConfigApplicationContext
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
		this.reader.setBeanNameGenerator(beanNameGenerator);
		this.scanner.setBeanNameGenerator(beanNameGenerator);
		getBeanFactory().registerSingleton(
				AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
	}
AnnotationConfigWebApplicationContext
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
		this.beanNameGenerator = beanNameGenerator;
	}

Get objects from ApplicationContext through beanName

@Override
public Object getBean(String name) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(name);
}

@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(name, requiredType);
}

Get objects from BeanFactory through beanName

@Override
public Object getBean(String name) throws BeansException {
	return getBean(name, Object.class);
}

@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
	try {
		if (isSingleton(name)) {
			return doGetSingleton(name, requiredType);
		}
		else {
			return lookup(name, requiredType);
		}
	}
	catch (NameNotFoundException ex) {
		throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment");
	}
	catch (TypeMismatchNamingException ex) {
		throw new BeanNotOfRequiredTypeException(name, ex.getRequiredType(), ex.getActualType());
	}
	catch (NamingException ex) {
		throw new BeanDefinitionStoreException("JNDI environment", name, "JNDI lookup failed", ex);
	}
}
private <T> T doGetSingleton(String name, Class<T> requiredType) throws NamingException {
	synchronized (this.singletonObjects) {
		if (this.singletonObjects.containsKey(name)) {
			Object jndiObject = this.singletonObjects.get(name);
			if (requiredType != null && !requiredType.isInstance(jndiObject)) {
				throw new TypeMismatchNamingException(
						convertJndiName(name), requiredType, (jndiObject != null ? jndiObject.getClass() : null));
			}
			return (T) jndiObject;
		}
		T jndiObject = lookup(name, requiredType);
		this.singletonObjects.put(name, jndiObject);
		return jndiObject;
	}
}

Posted by zszucs on Mon, 30 Mar 2020 14:54:21 -0700