preface
This series is based on Spring 5.2.2.BUILD-SNAPSHOT. Because the whole system of Spring is too large, only the source code of key parts will be parsed.
This article mainly introduces how the Spring IoC container loads bean s.
text
Let's take a look first Loading and registration of Spring IoC BeanDefinition To obtain the bean instance code:
public class BeanDefinitionDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions("META-INF/bean-definition.xml"); User user = beanFactory.getBean("user", User.class); System.err.println(user); } }
Through beanFactory.getBean() this method obtains the beans defined in XML. Next, we will focus on the operation behind this method.
Before we get started, let's take a look at FactoryBean and its usage.
FactoryBean introduction
FactoryBean interface plays an important role in spring framework. Spring provides more than 70 FactoryBean implementations. They hide the details of complex beans and bring convenience to the upper application. The following is the definition of the interface:
public interface FactoryBean<T> { // Returns the bean instance created by FactoryBean. If isSingleton() returns true, // Then the instance will be put into the Spring container and the single instance cache pool @Nullable T getObject() throws Exception; // Returns the bean type created by FactoryBean @Nullable Class<?> getObjectType(); // Returns whether the scope of the bean instance created by FactoryBean is singleton or prototype default boolean isSingleton() { return true; } }
When the class attribute of < bean > in the configuration file configures the implementation class FactoryBean, what is returned through getBean() is not the FactoryBean itself, but the object returned by FactoryBean ා getobject(), which is equivalent to that FactoryBean#getObject() proxy getBean(). The following is a simple code demonstration:
First, define a Car entity class:
public class Car { private Integer maxSpeed; private String brand; private Double price; public Integer getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(Integer maxSpeed) { this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }
If the above entity class is configured in the traditional way, each property will correspond to a < property > element label. If it is implemented in FactoryBean mode, it will be more flexible. The following comma separated method is used to configure values for all properties of Car at one time.
public class CarFactoryBean implements FactoryBean<Car> { private String carInfo; @Override public Car getObject() throws Exception { Car car = new Car(); String[] infos = carInfo.split(","); car.setBrand(infos[0]); car.setMaxSpeed(Integer.valueOf(infos[1])); car.setPrice(Double.valueOf(infos[2])); return car; } @Override public Class<?> getObjectType() { return Car.class; } @Override public boolean isSingleton() { return true; } public String getCarInfo() { return carInfo; } public void setCarInfo(String carInfo) { this.carInfo = carInfo; } }
Next, we configure it in XML.
<bean id="car" class="com.leisurexi.ioc.domain.CarFactoryBean"> <property name="carInfo" value="Super car,400,2000000"/> </bean>
Finally, see the test code and running results:
@Test public void factoryBeanTest() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions("META-INF/factory-bean.xml"); Car car = beanFactory.getBean("car", Car.class); System.out.println(car); CarFactoryBean carFactoryBean = beanFactory.getBean("&car", CarFactoryBean.class); System.out.println(carFactoryBean); }
You can see that if the beanName is prefixed with & to get the FactoryBean itself, the object returned by getObject() is not added.
Factorybean is special in that it can register two beans with the container, one is itself, the other is FactoryBean.getObject() method returns the bean represented by the value.
bean loading
AbstractBeanFactory#getBean
public <T> T getBean(String name, Class<T> requiredType) throws BeansException { // Call the doGetBean method (the method that does the actual operation starting with do) return doGetBean(name, requiredType, null, false); } /** * @param name bean Name of * @param requiredType bean Type of * @param args Show incoming construction parameters * @param typeCheckOnly Type check only */ protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // Get the actual name of the bean, as detailed below final String beanName = transformedBeanName(name); Object bean; // Try to get it directly from cache or ObjectFactory in singleton factories, as detailed below Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // Check if the bean is an implementation of FactoryBean. Instead of returning beans directly, // If yes, first check whether the beanName starts with & and if it returns the FactoryBean itself, // Instead of calling factorybean ා getobject() to return an object, see the following details bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Only in the case of A single instance can we try to solve the circular dependency. In the prototype mode, if there is A // B attribute, B has A attribute, so when dependency injection, it will generate when A has not been created // For the creation of B, return to create A here, causing circular dependency if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check whether the bean definition of the current bean is in the current bean factory, // Do not call the getBean() of the parent factory recursively to get the bean BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } // If it's not just type checking, it's creating bean s, which are recorded here if (!typeCheckOnly) { // The record bean has been created markBeanAsCreated(beanName); } try { // To merge BeanDefinition, see details below final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Instantiate the dependent bean before instantiating it, that is, the beanName configured in the dependencies on property String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { // Check whether there is circular dependency, that is, the current bean depends on dep, and dep depends on the current bean, as detailed below if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } // Put the dependency of dep and beanName into the cache, as detailed below registerDependentBean(dep, beanName); try { // Get the bean instance corresponding to the dependent dep. if the instance has not been created, create it first getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // If the scope of a bean is a singleton if (mbd.isSingleton()) { // To create and register a singleton bean, see details below sharedInstance = getSingleton(beanName, () -> { try { // Create bean instance return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); // Get bean instance bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // The scope of bean is prototype else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // Callback before prototype bean creation, // The default implementation is to save the bean name to the prototype currentlycreation cache beforePrototypeCreation(beanName); // Create bean instance prototypeInstance = createBean(beanName, mbd, args); } finally { // Callback after prototype bean creation, // The default implementation is to remove the bean name from the prototype current creation cache afterPrototypeCreation(beanName); } // Get bean instance bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // Custom scope else { // Get custom scope name String scopeName = mbd.getScope(); // Get scope object final Scope scope = this.scopes.get(scopeName); // If it is empty, the scope is not registered, and an exception is thrown if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // bean creation for other scopes // Create a new ObjectFactory and override the getObject method Object scopedInstance = scope.get(beanName, () -> { // Call the pre creation callback of the prototype bean beforePrototypeCreation(beanName); try { // Create a bean instance, which will be explained in the next article return createBean(beanName, mbd, args); } finally { // Call the callback after prototype bean creation afterPrototypeCreation(beanName); } }); // Get bean instance bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton",ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if the required type matches the type of the actual bean instance if (requiredType != null && !requiredType.isInstance(bean)) { try { // If the types are not equal, the conversion fails and an exception is thrown. If the conversion succeeds, the system returns directly T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } // Return bean instance return (T) bean; }
The above method is the whole process of getting bean s. Next, we will analyze the other main methods we call.
Convert the corresponding bean name
AbstractBeanFactory#transformedBeanName
protected String transformedBeanName(String name) { return canonicalName(BeanFactoryUtils.transformedBeanName(name)); } // BeanFactoryUtils.java public static String transformedBeanName(String name) { Assert.notNull(name, "'name' must not be null"); // If name doesn't start with & return directly if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { return name; } // Remove & prefix from name return transformedBeanNameCache.computeIfAbsent(name, beanName -> { do { beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); } while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); return beanName; }); } // SimpleAliasRegistry.java public String canonicalName(String name) { String canonicalName = name; String resolvedName; // If name is an alias, it will loop to find the actual name of the bean do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }
Try to get the bean from the singleton cache
AbstractBeanFactory#getSingleton
public Object getSingleton(String beanName) { // allowEarlyReference is set to true to allow early dependencies return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // First, check whether the single instance cache exists from the first level cache Object singletonObject = this.singletonObjects.get(beanName); // If it is empty and the current bean is being created, lock the global variable for processing if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // Get from L2 cache singletonObject = this.earlySingletonObjects.get(beanName); // Second level cache is empty & & Beans allow early exposure if (singletonObject == null && allowEarlyReference) { // Get the ObjectFactory corresponding to the bean from the L3 cache ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // Call the preset getObject() to get the bean instance singletonObject = singletonFactory.getObject(); // Put it into the second level cache and remove it from the third level cache // At this time, the bean has been instantiated but not initialized // When the bean is not initialized, if another bean references the bean, you can take it out of the secondary cache and return it this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
The above method is mainly to try to get bean s from the cache. There are three levels of cache, which is the key to Spring's solution of circular dependency. Later, we will focus on circular dependency.
Get bean instance object
AbstractBeanFactory#getObjectForBeanInstance
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { // name starts with & or not if (BeanFactoryUtils.isFactoryDereference(name)) { // Return directly if null if (beanInstance instanceof NullBean) { return beanInstance; } // beanName starts with & but is not of FactoryBean type, throwing an exception if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); } // Set isFactoryBean to true if (mbd != null) { mbd.isFactoryBean = true; } // Return bean instance return beanInstance; } // name does not start with & and is not of FactoryBean type, directly return if (!(beanInstance instanceof FactoryBean)) { return beanInstance; } // Here it means that name is not the beginning of & and is of FactoryBean type // I.e. get FactoryBean.getObject() the bean represented by the return value of the method Object object = null; if (mbd != null) { mbd.isFactoryBean = true; } else { // Get instance from cache object = getCachedObjectForFactoryBean(beanName); } if (object == null) { // Convert beanInstance to FactoryBean FactoryBean<?> factory = (FactoryBean<?>) beanInstance; // Merge BeanDefinition if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); // Get instance object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; } // FactoryBeanRegistrySupport.java protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { // If it is a singleton bean, and it already exists in the cache if (factory.isSingleton() && containsSingleton(beanName)) { // Lock up synchronized (getSingletonMutex()) { // Get from cache Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { // Call getObject() of FactoryBean to get the instance object = doGetObjectFromFactoryBean(factory, beanName); Object alreadyThere = this.factoryBeanObjectCache.get(beanName); // If the beanName already exists in the cache, replace the object with the if (alreadyThere != null) { object = alreadyThere; } else { if (shouldPostProcess) { // If the current bean is still being created, directly return if (isSingletonCurrentlyInCreation(beanName)) { return object; } // Callback before creation of singleton bean beforeSingletonCreation(beanName); try { // For the post-processing of obtaining the given object from FactoryBean, return as is by default object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", ex); } finally { // Callback after creation of singleton bean afterSingletonCreation(beanName); } } if (containsSingleton(beanName)) { // Place the beanName and object in the factoryBeanObjectCache this.factoryBeanObjectCache.put(beanName, object); } } } // Return to instance return object; } } else { // Call getObject() of FactoryBean to get the instance Object object = doGetObjectFromFactoryBean(factory, beanName); if (shouldPostProcess) { try { // For the post-processing of obtaining the given object from FactoryBean, return as is by default object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); } } // Return to instance return object; } } // FactoryBeanRegistrySupport.java private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException { Object object; try { // Call getObject() to get the instance object = factory.getObject(); } // Omit exception handling // Throw an exception if the object is null and the singleton bean is currently being created if (object == null) { if (isSingletonCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject"); } object = new NullBean(); } // Return object instance return object; }
The above code is summed up as follows: if the beanName starts with &, it will directly return to the FactoryBean instance; otherwise, call the getObject() method to get the instance, and then execute the postProcessObjectFromFactoryBean() callback. You can modify the instance in the callback method, and return as is by default.
Merge bean definition meta information
AbstractBeanFactory#getMergedLocalBeanDefinition
The merged BeanDefinition is hereinafter referred to as MergedBeanDefinition.
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { // Get MergedBeanDefinition from cache RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName); // If MergedBeanDefinition exists and is not expired, return it directly if (mbd != null && !mbd.stale) { return mbd; } // Get the registered BeanDefinition and merge it return getMergedBeanDefinition(beanName, getBeanDefinition(beanName)); } protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) throws BeanDefinitionStoreException { // Top level bean gets the merged BeanDefinition return getMergedBeanDefinition(beanName, bd, null); } /** * @param containingBd If it is a nested bean, the value is a top-level bean; if it is a top-level bean, the value is null */ protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { // Lock up synchronized (this.mergedBeanDefinitions) { // This rootbean definition RootBeanDefinition mbd = null; // Previous rootbean definition RootBeanDefinition previous = null; // If the bean is a top-level bean, get the MergedBeanDefinition directly if (containingBd == null) { mbd = this.mergedBeanDefinitions.get(beanName); } // No mergedbeandefinition | beandefinition expired if (mbd == null || mbd.stale) { previous = mbd; // If the bean does not have a parent if (bd.getParentName() == null) { // If bd itself is RootBeanDefinition, copy it directly, otherwise create a if (bd instanceof RootBeanDefinition) { mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); } else { mbd = new RootBeanDefinition(bd); } } else { // bean has parent BeanDefinition pbd; try { // Get the actual name of the parent bean String parentBeanName = transformedBeanName(bd.getParentName()); if (!beanName.equals(parentBeanName)) { // The current beanName is not equal to its parentBeanName // Get the MergedBeanDefinition of parent pbd = getMergedBeanDefinition(parentBeanName); } else { // If the parentBeanName is the same as the beanName of bd, the parent BeanFactory will be obtained // parentBeanName is allowed to be the same as itself only if a parent BeanFactory exists BeanFactory parent = getParentBeanFactory(); if (parent instanceof ConfigurableBeanFactory) { // If the parent BeanFactory is of ConfigurableBeanFactory type // Get the MergedBeanDefinition of the parent through the parent BeanFactory pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName); } else { // Throw an exception if the parent BeanFactory is not ConfigurableBeanFactory throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without an AbstractBeanFactory parent"); } } } catch (NoSuchBeanDefinitionException ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex); } // Using the parent MergedBeanDefinition to build a new RootBeanDefinition object (deep copy) mbd = new RootBeanDefinition(pbd); // Override the same properties as parent mbd.overrideFrom(bd); } // If the bean does not set the scope property, the default is singleton if (!StringUtils.hasLength(mbd.getScope())) { mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON); } // The current bean is a nested Bean & & the scope of the top-level bean is not a singleton & & the scope of the current bean is a singleton // In summary, if the top-level bean is not singleton, then the nested bean cannot be singleton if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { // Set the scope of the current bean to be the same as that of the top-level bean mbd.setScope(containingBd.getScope()); } // The current bean is a top-level Bean & & cache the metadata of the bean (this value defaults to true) if (containingBd == null && isCacheBeanMetadata()) { // Cache the MergedBeanDefinition of the current bean this.mergedBeanDefinitions.put(beanName, mbd); } } // The previous RootBeanDefinition is not empty, copy the related BeanDefinition cache if (previous != null) { copyRelevantMergedBeanDefinitionCaches(previous, mbd); } return mbd; } }
The above code is mainly to obtain MergedBeanDefinition. The main steps are as follows:
-
First, get the MergedBeanDefinition of the bean from the cache, and return it directly if it exists and does not expire.
-
No or expired MergedBeanDefinition, get the registered BeanDefinition to merge as the top-level bean.
-
bean has no parent (that is, the parent attribute in XML), which is directly encapsulated as RootBeanDefinition.
-
The bean has a parent. First get the parent MergedBeanDefinition, and then overwrite and merge the same properties as the parent.
Note: only abstract, scope, lazyInit, autowireMode, dependencyCheck, dependsOn, factoryBeanName, factoryMethodName, initMethodName, destroyMethodName will be overwritten, while constructorArgumentValues, propertyValues, methodOverrides will be merged.
-
If the scope is not set, the default scope is singleton.
-
Cache MergedBeanDefinition.
As mentioned above, if the bean has a parent, some properties will be merged. Here we will show the merged propertyValues:
First, define a SuperUser to inherit the User defined above, as follows:
public class SuperUser extends User { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "SuperUser{" + "address='" + address + '\'' + '}'; } }
Then we configure it in the XML file as follows:
<bean id="superUser" class="com.leisurexi.ioc.domain.SuperUser" parent="user"> <property name="address" value="Beijing"/> </bean>
Then the following figure is a screenshot of my Debug. You can see that the superUser's propertyValues combine the user's id and name properties.
Nested beans are also mentioned above. Let's take a brief look at what a nested bean is.
In spring, if a bean depends on a bean that does not want to be directly accessed by the spring container, nested beans can be used. Like ordinary beans, nested beans are defined by bean elements. Nested beans are only valid for their external beans. Spring cannot directly access nested beans. Therefore, it is unnecessary to specify id attribute to define nested beans. The following configuration fragment is an example of a nested bean:
Using the above configuration form can ensure that nested beans cannot be accessed by containers, so it is not necessary to worry about other programs modifying nested beans. There is no difference between the usage and result of the external bean.
Nested beans improve the cohesiveness of beans, but reduce the flexibility of programs. Consider using nested beans only if you are sure that you do not need to access a bean instance through the Spring container.
Looking for dependency
DefaultSingletonBeanRegistry#isDependent
protected boolean isDependent(String beanName, String dependentBeanName) { // Lock up synchronized (this.dependentBeanMap) { // Check whether beanName and dependentBeanName have circular dependency return isDependent(beanName, dependentBeanName, null); } } private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) { // If the current bean has been detected, return false directly if (alreadySeen != null && alreadySeen.contains(beanName)) { return false; } // Resolve alias, get actual beanName String canonicalName = canonicalName(beanName); // Get the beanName collection on which canonicalName depends Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName); // If it is empty, the dependency between the two has not been determined, return false if (dependentBeans == null) { return false; } // Returns true if the dependentBeanName already exists in the cache and the dependency has been determined if (dependentBeans.contains(dependentBeanName)) { return true; } // Circular check, i.e. check whether all beannames that depend on canonicalName are dependent by dependentBeanName (i.e. layer dependency) for (String transitiveDependency : dependentBeans) { if (alreadySeen == null) { alreadySeen = new HashSet<>(); } // Record the checked ones and skip next time alreadySeen.add(beanName); if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) { return true; } } return false; }
DefaultSingletonBeanRegistry#registerDependentBean
public void registerDependentBean(String beanName, String dependentBeanName) { // Resolve alias, get actual beanName String canonicalName = canonicalName(beanName); // Lock up synchronized (this.dependentBeanMap) { // Get canonicalName depends on the beanName collection. If it is empty, create a LinkedHashSet as the default value Set<String> dependentBeans = this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8)); // If the dependentBeanName has been recorded, return directly if (!dependentBeans.add(dependentBeanName)) { return; } } // Lock up synchronized (this.dependenciesForBeanMap) { // Here is the reverse of the above dependentBeanMap. The key is dependentBeanName Set<String> dependenciesForBean = this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8)); dependenciesForBean.add(canonicalName); } }
Let's take A case where the dependencies on attributes of A and B are each other
First get A, call isDependent() method, because the first time get A, there is no record dependency in the dependentBeanMap, and return false directly; then call registerDependentBean(), which will store the dependency in the dependentBeanMap in turn, that is, take B as the key, and value is A Set set containing A.
Then the getBean() method will be called to get B, and the isDependent() method will be called first. Because the dependency of B has been stored when getting A, the collection of the obtained dependentBeans contains A, so it directly returns true and throws A circular reference exception.
This method also introduces a cache dependenciesForBeanMap similar to dependentBeanMap. These two caches are easy to confuse. Here is another simple example: a depends on B, so the dependentBeanMap stores the Set with key B and value a, while the dependenciesForBeanMap stores the Set with key A and value B.
Create and register singleton bean s
DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); // Lock up synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); // The current bean does not exist in the cache, which is the first creation of the current bean if (singletonObject == null) { // Throw an exception if singletons is currently being destroyed if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } // Callback before creating a singleton bean beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { // Get the bean instance, where you can actually call the method to create the bean singletonObject = singletonFactory.getObject(); newSingleton = true; } // Omit exception handling finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } // Callback after creating a singleton bean afterSingletonCreation(beanName); } if (newSingleton) { // Put singletonObject into cache addSingleton(beanName, singletonObject); } } // Return bean instance return singletonObject; } } // The default implementation of the callback method before the creation of a single bean is to add beanName to the cache of the bean currently being created, // In this way, circular dependency can be detected protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } // The default implementation of the callback method after the creation of a single bean is to remove the bean name from the cache where the bean is currently being created protected void afterSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } } protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // The bean here has been initialized and put into the first level cache this.singletonObjects.put(beanName, singletonObject); // Remove L3 cache this.singletonFactories.remove(beanName); // Remove L2 cache this.earlySingletonObjects.remove(beanName); // Add beanName to the registered bean cache this.registeredSingletons.add(beanName); } }
Custom scope example
We implement a ThreadLocal level Scope, that is, the beans in the same thread are the same instance, and the beans in different threads are different instances. First, we inherit the Scope interface implementation, including methods. As follows:
public class ThreadLocalScope implements Scope { /** scope Name, which is configured by the scope attribute in XML */ public static final String SCOPE_NAME = "thread-local"; private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal<>("thread-local-scope"); /** * Returns the instance object, which is called by Spring */ @Override public Object get(String name, ObjectFactory<?> objectFactory) { Map<String, Object> context = getContext(); Object object = context.get(name); if (object == null) { object = objectFactory.getObject(); context.put(name, object); } return object; } /** * Get context map */ @NonNull private Map<String, Object> getContext() { Map<String, Object> map = threadLocal.get(); if (map == null) { map = new HashMap<>(); threadLocal.set(map); } return map; } @Override public Object remove(String name) { return getContext().remove(name); } @Override public void registerDestructionCallback(String name, Runnable callback) { // TODO } @Override public Object resolveContextualObject(String key) { Map<String, Object> context = getContext(); return context.get(key); } @Override public String getConversationId() { return String.valueOf(Thread.currentThread().getId()); } }
The above ThreadLocalScope focuses on get(), which is called by Spring.
Then configure the scope of the bean in XML as thread local. As follows:
<bean id="user" name="user" class="com.leisurexi.ioc.domain.User" scope="thread-local"> <property name="id" value="1"/> <property name="name" value="leisurexi"/> </bean>
Then let's test it. Test class:
@Test public void test() throws InterruptedException { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // Register custom scopes beanFactory.registerScope(ThreadLocalScope.SCOPE_NAME, new ThreadLocalScope()); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions("META-INF/custom-bean-scope.xml"); for (int i = 0; i < 3; i++) { Thread thread = new Thread(() -> { User user = beanFactory.getBean("user", User.class); System.err.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user.getClass().getName() + "@" + Integer.toHexString(user.hashCode())); User user1 = beanFactory.getBean("user", User.class); System.err.printf("[Thread id :%d] user1 = %s%n", Thread.currentThread().getId(), user1.getClass().getName() + "@" + Integer.toHexString(user1.hashCode())); }); thread.start(); thread.join(); } }
Let's talk about our main idea here. We have created three new threads to query whether the user bean s in the threads are equal or not, and whether different threads are different.
The results are as follows:
summary
This article mainly introduces the process of getBean() method. We can rearrange the following ideas:
- Get the actual name of the bean. If it exists in the cache, get the actual bean directly and return it.
- The cache does not exist. Judge whether the current factory has a BeanDefinition. Do not recursively go to the parent factory to create beans.
- Merge bean definition. If the dependencies on is not empty, initialize the dependent bean first.
- If the scope of a bean is a singleton, call the createBean() method to create an instance. This method will perform other life cycle callbacks of the bean, as well as property assignment and other operations. Then, execute the life cycle callbacks before and after the creation of a singleton bean, and put them into singleton objects for storage.
- If the scope of bean is a prototype, the createBean() method is called to create an instance and the lifecycle callback method is invoked before and after the prototype bean is executed.
- If the scope of bean is customized, get the corresponding Scope object, call the rewritten get() method to get the instance, and invoke the lifecycle callback method before and after executing the prototype bean.
- Finally, check whether the required type matches the type of the actual bean instance. If the conversion does not wait, return the instance.
The details of the createBean() method will be analyzed in a later article.
Finally, I wrote a simplified version of Spring, and the code will keep updating. Address: https://github.com/leisurexi/tiny-spring.
reference resources
- Deep analysis of Spring source code -- Hao Jia
- https://github.com/geektime-geekbang/geekbang-lessons