Spring Source Series--Source Analysis of bean Components

brief introduction The spring-bean component ...
Constructor ArgumentValues and ArgumentsHolder
Parametric construction instantiation
Several key points of knowledge
applyPropertyValues
setPropertyValue
brief introduction

The spring-bean component is the core of Spring IoC. We can use its beanFactory to get the objects we need. The instantiation, property assembly and initialization of objects can be handed over to Spring for management.This article will start with DefaulTListableBeanFactory.getBeanThe (Class) method begins the analysis of the process of getting beans, mainly as follows: Due to the length of the section, you can choose to read as needed:

  1. Design of beanFactory
  2. Processing of multiple beanName s
  3. Get a single bean
  4. Create a single bean
  5. Instantiation of bean s
  6. Attribute assembly for bean s
  7. Initialization of bean s (omitted)

There are many source codes for spring-bean s, some code that does not affect the overall analysis idea will be omitted (it will be noted in the code), and it may be unrealistic to want to analyze all the code, so I will stop here for some content, for example, this article will only analyze single beans instead of multiple beans.

Previous Review

Last Blog Spring Source Series (1) - Detailed introduction to bean components Introduces some important theoretical concepts of bean components and demonstrates how to use bean components with examples.In retrospect, these concepts are important and provide the theoretical basis for the bean component:

  1. Concepts of instantiation, attribute assembly, and initialization.Instantiation refers to the creation of a new object; assembly of attributes refers to the assignment of member attributes to the object; and initialization refers to the invocation of the object's initialization method.
  2. What is a bean: An instance or description object of a class is registered with the Spring IoC container, and the object of that class obtained through the Spring IoC container is a bean.
  3. What is a beanFactory: A factory for registering beans and getting beans.
  4. What is a beanDefinition: A description object that describes information such as instantiation, property assembly, initialization of beans.
Design of beanFactory

From a client perspective, a complete beanFactory factory contains the following basic functions:

  1. Register an alias.Corresponds to the AliasRegistry interface shown below.
  2. Register the singleton object.Corresponds to the SingletonBeanRegistry interface shown below.
  3. Register BeanDefinition objects.The BeanDefinitionRegistry interface corresponding to the figure below.
  4. Get the bean.This corresponds to the BeanFactory interface shown below.

In the spring-bean component, DefaultListableBeanFactory is a complete beanFactory factory, or an IoC container.

BeanFactory also has several extension interfaces, probably ConfigurableBeanFactory and AutoowireCapableBeanFactory:

  1. HierarchicalBeanFactory is used to support parent-child factories.For example, if the current beanFactory cannot find a bean, it tries to get it from the parent beanFactory.
  2. ConfigurableBeanFactory is used to provide support for configuring beanFactory.For example, register BeanPostProcessor, register TypeConverter, register OrderComparator, and so on.
  3. ListableBeanFactory is used to provide support for batch acquisition of beans (beans without parent factories).For example, we can get a map of a beanName-bean based on its type.
  4. AutowireCapableBeanFactory is used to provide support for managing the life cycle of bean s such as instantiation, property assembly, initialization, and so on.For example, the interface includes methods such as createBean, autowireBean, initializeBean, destroyBean, and so on.

When we register beans, the registration information for beans is placed in two different places, depending on how they are registered.

