Handwritten spring Chapter 3 - refactoring, using dependencies to improve instantiation bean operations

Posted by SP8600 on Sun, 05 Sep 2021 04:27:33 +0200

preface

In the previous article, we wrote a spring short version framework based on the single responsibility principle and template method pattern, but in the process of use, we found that if the bean to be loaded into the container has parameters, there will be problems when creating the bean. In this chapter, we need to adjust the framework accordingly.

objective

Through this code modification, the container can access not only nonparametric beans, but also parameterized beans.

Design ideas

Because of this requirement, the container needs to be greatly adjusted, the author will conduct top-down analysis during refactoring. The corresponding class diagram is as follows:

BeanFactory transformation point analysis

By looking at the previous code, we found that the getBean method of BeanFactory has only one name. Assuming that my project has been online and this function is used well without parameters, we should not change this function, but add a new getBean method, but the parameters are (String name,Object... agrs), This also reflects the opening and closing principle from another angle (closed for modification and open for expansion).

AbstractBeanFactory transformation point analysis

After completing the top-level thinking analysis, we begin to look at the abstract class AbstractBeanFactory that regulates container behavior. In this reconstruction, we added a new getBean method when inheriting the BeanFactory interface. The code after writing is as follows:

public Object getBean(String name,Object... args) throws BeansException {
		Object bean = getSingleton(name);
        if (bean != null) {
            return (T) bean;
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        return (T) createBean(name, beanDefinition, args);
       }

At this time, we find a problem. Isn't this code similar to the code of * * getBean(String name) * *?

public Object getBean(String name) throws BeansException {
        Object singleton = getSingleton(name);
        if (singleton!=null){
            return singleton;
        }
        BeanDefinition beanDefinition = getBeanDefinition(name);
        return createBean(name,beanDefinition);
    }

In order to simplify the code, in this case, we should analyze the commonalities of the two getbeans, and then extract a passage of passing code. Through analysis, we can see that the difference between the two getbeans is only whether the constructor parameters need to be passed when creating an object. Therefore, we simply write a doGetBean method with the function parameters * * (String name, Object... args). In this way, for the parameterless getBean method, args can pass null.
After such an operation, the previous abstract method createBean (string beanname, beandefinition, beandefinition) is obviously not applicable (since we may take out beans with parameters, I should be able to create beans with parameters), so the new createBean should be createBean (string beanname, beandefinition, beandefinition, object [] args)**

Instantiation tool design

Considering that there are many methods to instantiate beans, such as jdk and cglib, when writing instantiation bean tools, we should consider the long-term development and ensure that the code can expand new tools at any time. Therefore, we use one interface to limit their work, At the same time, we can also well follow the dependency inversion principle. High-level modules should not rely on detail modules, but on their abstraction. Therefore, create an interface InstantiationStrategy, so that when other high-level modules need to use this tool, they only need to use it as private member variables, In this way, no matter how the subsequent implementation code of the strategy is modified, it will not affect other logic of the high-level module, and ensure that each performs its own duties. The code is as follows:

public interface InstantiationStrategy {

    Object instantiate(String beanName, BeanDefinition beanDefinition, Constructor ctor,Object[] args) throws BeansException;
}

After that, we are writing two classes of instantiated bean s, one is the jdk based SimpleInstantiationStrategy, and the other is the cglib based cglibsubclassing instantiationstrategy

AbstractAutowireCapableBeanFactory transformation point analysis

Our previous createBean method code is as follows:

@Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {
        Object bean = null;
        try {
            bean = beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

Considering that there may be parameters or no parameters when creating beans, we need to design the code for generality during refactoring, because we have handled it in AbstractBeanFactory above. The code is as follows:

 public Object getBean(String name) throws BeansException {
       return doGetBean(name,null);
    }


    public Object getBean(String name,Object... args) throws BeansException {
        return doGetBean(name,args);
    }

For this article, we only need to judge whether the constructor has parameters. If it has parameters, we can pass the constructor and parameters to the instantiation strategy. If there are no parameters, we can directly pass the empty constructor. The code is as follows. Careful readers can see that for the work of creating beans, the author creates a function createbean instance. Why? Code simplicity once said:

The code in the same function should belong to the same level

Since the previous code already has an addSingleton method, in order to keep the code hierarchy consistent, the specific details of creating a bean are also put into a function, createbean instance.

private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            bean = createBeanInstance(beanName, beanDefinition, args);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }


        addSingleton(beanName, bean);
        return bean;
    }


    protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object[] args) {
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructor = beanClass.getDeclaredConstructors();
        for (Constructor<?> ctor : declaredConstructor) {
            if (args != null && ctor.getParameterTypes().length == args.length) {
                constructorToUse = ctor;
                break;
            }
        }

        return getInstantiationStrategy().instantiate(beanName, beanDefinition, constructorToUse, args);
    }


    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

