4. Dependency injection of IOC container
The initialization process of IoC container is analyzed in detail. The main work of this initialization process is to establish BeanDefinition data mapping in IoC container. In this process, I didn't see IoC container injecting Bean dependencies. Next, I'll analyze how IoC container injects Bean dependencies.
Assuming that the current IoC container has loaded the user-defined Bean information, start to analyze the principle of dependency injection. First, note that the dependency injection process is triggered when the user asks for a Bean from the IoC container for the first time. Of course, there are exceptions, that is, we can let the container complete the pre instantiation of a Bean by controlling the lazy init attribute in the BeanDefinition information. This pre instantiation is actually a process of completing dependency injection, but it is completed during initialization. When a user asks for a Bean from the IoC container, there is an interface definition of getBean () in the basic IoC container interface BeanFactory. The implementation of this interface is where the dependency injection is triggered. In order to further understand the implementation of this dependency annotation process, let's start with the base class AbstractBeanFactory of DefaultListableBeanFactoiy to see the implementation of getBean(). The code is as follows:
//In the AbstractBeanFactory class: @Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } @Override public <T> T getBean(String name, Class<T> requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); } @Override public Object getBean(String name, Object... args) throws BeansException { return doGetBean(name, null, args, false); }
You can see that getBean() in AbstractBeanFactory calls doGetBean() method. Let's take a look at this method:
//In the AbstractBeanFactory class: protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. //First, get the beans from the cache and process the beans in singleton mode that have been created. The requests for such beans do not need to be created repeatedly Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } // Here, getObjectForBeanInstance() completes the relevant processing of FactoryBean to obtain the production results of FactoryBean beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); } // This Bean is not in the container yet else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. // Here, check whether the BeanDefinition in the IOC container exists. Check whether it is possible to retrieve the required Bean from the current BeanFactory, // If you can't get it from the current factory, go to the parent BeanFactory. If you can't get it from the current parent factory, go up the parent BeanFactory chain BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate") .tag("beanName", name); try { if (requiredType != null) { beanCreation.tag("beanType", requiredType::toString); } // Here, the BeanDefinition is obtained according to the name of the Bean RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. // Get all dependent beans of the current Bean, which will trigger the recursive call of getBean until it goes to a Bean without any dependencies String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. The Bean to be created is a singleton // Here, the singleton bean instance is created by calling the createBean method. There is a callback function getObject, // The createBean of ObjectFactory will be called in getSingleto. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // The created Bean is a multi instance else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // Other scopes, such as Request, Session, application and websocket else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } } catch (BeansException ex) { beanCreation.tag("exception", ex.getClass().toString()); beanCreation.tag("message", String.valueOf(ex.getMessage())); cleanupAfterBeanCreationFailure(beanName); throw ex; } finally { beanCreation.end(); } } // Check the type of the created Bean and the type of the getBean specified in the requiredType parameter return adaptBeanInstance(name, beanInstance, requiredType); } @SuppressWarnings("unchecked") // Check the type of the created Bean. If there is no problem, return the newly created Bean, which is already a Bean containing dependencies <T> T adaptBeanInstance(String name, Object bean, @Nullable Class<?> requiredType) { // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { Object convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return (T) convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }
This is the entry of dependency injection, where dependency injection is triggered, and dependency injection occurs on the premise that the BeanDefinition data in the container has been established. Program = data + algorithm. The previous BeanDefinition is data. Let's see how these data serve dependency injection. Although the dependency injection process does not involve complex algorithm problems, it is not simple, because we all know that Spring provides many parameter configurations for the use of loC container, and each parameter configuration actually represents the implementation characteristics of an IOC container, Many of these features need to be implemented in the process of dependency injection or Bean life cycle management.
The detailed process of dependency injection will be analyzed below. A general process of dependency injection can be seen in the figure below:
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-miubl8v0-1640347862850)( https://b3logfile.com/file/2021/12/image-acbb8f58.png )]
In particular, getBean() is the starting point of dependency injection. After that, createBean() will be called. Let's learn about the implementation process through the createBean() code. In this process, the Bean object will be generated according to the requirements defined by the BeanDefinition.
This createBean() is implemented in AbstractAutowireCapableBeanFactory. createBean() not only generates the required
Bean initialization is also processed. As shown in the following code:
//AbstractAutowireCapableBeanFactory class: @Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. // Here, you can determine whether the bean to be created can be instantiated and whether this class can be loaded through the class loader Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } // Call to create a Bean to create a Bean try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } // Bean generation protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; // If it is a singleton, remove the from the cache first if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } // This is where the Bean is created, which is completed by createBeanInstance if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. 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; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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)); } // Initialize the bean instance. // This is the initialization of beans. Dependency injection often occurs here. exposedObject will be initialized after initialization // Returns the Bean after dependency injection is completed Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); 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); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
Here, we can see that the methods closely related to dependency annotation are createbean() and populateBean(), which are introduced below. The Java object contained in the Bean is generated in createbean stance (). This object can be generated in many different ways, either through the factory method or through the autowire feature of the container. These generation methods are specified by the relevant BeanDefinition. As shown in the following code, you can see the corresponding implementations of different generation methods.
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. //Determine that the class that creates the Bean instance can be instantiated Class<?> beanClass = resolveBeanClass(mbd, beanName); 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()); } Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } // Factory instantiation is used here if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... 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); } } // Candidate constructors for autowiring? // Method instantiation using constructor Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. // Instantiate the Bean using the default constructor return instantiateBean(beanName, mbd); } // The most common instantiation process is instantiateBean protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { try { Object beanInstance; if (System.getSecurityManager() != null) { beanInstance = AccessController.doPrivileged( (PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this), getAccessControlContext()); } else { beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this); } BeanWrapper bw = new BeanWrapperImpl(beanInstance); initBeanWrapper(bw); return bw; } catch (Throwable ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); } }
Here, the Bean is instantiated with CGLIB. CGLIB is a common class library of bytecode generator. It provides a series of API s to provide the function of generating and converting Java bytecode. CGLIB is also used in Spring AOP to enhance Java bytecode. In the loC container, to learn how to use CGLIB to generate Bean objects, you need to take a look at the simplelnstationationstrategy class. This class is the default class used by Spring to generate Bean objects. It provides two methods to instantiate Java objects. One is through BeanUtils, which uses the reflection function of the JVM, and the other is generated through the aforementioned CGUB. The following code:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
final Constructor<?> ctor, Object... args) {
if (!bd.hasMethodOverrides()) {
if (System.getSecurityManager() != null) {
// use own privileged to change accessibility (when security is on)
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(ctor);
return null;
});
}
//Instantiate beans using BeanUtils. The instantiation of BeanUtils instantiates beans through the Constructor, using the function of JVM reflection
return BeanUtils.instantiateClass(ctor, args);
}
else {
//Using CGLIB to instantiate objects
return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
}
}
Here, we have analyzed the whole process of instantiating Bean objects. Based on the generation of instantiated Bean objects, we will introduce how Spring processes these objects, that is, how to set the dependencies of these Bean objects and complete the whole dependency injection process after the generation of Bean objects. This process involves the processing of the attributes of various Bean objects (i.e. the processing of dependencies). The processing of these dependencies is based on the resolved BeanDefinitione. To understand this process in detail, you need to go back to the previous populateBean method. The implementation of this method in AbstractAutowireCapableBeanFactory is shown in the following code:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, final Constructor<?> ctor, Object... args) { if (!bd.hasMethodOverrides()) { if (System.getSecurityManager() != null) { // use own privileged to change accessibility (when security is on) AccessController.doPrivileged((PrivilegedAction<Object>) () -> { ReflectionUtils.makeAccessible(ctor); return null; }); } //Instantiate beans using BeanUtils. The instantiation of BeanUtils instantiates beans through the Constructor, using the function of JVM reflection return BeanUtils.instantiateClass(ctor, args); } else { //Using CGLIB to instantiate objects return instantiateWithMethodInjection(bd, beanName, owner, ctor, args); } }
Here, the BeanDefinitionValueResolver is used to parse the BeanDefinition and inject it into the property. Next, go to the BeanDefinitionValueResolver to see the implementation of the parsing process to update the Bean reference
Take row parsing as an example. The specific process of parsing Bean reference is as follows:
//In the BeanDefinitionValueResolver class: // Resolve a reference dependency and inject it into the property private Object resolveReference(Object argName, RuntimeBeanReference ref) { try { Object bean; //RuntimeBeanReference gets the type of reference. This RuntimeBeanReference is configured when loading BeanDefinition Class<?> beanType = ref.getBeanType(); //If ref is in the parent IOC container, get it from the parent IOC container if (ref.isToParent()) { BeanFactory parent = this.beanFactory.getParentBeanFactory(); if (parent == null) { throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean " + ref + " in parent factory: no parent factory available"); } if (beanType != null) { bean = parent.getBean(beanType); } else { bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName()))); } } // To obtain a Bean in the current IOC container, a getBean will be triggered here. If dependency injection does not occur, the occurrence of corresponding dependency injection will be triggered here else { String resolvedName; if (beanType != null) { NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType); bean = namedBean.getBeanInstance(); resolvedName = namedBean.getBeanName(); } else { resolvedName = String.valueOf(doEvaluate(ref.getBeanName())); bean = this.beanFactory.getBean(resolvedName); } this.beanFactory.registerDependentBean(resolvedName, this.beanName); } if (bean instanceof NullBean) { bean = null; } return bean; } catch (BeansException ex) { throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); } } //For the dependency of the reference with the collection as the attribute, resolve each dependency /**
* For each element in the managed array, resolve reference if necessary.
*/ private Object resolveManagedArray(Object argName, List<?> ml, Class<?> elementType) { Object resolved = Array.newInstance(elementType, ml.size()); for (int i = 0; i < ml.size(); i++) { Array.set(resolved, i, resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); } return resolved; } /**
* For each element in the managed list, resolve reference if necessary.
*/ private List<?> resolveManagedList(Object argName, List<?> ml) { List<Object> resolved = new ArrayList<>(ml.size()); for (int i = 0; i < ml.size(); i++) { resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); } return resolved; }
The annotator of both attributes calls resolvevaluelfnecessity (), which contains all the processing of the injection type. Let's take a look at the implementation of resolvevaluelfnecessity():
// Various attributes are parsed here @Nullable public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { // We must check each value to see whether it requires a runtime reference // to another bean to be resolved. //Here, the RuntimeBeanReference is parsed. The RuntimeBeanReference is the data object generated when parsing the BeanDefinition if (value instanceof RuntimeBeanReference) { RuntimeBeanReference ref = (RuntimeBeanReference) value; return resolveReference(argName, ref); } else if (value instanceof RuntimeBeanNameReference) { String refName = ((RuntimeBeanNameReference) value).getBeanName(); refName = String.valueOf(doEvaluate(refName)); if (!this.beanFactory.containsBean(refName)) { throw new BeanDefinitionStoreException( "Invalid bean name '" + refName + "' in bean reference for " + argName); } return refName; } else if (value instanceof BeanDefinitionHolder) { // Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases. BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value; return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition()); } else if (value instanceof BeanDefinition) { // Resolve plain BeanDefinition, without contained name: use dummy name. BeanDefinition bd = (BeanDefinition) value; String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(bd); return resolveInnerBean(argName, innerBeanName, bd); } else if (value instanceof DependencyDescriptor) { Set<String> autowiredBeanNames = new LinkedHashSet<>(4); Object result = this.beanFactory.resolveDependency( (DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter); for (String autowiredBeanName : autowiredBeanNames) { if (this.beanFactory.containsBean(autowiredBeanName)) { this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName); } } return result; } else if (value instanceof ManagedArray) { // May need to resolve contained runtime references. ManagedArray array = (ManagedArray) value; Class<?> elementType = array.resolvedElementType; if (elementType == null) { String elementTypeName = array.getElementTypeName(); if (StringUtils.hasText(elementTypeName)) { try { elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader()); array.resolvedElementType = elementType; } catch (Throwable ex) { // Improve the message by showing the context. throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Error resolving array type for " + argName, ex); } } else { elementType = Object.class; } } return resolveManagedArray(argName, (List<?>) value, elementType); } else if (value instanceof ManagedList) { // May need to resolve contained runtime references. return resolveManagedList(argName, (List<?>) value); } else if (value instanceof ManagedSet) { // May need to resolve contained runtime references. return resolveManagedSet(argName, (Set<?>) value); } else if (value instanceof ManagedMap) { // May need to resolve contained runtime references. return resolveManagedMap(argName, (Map<?, ?>) value); } else if (value instanceof ManagedProperties) { Properties original = (Properties) value; Properties copy = new Properties(); original.forEach((propKey, propValue) -> { if (propKey instanceof TypedStringValue) { propKey = evaluate((TypedStringValue) propKey); } if (propValue instanceof TypedStringValue) { propValue = evaluate((TypedStringValue) propValue); } if (propKey == null || propValue == null) { throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Error converting Properties key/value pair for " + argName + ": resolved to null"); } copy.put(propKey, propValue); }); return copy; } else if (value instanceof TypedStringValue) { // Convert value to target type here. TypedStringValue typedStringValue = (TypedStringValue) value; Object valueObject = evaluate(typedStringValue); try { Class<?> resolvedTargetType = resolveTargetType(typedStringValue); if (resolvedTargetType != null) { return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType); } else { return valueObject; } } catch (Throwable ex) { // Improve the message by showing the context. throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Error converting typed String value for " + argName, ex); } } else if (value instanceof NullBean) { return null; } else { return evaluate(value); } }
After this parsing process is completed, conditions have been prepared for dependency annotation, dependency has been prepared, and there are various ways to deal with attributes. Then you need to really set the Bean object to the place where it is dependent on another Bean attribute. The occurrence of dependency injection is called in the setPropertyValues() of BeanWrapper. The concrete implementation is implemented in processKeyedProperty of the AbstractNestablePropertyAccessor class.
private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) { Object propValue = getPropertyHoldingValue(tokens); PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); if (ph == null) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + tokens.actualName, "No property handler found"); } Assert.state(tokens.keys != null, "No token keys"); String lastKey = tokens.keys[tokens.keys.length - 1]; //Property injection of Array type if (propValue.getClass().isArray()) { Class<?> requiredType = propValue.getClass().getComponentType(); int arrayIndex = Integer.parseInt(lastKey); Object oldValue = null; try { if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { oldValue = Array.get(propValue, arrayIndex); } Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), requiredType, ph.nested(tokens.keys.length)); int length = Array.getLength(propValue); if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { Class<?> componentType = propValue.getClass().getComponentType(); Object newArray = Array.newInstance(componentType, arrayIndex + 1); System.arraycopy(propValue, 0, newArray, 0, length); int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); String propName = tokens.canonicalName.substring(0, lastKeyIndex); setPropertyValue(propName, newArray); propValue = getPropertyValue(propName); } Array.set(propValue, arrayIndex, convertedValue); } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, "Invalid array index in property path '" + tokens.canonicalName + "'", ex); } } // Injection of List type attributes else if (propValue instanceof List) { Class<?> requiredType = ph.getCollectionType(tokens.keys.length); List<Object> list = (List<Object>) propValue; int index = Integer.parseInt(lastKey); Object oldValue = null; if (isExtractOldValueForEditor() && index < list.size()) { oldValue = list.get(index); } Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), requiredType, ph.nested(tokens.keys.length)); int size = list.size(); if (index >= size && index < this.autoGrowCollectionLimit) { for (int i = size; i < index; i++) { try { list.add(null); } catch (NullPointerException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, "Cannot set element with index " + index + " in List of size " + size + ", accessed using property path '" + tokens.canonicalName + "': List does not support filling up gaps with null elements"); } } list.add(convertedValue); } else { try { list.set(index, convertedValue); } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, "Invalid list index in property path '" + tokens.canonicalName + "'", ex); } } } // Injecting Map type attributes else if (propValue instanceof Map) { Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length); Class<?> mapValueType = ph.getMapValueType(tokens.keys.length); Map<Object, Object> map = (Map<Object, Object>) propValue; // IMPORTANT: Do not pass full property name in here - property editors // must not kick in for map keys but rather only for map values. TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor); Object oldValue = null; if (isExtractOldValueForEditor()) { oldValue = map.get(convertedMapKey); } // Pass full property name and old value in here, since we want full // conversion ability for map values. Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), mapValueType, ph.nested(tokens.keys.length)); map.put(convertedMapKey, convertedMapValue); } // Other types of attribute injection else { throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, "Property referenced in indexed property path '" + tokens.canonicalName + "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); } }
In the process of Bean creation and object dependency injection, dependency injection needs to be completed recursively according to the information in BeanDefinition. As can be seen from the above recursive processes, these recursions take getBean() as the entry. One recursion is to find the required Bean in the context system and create the recursive call of the Bean. The other recursion is to recursively call the getBean() method of the container to get the dependent Bean of the current Bean during dependency injection, and also trigger the creation and injection of dependent beans. During dependency injection of Bean attributes, the parsing process is also a recursive process. In this way, the creation and injection of beans are completed layer by layer according to the dependencies until the creation of the current Bean is finally completed. With the creation of this top-level Bean and the completion of its attribute dependency injection, it means that the injection of the whole dependency chain related to the current Bean is also completed.
After Bean creation and dependency injection are completed, a series of beans linked by dependencies are established in the loC container. This Bean is no longer a simple java object. After the dependency relationship between the Bean series and beans is established, it can be easily used by upper layer applications through the relevant interface methods of the loC container. Continue to take the bucket as an example. Here, we not only found the water source, but also successfully loaded the water into the bucket. At the same time, we completed a series of treatment for the water in the bucket, such as disinfection and boiling. Although it is still water, after a series of treatment, the water is boiled water and can be drunk directly.