class DefaultSingletonBeanRegistry { // beanName=singletonObject key-value pairs // In addition to where the registerSingleton will be placed, the single bean instance generated by the registerBeanDefinition will also be placed here private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); } class DefaultListableBeanFactory { // BeanName=beanDeflection key-value pair private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); }

Next, we start to analyze the source code. Registering bean s is easy, so let's just look at the code of getBean(Class).

Start with the getBean(requiredType) method

Enter DefaultListableBeanFactory.getBean(Class) method, and expand gradually.In DefaulTListableBeanFactory.resolveBeanIn the (ResolvableType, Object[], boolean) method, if the bean is not available in the current beanFactory, an attempt is made to obtain it from the parent beanFactory, which also indicates that the parent and child beanFactories allow beans with the same beanName, but the current beanFactory has a higher priority when it is acquired.

public <T> T getBean(Class<T> requiredType) throws BeansException { // Adaptive Input Parameters // As you can see, we can also specify construction parameters when getting bean s return getBean(requiredType, (Object[]) null); } public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); // Continue Adapting Input Parameters // The third parameter here indicates that if the beanName corresponding to the specified type is not unique, true returns null and false throws an exception Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false); // Throw an exception if the bean is not available if (resolved == null) { throw new NoSuchBeanDefinitionException(requiredType); } return (T) resolved; } private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) { // NamedBeanHolder here simply encapsulates a layer of bean instances without much attention NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull); // If you get a bean instance, return if (namedBean != null) { return namedBean.getBeanInstance(); } // If not, try to get it from parent beanFactory // This part of the code omits...... return null; }
What if there are multiple beanName s

Obtaining beans from a beanType may result in a type that has multiple beanName s associated with it. As we have said in the use example, this can be done by specifying isPrimary = true for the beanDefinition or by registering a comparator.Next, let's look at the process.

Enter DefaultListableBeanFactory.resolveNamedBean(ResolvableType, Object[], boolean) method.If the specified type matches more than one beanName, the following processing occurs:

  1. If there are beanNames registered with registerSingleton, or beanNames registered with registerBeanDefinition and autowireCandidate = true, only those beanNames are retained and the other beanNames are excluded;
  2. If there are still multiple beanNames, check to see if there is only one that passes through the registerBeanDefinition and isPrimary = true (multiple errors occur), and if so, make it the only beanName to match to;
  3. If there are still multiple beanNames, use our registered OrderComparator to determine the smallest priority as the only beanName. Note that the objects registered with registerSingleton and registered with registerBeanDefinition are not the same;
  4. If there are still multiple beanName s, according to nonUniqueAsNull, return null for true and throw a NoUniqueBeanDefinitionException exception for false.
private <T> NamedBeanHolder<T> resolveNamedBean( ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); // Gets all beanName s of the specified type, possibly matching multiple String[] candidateNames = getBeanNamesForType(requiredType); // If the specified type matches more than one beanName, do the following: // If there are beanNames registered with registerSingleton, or beanNames registered with registerBeanDefinition and autowireCandidate = true, only those beanNames are retained and the other beanNames are excluded; if (candidateNames.length > 1) { List<String> autowireCandidates = new ArrayList<>(candidateNames.length); for (String beanName : candidateNames) { if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { autowireCandidates.add(beanName); } } if (!autowireCandidates.isEmpty()) { candidateNames = StringUtils.toStringArray(autowireCandidates); } } // If there is only one beanName left, get the beans based on the beanName and beanType if (candidateNames.length == 1) { String beanName = candidateNames[0]; return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args)); } // If more than one exists, further processing is required else if (candidateNames.length > 1) { Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length); // Traverse candidate beanName for (String beanName : candidateNames) { // If the beanName is registered through registerSingleton and the incoming construction parameter is empty // Get the bean instance and put it in candidates if (containsSingleton(beanName) && args == null) { Object beanInstance = getBean(beanName); candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance)); } else { // In other cases, get the type corresponding to the beanName and put it in candidates // Note that the type here is not necessarily the type we specified, for example, if I specified UsErServiceFactoryBean.classAnd here we're returningUserService.class candidates.put(beanName, getType(beanName)); } } // If there is only one registered by registerBeanDefinition and isPrimary=true (multiple errors occur), make it the only beanName to match to String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass()); // If you are still unsure, use our registered OrderComparator to determine the priority of the value in candidates and select the key corresponding to the lowest priority as the only beanName if (candidateName == null) { candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass()); } if (candidateName != null) { Object beanInstance = candidates.get(candidateName); // If the value in candidates itself is a bean instance, it would be good to go back directly // If not, get beans based on beanName and beanType if (beanInstance == null || beanInstance instanceof Class) { beanInstance = getBean(candidateName, requiredType.toClass(), args); } return new NamedBeanHolder<>(candidateName, (T) beanInstance); } // If you still cannot determine the unique beanName and set nonUniqueAsNull=false (default is false), an error will occur if (!nonUniqueAsNull) { throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); } } return null; }
Get beans from beanName and beanType

Get intoAbstractBeanFactory.getBean(String, Class<T>, Object...).There are four steps in this approach:

  1. Escape name.Escape names when they are aliases or in the form of'&'+ factory beanName;
  2. If it is a single bean and the construction parameter is empty, the generated beans will be retrieved from singletonObjects, or from earlySingletonObjects/singletonFactories, beans that have been instantiated but may not have been assembled or initialized.If the result is not null, return the corresponding bean instance directly.
  3. If the current beanFactory does not have a specified beanName, it is fetched in the parent beanFactory;
  4. If the current bean needs to depend on other beans, it will get the dependent beans first.
  5. Generate a single bean or multiple beans according to scope selection;
  6. For type checking, if the obtained bean s do not match, they will be converted with our registered type converter first, and if they do not match, BeanNotOfRequiredTypeException will be thrown.
public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException { // Adaptive Input Parameters // The last parameter here refers to whether the retrieved beans are purely for type checking, and if so, the beanFactory will not mark that the beans are being generated and will only be useful for single beans return doGetBean(name, requiredType, args, false); } @SuppressWarnings("unchecked") protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // Escape the name we passed in, which includes two things: // 1. If it is an alias, beanName that needs to be converted to an alias object; // 2. If'&'+ factoryBeanName, you need to remove the previous'&' final String beanName = transformedBeanName(name); Object bean; // Get singletons // Note that what you get here may be bean s that have already been initialized, that have not been initialized, or that have not even been assembled Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // Omit log section...... // Get the bean, because sharedInstance might be a factoryBean, and if we want a corresponding bean for the factoryBean, we also need a getObject bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // If the current thread is already generating beanName-corresponding beans, an error will occur if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // If the current beanFactory does not have a specified beanName, it is fetched in the parent beanFactory // This section omits....... // This tag specifies that the beans are being created and generally makes sense for single beans if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { // Gets the RootBeanDefinition object corresponding to the specified beanName final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // Check RootBeanDefinition, which is currently checking to see if the corresponding type is an abstract class. Yes, there was a mistake checkMergedBeanDefinition(mbd, beanName, args); // If the current bean needs to depend on other beans, the dependent beans are obtained first // This section omits....... // Create a single bean if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { // Enter Create bean or factoryBean return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); // Get a bean instance bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // Create multiple bean s else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // Mark that the current thread is creating the bean beforePrototypeCreation(beanName); // Enter Create bean or factoryBean prototypeInstance = createBean(beanName, mbd, args); } finally { // Remove the tag that this bean is creating in the current thread afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // This is generally a custom Scope scenario, which is left out here else { // ······· } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // If the obtained bean instance is not of the type we specified if (requiredType != null && !requiredType.isInstance(bean)) { try { // Convert using our registered type converter T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); // If it cannot be converted, an error will occur if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }

Since the creation of single beans is similar to that of multiple beans, this article only chooses single beans for analysis.

Get a single bean

Enter DefaultSingletonBeanRegistry.getSingleton(String, ObjectFactory).This approach involves several processes, mainly dealing with multithreaded problems:

  1. Gets the beans of the specified beanName, if they already exist, and does not create them, in order to handle the problem of creating beans simultaneously with multiple threads;
  2. If the current bean is already in the process of being created, a BeanCurrentlyInCreationException will be thrown, which should not happen if a single bean was previously locked;
  3. Create a single bean;
  4. If created successfully, add the bean instance to singletonObjects and delete the corresponding key-value pairs in singletonFactories and earlySingletonObjects.
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); // I don't really understand why singletonObjects are used as locks here // Because when you get bean s from earlySingletonObjects/singletonFactories that have been instantiated but may not have been assembled or initialized, the locks used are also singletonObjects, so the mechanisms exposed in advance seem to be obsolete???TODO synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // Creation of a single is not allowed if the current beanFactory's instance is being destroyed if (this.singletonsCurrentlyInDestruction) { // Omitting errors...... } // Judging if the current bean is already being created throws the BeanCurrentlyInCreationException if it is // Because of the lock, this should not happen beforeSingletonCreation(beanName); boolean newSingleton = false; // Omit part of the code. try { // The createBean method is executed here singletonObject = singletonFactory.getObject(); newSingleton = true; } // I don't understand that. Should singletonObjects not be locked?TODO catch (IllegalStateException ex) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } // If BeanCreationException is thrown, catch (BeanCreationException ex) { // Omit part of the code. throw ex; } finally { // Omit part of the code. // IllegalStateException is thrown if the current bean is not in the create state afterSingletonCreation(beanName); } // If created successfully, add the bean instance to singletonObjects and delete the corresponding key-value pairs in singletonFactories and earlySingletonObjects if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }

In the above method, if you cannot get the generated single bean, you start creating the bean.

Create a single bean

Enter AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[]).This method includes the following processes:

