Loading of spring IOC beans

Posted by nels on Sat, 27 Jun 2020 07:22:57 +0200

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:

  1. First, get the MergedBeanDefinition of the bean from the cache, and return it directly if it exists and does not expire.

  2. No or expired MergedBeanDefinition, get the registered BeanDefinition to merge as the top-level bean.

  3. bean has no parent (that is, the parent attribute in XML), which is directly encapsulated as RootBeanDefinition.

  4. 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.

  5. If the scope is not set, the default scope is singleton.

  6. 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:

  1. Get the actual name of the bean. If it exists in the cache, get the actual bean directly and return it.
  2. The cache does not exist. Judge whether the current factory has a BeanDefinition. Do not recursively go to the parent factory to create beans.
  3. Merge bean definition. If the dependencies on is not empty, initialize the dependent bean first.
  4. 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.
  5. 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.
  6. 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.
  7. 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

Topics: Spring xml Attribute Java