Class diagram

Code structure

code

BeanFactory

package cn.shark.springframework.beans.factory;

import cn.shark.springframework.beans.BeansException;

/**
 * Set the bean factory as the interface to facilitate various subsequent extensions
 */
public interface BeanFactory {

    Object getBean(String name) throws BeansException;


    Object getBean(String name,Object... agrs) throws BeansException;



}

AbstractBeanFactory

package cn.shark.springframework.beans.factory.support;


import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.BeanFactory;
import cn.shark.springframework.beans.factory.config.BeanDefinition;


public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {


    public Object getBean(String name) throws BeansException {
       return doGetBean(name,null);
    }


    public Object getBean(String name,Object... args) throws BeansException {
        return doGetBean(name,args);
    }

    /**
     * Using the template method pattern ensures that the later bean container only needs to focus on its own responsibilities and unify the bean container specification
     *
     * @param name
     * @return
     * @throws BeansException
     */
    protected <T> T doGetBean(String name, Object... args) {
        Object bean = getSingleton(name);
        if (bean != null) {
            return (T) bean;
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        return (T) createBean(name, beanDefinition, args);
    }

    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;


    /**
     * Create the bean and put it in the singleton container
     *
     * @param beanName
     * @param beanDefinition
     * @return
     * @throws BeansException
     */
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException;


}

InstantiationStrategy

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;

public interface InstantiationStrategy {

    Object instantiate(String beanName, BeanDefinition beanDefinition, Constructor ctor,Object[] args) throws BeansException;
}

SimpleInstantiationStrategy

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SimpleInstantiationStrategy implements InstantiationStrategy {
    @Override
    public Object instantiate(String beanName, BeanDefinition beanDefinition, Constructor ctor, Object[] args) throws BeansException {
        Class clazz = beanDefinition.getBeanClass();
        try {
            if (ctor != null) {

                return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);

            } else {
                return clazz.getDeclaredConstructor().newInstance();
            }
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
        }
    }
}

CglibSubclassingInstantiationStrategy

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.config.BeanDefinition;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

import java.lang.reflect.Constructor;

public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {
    @Override
    public Object instantiate(String beanName, BeanDefinition beanDefinition, Constructor ctor, Object[] args) throws BeansException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        /**
         * In cglib, Callback is a tag interface, and the Callback used by Enhancer is a sub interface of the Callback interface in cglib
         */
        enhancer.setCallback(new NoOp() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        });
        if (ctor == null) {
            return enhancer.create();
        }

        return enhancer.create(ctor.getParameterTypes(), args);
    }
}

AbstractAutowireCapableBeanFactory

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            bean = createBeanInstance(beanName, beanDefinition, args);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }


        addSingleton(beanName, bean);
        return bean;
    }


    protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object[] args) {
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructor = beanClass.getDeclaredConstructors();
        for (Constructor<?> ctor : declaredConstructor) {
            if (args != null && ctor.getParameterTypes().length == args.length) {
                constructorToUse = ctor;
                break;
            }
        }

        return getInstantiationStrategy().instantiate(beanName, beanDefinition, constructorToUse, args);
    }


    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }
}

test case

package cn.shark.springframework;

import cn.shark.springframework.bean.UserService;
import cn.shark.springframework.beans.factory.config.BeanDefinition;
import cn.shark.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.junit.Test;

public class ApiTest {

    @Test
    public void test(){
        DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();

        BeanDefinition beanDefinition=new BeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService",beanDefinition);

        UserService userService= (UserService) beanFactory.getBean("userService","Xiao Ming");
        userService.queryUserInfo();
    }
}


summary

Through this reconstruction, we can see that it is precisely because of the good structural design and strict compliance with the software design principles that the reconstructed code has not changed greatly. In this reconstruction, we have completely not moved to the beandefinition registration logic, that is, the relevant interfaces and inheritance classes of BeanDefinitionRegistry, It is precisely because of the single responsibility architecture design that DefaultListableBeanFactory only focuses on its own personalized customization or beandefinition and registration of beandefinition, and the creation of beans is completed by its inherited abstract class AbstractAutowireCapableBeanFactory. Therefore, in this reconstruction, DefaultListableBeanFactory does not need any changes. This is the importance of software design principles.

Reference articles

Seven principles of software architecture design

Opening and closing principle - close for modification and open for expansion

Handwritten spring Chapter 2 - writing extensible containers using design patterns

Clean Code

Chapter 4 of Spring column: emerging, class instantiation strategy with constructor based on Cglib

Topics: Java Spring source code mvc