  1. Parse the beanType and wrap the RootBeanDefinition again;
  2. Execute the postProcessBeforeInstantiation method of our registered InstantiationAwareBeanPostProcessor and return a non-empty object if it is returned.That is, we can customize the instantiation, assembly, and initialization of bean s in this method.
  3. Create a bean.
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { RootBeanDefinition mbdToUse = mbd; // Resolve the current RootBeanDefinition's corresponding generated bean type and wrap it again Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Omit part of the code. try { // Execute the postProcessBeforeInstantiation method of our registered InstantiationAwareBeanPostProcessor.That is, we can customize the instantiation, assembly, and initialization of bean s in this method. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); // If the method returns a bean, it returns directly if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { // Create bean s Object beanInstance = doCreateBean(beanName, mbdToUse, args); return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } }

Enter AbstractAutowireCapableBeanFactory.doCreateBean(String, RootBeanDefinition, Object[]).This method mainly contains the following processes:

  1. Instantiate the bean;
  2. Execute the postProcessMergedBeanDefinition method of our registered MagedBeanDefinitionPostProcessor;
  3. If it is a singleton, expose the beans that have not been assembled and initialized first, that is, put them in singletonFactories, and if other threads come in to get them, return the beans or factoryBean s without waiting;
  4. Attribute assembly;
  5. Initialization;
  6. Put the generated bean s into disposableBeans.
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // instantiation // If it is a singleton, try to get it from factoryBeanInstanceCache if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } // Instantiate bean s if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Execute the postProcessMergedBeanDefinition method of our registered MagedBeanDefinitionPostProcessor synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // A singleton can expose bean s that have not been assembled and initialized, that is, placed in singletonFactories boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // Attribute assembly populateBean(beanName, mbd, instanceWrapper); // Initialization exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } // Omit part of the code. // Place the generated bean s or factoryBean s in disposableBeans try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }

Next, you'll expand instantiation, property assembly, and initialization of bean s.Among them, the code for instantiation and attribute assembly is more complex, so we focus on the analysis. As for the initialization part, it is left for the reader to read.

instantiation

Enter AbstractAutowireCapableBeanFactory.createBeanInstance(String, RootBeanDefinition, Object[]).The main process of this method is as follows:

  1. Parse the beanType and do some necessary checks on it;
  2. Get the bean directly through the InstanceSupplier or FactoryMethod that we set up, and return the object directly if any;
  3. If the construction parameter is empty, the parsed construction object can be reused, if any;
  4. Execute determineCandidateConstructors of our registered Smart InstantiationAwareBeanPostProcessor to get an array of construction objects;
  5. If the resulting array is not empty, or if the assembly mode of the beanDefinition is a construction injection, or if the beanDefinition contains a construction parameter, or if the construction parameter we passed in is not empty, the instantiated bean is entered
  6. Otherwise, use parameterless constructs to instantiate.
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Resolve bean Type Class<?> beanClass = resolveBeanClass(mbd, beanName); // Error if bean type is not public if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } // Get instantiated bean s from Supplier defined in RootBeanDefinition Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } // Instantiate bean s by defining FactoryMethod in RootBeanDefinition if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // If the construct parameter is empty, you can reuse the parsed construct object (if any) boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } // Execute determineCandidateConstructors of our registered Smart InstantiationAwareBeanPostProcessor to get an array of Constructor objects, if any Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); // If the resulting array is not empty, or if the assembly mode of the beanDefinition is a construction injection, or if the beanDefinition contains a construction parameter, or if the construction parameter we passed in is not empty, enter the instantiated bean or factoryBean if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Omit part of the code. // Instantiate bean s or factoryBean s using parameterless constructs return instantiateBean(beanName, mbd); }

Instantiation methods include parametric and parametric construct instantiation. This article only discusses the case of parametric construct instantiation.

Constructor ArgumentValues and ArgumentsHolder

Before proceeding with the analysis, it is necessary to understand the Constructor ArgumentValues and ArgumentsHolder classes.

First, Constructor ArgumentValues defines the value of the parameter list for the construction method.In spring, Constructor ArgumentValues are generally defined in BeanDefinition objects, which affect the instantiation of beans and are the basis for selecting construction objects when instantiating beans.

public class ConstructorArgumentValues { // Index + Parameter Value // For example, a construction method corresponding to a new User(int age, String name, String address) can contain elements: 0=new ValueHolder (18), 2=new ValueHolder (Beijing) private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>(); // General parameter values // For example, for a construction method corresponding to a new User(int age, String name, String address), if indexed ArgumentValues do not contain a value for name, it can be found in generic ArgumentValues, and we only need to add the element: new ValueHolder ("zzs001").String.class) private final List<ValueHolder> genericArgumentValues = new ArrayList<>(); // Internal class representing the value of a parameter public static class ValueHolder implements BeanMetadataElement { @Nullable private Object value; @Nullable private String type; @Nullable private String name; @Nullable private Object source; private boolean converted = false; }

ArgumentsHolder is the internal class of ConstructorResolver and, like ConstructorArgumentValues, is the value of the parameter list used to define the construction method. The difference is that the value of ConstructorArgumentValues is "unresolved", while ArgumentsHolder contains "preparedArguments", "rawArguments", and "argume ArgumentValues Completed".Nts) Three values.

Why is that so?Because the type of parameter value in Constructor ArgumentValues does not necessarily match that in the construction method, there are two cases:

  1. Types are different, but they can be converted through TypeConverter.For example, in the construction method of new User(int age, String name, Address address), I can add 2=new AddressVO() to Constructor ArgumentValues, at which point whenever spring finds the appropriate converter, the conversion process is as follows:'rawArguments'-'Resolution Complete'.
  2. Different types, the values of the parameters point to other bean s and, of course, can be references recognized by other spring s.For example, in the construction method of new User(int age, String name, Address address), I can add 2=new RootBeanDefinition in Constructor ArgumentValues.Address.class), the conversion process is "preparedArguments" -- "rawArguments".
private static class ArgumentsHolder { public final Object[] rawArguments; public final Object[] arguments; public final Object[] preparedArguments; public boolean resolveNecessary = false; }

After understanding these two classes, we continue to analyze the source code for the instantiation.

Parametric construction instantiation

Enter AbstractAutowiReCapableBeanFactory.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[]) method.A ConstructorResolver object is created here and its autowireConstructor method is called directly.

protected BeanWrapper autowireConstructor( String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) { return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); }

Get intoConstructorResolver.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[]).This method has a lot of code and can be divided into two scenarios for better understanding:

  1. Explicitly specify the construction parameters in the parameter.The parameter values for this scenario are all parsed by default, so no parsing is required, and the scenario requires that the number of parameters for the corresponding construction object be the same as specified.
  2. ConstructorArgumentValues are specified in the BeanDefinition object.The parameter values for this scenario need to be converted in two steps, and the scenario requires that the number of parameters corresponding to the construction object be no less than the specified number.
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); // Define the constructor that will ultimately be used to instantiate the object Constructor<?> constructorToUse = null; // Define the object that holds the construction parameters ("Unresolved", "Parsed Incomplete", "Parsed Complete") ArgumentsHolder argsHolderToUse = null; // Define the construction parameters that will ultimately be used to instantiate the object Object[] argsToUse = null; // The input parameter explicitly declares the construction parameter (Scenario 1), so you don't need to parse the parameter list values, but you need to parse the construction object if (explicitArgs != null) { argsToUse = explicitArgs; } else { Object[] argsToResolve = null; // The BeanDefinition object specifies ConstructorArgumentValues (Scenario 2), which do not need to be resolved if the parameter list value or construction object has already been resolved synchronized (mbd.constructorArgumentLock) { constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // Found a cached constructor... argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { argsToResolve = mbd.preparedConstructorArguments; } } } if (argsToResolve != null) { argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true); } } // Enter Parse Parameter List Value and Construct Object if (constructorToUse == null || argsToUse == null) { // If the input does not explicitly specify an array of construction objects, use reflection to get Constructor<?>[] candidates = chosenCtors; if (candidates == null) { Class<?> beanClass = mbd.getBeanClass(); try { // BeanDefinition allows you to define whether to include non-public methods candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); } catch (Throwable ex) { // Omit Code...... } } // If there is only one parameterless construct in the array and no parameter list value is specified in both the input and BeanDefinition, the construct parameter marking the BeanDefinition object is resolved and the bean is instantiated if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) { // Omit Code...... } // Determine if parsing constructs is required boolean autowiring = (chosenCtors != null || mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); // Here is the parameter list value for Parse Incomplete ConstructorArgumentValues resolvedValues = null; // Get the minimum number of required construction parameters int minNrOfArgs; // The input parameter explicitly declares the construction parameter (Scenario 1), minNrOfArgs being the length of the specified array if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { // Specifying Constructor ArgumentValues in the BeanDefinition object (Scenario 2) requires minNrOfArgs to be calculated and converted to Unresolved --> Unresolved Parsing ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); resolvedValues = new ConstructorArgumentValues(); minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } // Arrange from small to large based on number of parameters AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; Set<Constructor<?>> ambiguousConstructors = null; LinkedList<UnsatisfiedDependencyException> causes = null; // Traversing through candidate construction objects for (Constructor<?> candidate : candidates) { // Gets the number of parameters for the current construction object int parameterCount = candidate.getParameterCount(); // Jump out of loop 1 if the previous loop has found a matching construction object if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) { break; } // If the number of parameters for the current construction object is less than minNrOfArgs, iterate through the next // Note that explicitly specifying a construction parameter (Scenario 1) in the input parameter requires that the number of parameters for the corresponding construction object be the same as specified.Specifying Constructor ArgumentValues (Scenario 2) in the BeanDefinition object requires that the number of parameters for the corresponding construction object be no less than the number specified if (parameterCount < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; // Gets an array of parameter types for the current construction object Class<?>[] paramTypes = candidate.getParameterTypes(); // The case where Constructor ArgumentValues is specified in the BeanDefinition object (Scenario 2) if (resolvedValues != null) { // Convert Parse Not Completed - > Parse Complete try { // This is to process the Constructor Properties annotation for JDK6, otherwise null will be returned. String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { // Gets an array of parameter names for the current construction object paramNames = pnd.getParameterNames(candidate); } } // Create ArgumentsHolder object argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1); } catch (UnsatisfiedDependencyException ex) { // Omit Code...... continue; } } // The case where a construction parameter (Scenario 1) is explicitly specified in the arguments else { // Continue looping if the number of current construction parameters is less than the number of specified parameters if (parameterCount != explicitArgs.length) { continue; } // Create an ArgumentsHolder object, because there is no need to parse parameters, so raw, prepared, resolved are all the same argsHolder = new ArgumentsHolder(explicitArgs); } // Calculates the difference between the specified parameter and the type of parameter currently constructed int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); // Difference value less than threshold if (typeDiffWeight < minTypeDiffWeight) { // Get matched construction objects and parameters constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } // The difference value is greater than the threshold value, which is not considered else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { // Omit Code...... } } // If an appropriate construction object is not found, an error will be thrown if (constructorToUse == null) { // Omit Code...... } else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { // Omit Code...... } // The BeanDefinition object specifies Constructor ArgumentValues (Scenario 2), and in order to reuse the parsed constructs and parameter lists, it is necessary to mark that the current BeanDefinition's construct parameters have been parsed if (explicitArgs == null && argsHolderToUse != null) { argsHolderToUse.storeCache(mbd, constructorToUse); } } Assert.state(argsToUse != null, "Unresolved constructor arguments"); // The next step is to instantiate objects using constructed objects and parameters instead of looking down. bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse)); return bw; }

The instantiation part is more difficult, but there are some abstract concepts to understand first, such as the conversion of two scenes, parameters, and so on.

Attribute assembly

Enter AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper).This method includes the following processes:

  1. Execute the postProcessAfterInstantiation method of our registered InstantiationAwareBeanPostProcessor, and if false is returned, return directly without assembling attributes;
  2. Gets the PropertyValues object in the beanDefinition and populates the PropertyValues object according to the injection type set by the beanDefinition;
  3. Perform the postProcessProperties method of our registered InstantiationAwareBeanPostProcessor to modify the PropertyValues object;
  4. Dependency check (if set);
  5. Perform attribute assembly.
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { // If the instance object is empty, throw an exception or return directly if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { return; } } // Execute the postProcessAfterInstantiation method of our registered InstantiationAwareBeanPostProcessor, and if false is returned, return directly without assembling attributes if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } // Gets the PropertyValues in the BeanDefinition object, which contains a list of PropertyValue objects with name=value PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); // Fill according to the injection method we set int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Assembly by name if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Assembly by type if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } // Is InstantiationAwareBeanPostProcessors registered in beanFactory boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); // Is Dependency Check Set in BeanDefinition Object boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { // If it is empty, get it from the BeanDefinition object again, TODO? pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // Perform the postProcessProperties method of our registered InstantiationAwareBeanPostProcessor to modify the PropertyValues object PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); // Omit part of the code. pvs = pvsToUse; } } } // If a dependency check is set in the BeanDefinition object, you need to check the dependency settings if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs != null) { // Perform attribute assembly applyPropertyValues(beanName, mbd, bw, pvs); } }

This method mainly involves three methods: autowireByName, autowireByType and applyPropertyValues. The first two methods are not expanded for the time being, just the last one.

Several key points of knowledge

Before analyzing the applyPropertyValues method, we need to know a few points of knowledge.Use this class as an example.

public class User { private String name; private int age; private Address address; private List<String> hobbies; } class Address { private String region; private String desc; }

Several forms of propertyName

When we set a property value for a beanDefinition, this assignment is generally used, and it becomes "normal form".

rootBeanDefinition.getPropertyValues().add("name", "zzs001"); rootBeanDefinition.getPropertyValues().add("age", 18); rootBeanDefinition.getPropertyValues().add("address", new Address("", "")); rootBeanDefinition.getPropertyValues().add("hobbies", new ArrayList());

spring also supports other assignments for member attributes of type object, list, array, map, and so on, as follows: "Nested object form" and "Index form":

// Nested Object Form rootBeanDefinition.getPropertyValues().add("address.region", ""); rootBeanDefinition.getPropertyValues().add("address.desc", ""); // Index Form rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

It is precisely because propertyName introduces many forms that the simple assignment behavior is very complex.For example, the form of nested objects can be as follows:foo.user.address.region, can be nested almost always.

PropertyAccessor

A propertyAccessor object is generally bound to an instance object, and its properties can be accessed through the method of the PropertyAccessor interface.The final assignment to a member's property in a property assembly is to call its setPropertyValue method.A map is maintained in AbstractNestablePropertyAccessor, where key is the property name (without nesting and indexing) of the currently bound object, and value is the property name for the PropertyAccessor object.

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor { private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors; }

In the example above,

rootBeanDefinition.getPropertyValues().add("name", "zzs001"); rootBeanDefinition.getPropertyValues().add("age", 18);

This form shares a ProertyAccessor object bound to an instance of the User type.

// Nested Object Form rootBeanDefinition.getPropertyValues().add("address.region", ""); rootBeanDefinition.getPropertyValues().add("address.desc", "");

This form shares a ProertyAccessor object bound to an instance of the Address type, which is maintained in nested PropertyAccessor s with the name "address".

// Index Form rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

This form is also a ProertyAccessor object bound to an instance of the User type, which is maintained in nested PropertyAccessor s with the name "hobbies".

PropertyTokenHolder

PropertyTokenHolder is the internal class of AbstractNestablePropertyAccessor, which is more of a propertyName for "indexed form".For example, "hobbies[0]" in the ProertyTokenHolder, actualName = hobbies, canonicalName = [0], keys = .

protected static class PropertyTokenHolder { public PropertyTokenHolder(String name) { this.actualName = name; this.canonicalName = name; } public String actualName; public String canonicalName; @Nullable public String[] keys; }

Next, continue to analyze the code for the attribute assembly.

applyPropertyValues

Enter AbstractAutowireCapableBeanFactory.applyPropertyValues(String, BeanDefinition, BeanWrapper, PropertyValues) method.As with construction parameters, parameters that set member properties require "two conversions", which are not explained in detail here.This method mainly includes the following processes:

  1. Get a list of property objects, and if the property objects of this list have completed two conversions, assemble the properties directly;
  2. Traverse through the list of property objects and convert them twice. If there are no objects like BeanDefinition, BeanDefinitionHolder, etc. in the list, the property values object is set and the conversion is completed. The next time this method is called, the conversion is not needed.
  3. Attribute assembly.
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { // If there are no properties to inject, return directly if (pvs.isEmpty()) { return; } // Omit part of the code. MutablePropertyValues mpvs = null; // Get a list of property objects List<PropertyValue> original; if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; // Assemble attributes directly if all attribute objects have completed a "two-pass" if (mpvs.isConverted()) { try { bw.setPropertyValues(mpvs); return; } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } original = mpvs.getPropertyValueList(); } else { original = Arrays.asList(pvs.getPropertyValues()); } // Get our registered type converter TypeConverter converter = getCustomTypeConverter(); if (converter == null) { converter = bw; } // Create parser for first conversion BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); // Define a list to hold property objects that complete two conversions // Note that there is no so-called replication, so don't get confused by the name List<PropertyValue> deepCopy = new ArrayList<>(original.size()); boolean resolveNecessary = false; // Traversing Attribute Objects for (PropertyValue pv : original) { // The current property object has completed two conversions and is added directly to the list if (pv.isConverted()) { deepCopy.add(pv); } else { String propertyName = pv.getName(); Object originalValue = pv.getValue(); // Omit part of the code. // First conversion Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); Object convertedValue = resolvedValue; // If the current property is writable and the property name is not similar toFoo.barOr addresses[0], a second conversion is required boolean convertible = bw.isWritableProperty(propertyName) && !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); if (convertible) { convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); } // If the converted property object is the same as the original object, it generally refers to the normal object, not BeanDefinition, BeanDefinitionHolder, etc. if (resolvedValue == originalValue) { // Set the multiplexed target object if a second conversion is required if (convertible) { pv.setConvertedValue(convertedValue); } // Add original property object to list deepCopy.add(pv); } // This is not considered else if (convertible && originalValue instanceof TypedStringValue && !((TypedStringValue) originalValue).isDynamic() && !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { pv.setConvertedValue(convertedValue); deepCopy.add(pv); } // Other cases else { // Tags need to be parsed every time resolveNecessary = true; // Add original property object to list deepCopy.add(new PropertyValue(pv, convertedValue)); } } } // If you do not include BeanDefinition, BeanDefinitionHolder, and so on, set PropertyValues to Converted so that no conversion is required the next time this method is called if (mpvs != null && !resolveNecessary) { mpvs.setConverted(); } // Attribute assembly try { bw.setPropertyValues(new MutablePropertyValues(deepCopy)); } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); } }

Enter AbstractPropertyAccessor.setPropertyValues(PropertyValues) method.This iterates through the list of attribute objects, one by one.

public void setPropertyValues(PropertyValues pvs) throws BeansException { // Access Adaptation // The next two parameters represent whether the NotWritablePropertyException exception is ignored, and whether the NullValueInNestedPathException exception is ignored. setPropertyValues(pvs, false, false); } public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { // Get a list of property objects List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); for (PropertyValue pv : propertyValues) { // Omit try-catch code and other exception-related code....... setPropertyValue(pv); } }

setPropertyValue

Enter AbstractNestablePropertyAccessor.setPropertyValue(PropertyValue).This method includes the following processes:

  1. Gets the PropertyAccessor object corresponding to the propertyName, where the propertyName of the Nested Object Form is resolved;
  2. Create a PropertyTokenHolder object, where the propertyName in Index Form is parsed;
  3. Use the PropertyAccessor object for assignment.
public void setPropertyValue(PropertyValue pv) throws BeansException { // Adaptive Input Parameters setPropertyValue(pv.getName(), pv.getValue()); } public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException { AbstractNestablePropertyAccessor nestedPa; try { // Gets the PropertyAccessor object corresponding to the propertyName, where the propertyName of the Nested Object Form is resolved // If present in the cache, it will be reused nestedPa = getPropertyAccessorForPropertyPath(propertyName); } catch (NotReadablePropertyException ex) { throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", ex); } // Create a PropertyTokenHolder object, where the propertyName in Index Form is parsed PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); // Assignment using PropertyAccessor object nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value)); }

Enter AbstractNestablePropertyAccessor.setPropertyValue(PropertyTokenHolder, PropertyValue) method.Different methods are called here depending on whether propertyName is an "index form".

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { if (tokens.keys != null) { processKeyedProperty(tokens, pv); } else { processLocalProperty(tokens, pv); } }

Here we don't look at a method where propertyName is "indexed" but at processLocalProperty.

private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { // Gets the PropertyHandler object corresponding to the actualName and reuses it if there is a cache PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); if (ph == null || !ph.isWritable()) { // Omit part of the code. } Object oldValue = null; try { Object originalValue = pv.getValue(); Object valueToApply = originalValue; if (!Boolean.FALSE.equals(pv.conversionNecessary)) { // Because our attribute parameters are all converted, we won't look at the converted code here if (pv.isConverted()) { valueToApply = pv.getConvertedValue(); } else { // Omit part of the code. } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } // The next step is to assign values to attributes through reflection and expand them later ph.setValue(valueToApply); } catch (Exception ex) { // Omit part of the code. } }

So far as code analysis for attribute assembly is concerned.

Last Supplement

The source code for spring-bean s is basically finished above.For the getBean process, the things not expanded in this article include:

  1. Get and create multiple instances of bean s;
  2. Parameterless construction instantiation;
  3. Attribute assembly in which the list of attribute values is populated (autowireByName and autowireByType), and the attribute names are indexed
  4. Initialization of bean s.

Interested readers can analyze themselves.In addition, if there are any errors in the above content, you are welcome to correct them.

Finally, thank you for reading.

Please move the related source code: spring-beans

This is an original article, please attach a link to the source of the original: https://www.cnblogs.com/ZhangZiSheng001/p/13196228.html

26 June 2020, 20:20 | Views: 2886

Add new comment

For adding a comment, please log in
or create account

0 comments