I believe many friends are already familiar with Spring, and they are often asked about relevant knowledge of Spring in interviews, such as IOC, DI, AOP, etc. The following is a way to get familiar with and understand the relevant content of IOC by writing IOC


The IOC analysis

What is the IOC

Inversion of Control

What to make of this inversion of control?

Inversion: the retrieval of dependent objects has been reversed, from self-creation to retrieval (and auto-injection) from the IOC container; In other words, you don’t come to me, I come to you. The traditional way is that I control other objects inside the object. With IOC, IOC is a special container that creates and manages these objects

For example, we usually find a girlfriend or boyfriend, will try every means to inquire about their contact information ah, ah hobby and so on, these things are need us to do. IOC is like dating agencies, chat groups and so on. Then we can present our requirements to them, such as height, weight, appearance and so on. These intermediaries will provide a partner according to our requirements, and then we can fall in love with this person

What benefits can IOC bring

As you can see from the above brief description, IOC has the following benefits:

  1. The code is cleaner, you don’t need to new the objects you use, and you can decouple them
  2. Interface oriented programming decouples the user from the concrete, making it easy to extend and replace the implementers
  3. AOP enhancements can be easily implemented

What does an IOC container do

The IOC’s main job is to create and manage instances of these classes, which can then be made available to consumers

Whether the IOC container is an instance of the factory pattern

Yes, IOC is responsible for creating instance objects of the class, getting them if needed from the IOC container, which can also be called Bean factories, producing Bean instances


IOC Design and Implementation

What is needed to design an IOC

Given that the IOC container is a Bean factory, do you need an interface to the Bean factory that creates and retrieves these beans?

How do you know what the user-supplied bean looks like? Do you also need an interface to define these beans?

The Bean factory and the Bean definition interface are already available, so how does the Bean factory know how to create the Bean? Is it necessary to tell the Bean factory the information about the Bean definition

To sum up, designing IOC requires the following three elements:

1. Bean factory interface

2. Beans define interfaces

3. Registration interface defined by Bean

Defines the interface

One: Bean factory interface

It is mainly used to create and get Bean instances

/ * * *@ClassName BeanFactory
 * @Description: Bean factory interface, responsible for creating and getting beans *@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public interface BeanFactory {

    /** Get the bean */
    Object getBean(String beanName) throws Exception;
}
Copy the code

The registration interface defined by the Bean

What methods are required in the registration interface defined by the Bean?

It is necessary to be able to register and retrieve Bean definition information, so the registered Bean definition information also needs to distinguish it, it is not necessary to give each Bean definition, let it have a unique name on the line

/ * * *@ClassName BeanDefinitionRegistry
 * @Description: The registered interface for the Bean definition that acts as a bridge between the Bean definition and the Bean factory@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public interface BeanDefinitionRegistry {

    /** Register Bean definition information, beanName is used to distinguish registered Bean definition */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                        throws BeanDefinitionRegisterException;

    /** Get Bean information */
    BeanDefinition getBeanDefinition(String beanName);

    /** Whether the Bean definition is already registered */
    boolean containsBeanDefinition(String beanName);
}
Copy the code

Custom exception classes:

/ * * *@ClassName BeanDefinitionRegisterException
 * @Description: Custom exception class *@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public class BeanDefinitionRegisterException extends Exception {

    public BeanDefinitionRegisterException(String message) {
        super(message);
    }

    public BeanDefinitionRegisterException(String message, Throwable cause) {
        super(message, cause); }}Copy the code

The Bean defines the interface

What is the Bean definition for? Is telling the Bean factory how to create a Bean of a particular class

There are several ways to get an instance of a class:

  1. New constructor
User user = new User()
Copy the code
  1. Factory method: Static factory
public class UserFactory {
   public static User getUser(a) {
      return newUser(); }}Copy the code
  1. Factory method: member method
public class UserFactory {
   public User getUser(a) {
      return newUser(); }}Copy the code

What information do Bean factories need to know to help us create beans?

  1. With the new constructor, you need to know the class name
  2. With static factory methods, you need to know the factory class name, factory method name
  3. With the member factory method, you need to know the factory bean name, factory method name

Do you need to create a new Bean every time you get an instance of a Bean from the Bean factory? Definitely not. Some of them just need singletons

The Bean definition information is needed to tell the Bean factory how to create the Bean, so the Bean definition needs to provide the Bean factory with some methods:

  1. Get the Bean’s Class name: getBeanClass():Class
  2. GetFactoryMethodName: getFactoryMethodName():String
  3. GetFactoryBeanName: getFactoryBeanName():String
  4. GetScope ():String, isSingleton(), isPrototype()

Is it enough to provide the above methods? What else is there in the life cycle of a class object?

  1. Does it require some initialization after object creation: getInitMethodName():String
  2. For example, some objects also need to do some special destruction logic (such as releasing resources) when they are destroyed: getDestroyMethodName():String

Provide the above initialization and destruction methods for the user to use, and in the case of the Bean factory, get those initialization and destruction methods

/ * * *@ClassName Beandefinition
 * @Description: Bean defines interface *@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public interface BeanDefinition {

    Singleton / * * * /
    String SCOPE_SINGLETON = "singleton";

    /** 多例 */
    String SCOPE_PROTOTYPE = "prototype";

    /** Get Bean */ via constructorClass<? > getBeanClass();/** Set beanClass */
    void setBeanClass(Class
        beanClass);

    /** Get the Bean */ from the static factory
    String getFactoryMethodName(a);

    /** Sets the factory method name */
    void setFactoryMethodName(String factoryMethodName);

    /** Get the Bean */ from the member factory
    String getFactoryBeanName(a);

    /** Sets the factory Bean name */
    void setFactoryBeanName(String factoryBeanName);

    /** Get the range */
    String getScope(a);

    /** Set range */
    void setScope(String scope);

    /** is the singleton */
    boolean isSingleton(a);

    /** */
    boolean isPrototype(a);

    /** Get the initialization method */
    String getInitMethodName(a);

    /** Sets the initialization method */
    void setInitMethodName(String initMethodName);

    /** Get the destruction method */
    String getDestroyMethodName(a);

    /** Sets the destruction method */
    void setDestroyMethodName(String destroyMethodName);

    /** * validates that the Bean definition can be registered */ while being registered
    default boolean validate(a) {
        // No BeanClass is defined, or no factory method or factory bean is specified.
        // This is playing me, having nothing is like asking for a date
        if (getBeanClass() == null) {
            if (StringUtils.isBlank(this.getFactoryMethodName())
                    || StringUtils.isBlank(this.getFactoryBeanName())) {
                return false; }}// The factory bean defined by the class is invalid
        if(getBeanClass() ! =null && StringUtils.isNoneBlank(this.getFactoryBeanName())) {
            return false;
        }
        return true;
    };
}
Copy the code

Implementing an interface

Now that we have interfaces, is it time to implement them, to do something interesting?

In the first place? GenericBeanDefinition class to implement a GenericBeanDefinition

GenericBeanDefinition implementation of Bean definition

The implementation class for the Bean definition, which is relatively simple, does nothing more than get and set the Bean definition information

/ * * *@ClassName GenericBeanDefinition
 * @Description: Implementation class * for the Bean definition@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public class GenericBeanDefinition implements BeanDefinition {

    privateClass<? > beanClass;private String factoryMethodName;

    private String factoryBeanName;

    private String initMethodName;

    private String destroyMethodName;

    private String scope = BeanDefinition.SCOPE_SINGLETON;

    @Override
    publicClass<? > getBeanClass() {return beanClass;
    }

    @Override
    public void setBeanClass(Class
        beanClass) {
        this.beanClass = beanClass;
    }

    @Override
    public String getFactoryMethodName(a) {
        return factoryMethodName;
    }

    @Override
    public void setFactoryMethodName(String factoryMethodName) {
        this.factoryMethodName = factoryMethodName;
    }

    @Override
    public String getFactoryBeanName(a) {
        return factoryBeanName;
    }

    @Override
    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    @Override
    public String getScope(a) {
        return scope;
    }

    @Override
    public void setScope(String scope) {
        this.scope = scope;
    }

    @Override
    public boolean isSingleton(a) {
        return scope.equals(BeanDefinition.SCOPE_SINGLETON);
    }

    @Override
    public boolean isPrototype(a) {
        return scope.equals(BeanDefinition.SCOPE_PROTOTYPE);
    }

    @Override
    public String getInitMethodName(a) {
        return initMethodName;
    }

    @Override
    public void setInitMethodName(String initMethodName) {
        this.initMethodName = initMethodName;
    }

    @Override
    public String getDestroyMethodName(a) {
        return destroyMethodName;
    }

    @Override
    public void setDestroyMethodName(String destroyMethodName) {
        this.destroyMethodName = destroyMethodName;
    }

    @Override
    public String toString(a) {
        return "GenericBeanDefinition{" +
                "beanClass=" + beanClass +
                ", factoryMethodName='" + factoryMethodName + '\' ' +
                ", factoryBeanName='" + factoryBeanName + '\' ' +
                ", initMethodName='" + initMethodName + '\' ' +
                ", destroyMethodName='" + destroyMethodName + '\' ' +
                ", scope='" + scope + '\' ' +
                '} '; }}Copy the code

Two: Bean factory implementation DefaultBeanFactory

The next step is to implement the Bean factory and get it up and running

First, consider whether the bean definition information needs to be stored. Define a Map to cache the bean definition information

    /** Bean defines cache */
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
Copy the code

Created beans also need to be stored for future retrieval

    /** Bean cache */
    private Map<String, Object> beanMap = new ConcurrentHashMap<>();
Copy the code

There are a few things you need to do in getBean to create the Bean instance, which can then be initialized

   public class DefaultBeanFactory implements BeanFactory.BeanDefinitionRegistry {
        @Override
        public Object getBean(String beanName) throws Exception {
            returndoGetBean(beanName); }}Copy the code

Next implement the doGetBean method:

From the above statement, you can see that there are three ways to create a Bean instance: through a constructor, through a static factory, and through a member factory method. The code looks like this:

 private Object doGetBean(String beanName) throws Exception {

        // Check whether the beanName object has been created
        Object bean = beanMap.get(beanName);
        if(bean ! =null) {
            return bean;
        }

        BeanDefinition bd = beanDefinitionMap.get(beanName);
        Objects.requireNonNull(bd, "Can't recruit ["+beanName+"> < span style =" max-width: 100%; clear: both;); Class<? > beanClass = bd.getBeanClass();if(beanClass ! =null) {
            // Build objects using constructors
            if (StringUtils.isBlank(bd.getFactoryMethodName())) {
                bean = createBeanByConstructor(bd);
            } else { // Build objects from static factoriesbean = createBeanByStaticFactory(bd); }}else { // Build objects from member factories
            bean = createBeanByFactoryBean(bd);
        }

        // Start the bean's life cycle
        if (StringUtils.isNotBlank(bd.getInitMethodName())) {
            doInitMethod(bean, bd);
        }

        // Processing of singleton beans
        if (bd.isSingleton()) {
            beanMap.put(beanName, bean);
        }

        return bean;
    }
Copy the code

Code logic: first go to beanMap to fetch Bean, if already exists directly return; Then, the bean definition information is obtained according to beanName, followed by a non-null judgment according to beanName if the bean definition can not be obtained; If beanClass is not null and the factory method name is null, then we know that the Bean is created based on the constructor; If the factory method is not empty, the Bean is created from the static factory; If the beanClass is empty, you can conclude that the Bean was created according to the member method

  1. Create the Bean through a constructor

You must first get the class name, then instantiate the Bean according to newInstance, and finally return it

    /** Build the object with the constructor */
    private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
        // Get the class nameClass<? > type = bd.getBeanClass();// Instantiate the bean
        Object bean = type.newInstance();
        return bean;
    }
Copy the code
  1. Create beans through static factories

Static factories create beans based on classes. Method name to create, first is also to get the class name, then is to get the factory method name, according to getMethod to get the method, and then call the method to instantiate

    /** Build objects from static factories */
    private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
        // Get the factory class nameClass<? > type = bd.getBeanClass();// Get the factory method name
        String factoryMethodName = bd.getFactoryMethodName();
        Method method = type.getMethod(factoryMethodName, null);
        Object object = method.invoke(type, null);
        return object;
    }
Copy the code
  1. Create beans through member factories

Member factory to create Bean, first get factory Bean, then get factory method, finally getMethod according to getMethod, then call method to instantiate

    /** Build objects from member factories */
    private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
        // Get the factory bean name
        String factoryBeanName = bd.getFactoryBeanName();
        // Get the factory bean
        Object factoryBean = getBean(factoryBeanName);
        // Get the factory method
        String factoryMethodName = bd.getFactoryMethodName();
        Method method = factoryBean.getClass().getMethod(factoryMethodName, null);
        Object object = method.invoke(factoryBean, null);
        return object;
    }
Copy the code

Here is the implementation of the Bean registration interface:

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {
        Objects.requireNonNull(beanName, "BeanName needs to be specified to register beans");
        Objects.requireNonNull(beanDefinition, "BeanDefinition needs to be specified to register beans");

        if(! beanDefinition.validate()) {throw new BeanDefinitionRegisterException("The name is ["+beanName+"】 beanName is not legal," + beanDefinition);
        }
        if (containsBeanDefinition(beanName)) {
            throw new BeanDefinitionRegisterException("The name is ["+beanName+"] beanName has been registered," + beanName);
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }
Copy the code

Code logic: BeanName is used to distinguish Bean definition information, so a non-null judgment is added. Bean definition information should also be judged whether it is empty. Then, according to the verification method in the Bean definition interface, the Bean definition information is judged whether it is legal. If the Container BeanDefinition method is used to determine if the Bean has been registered, put the beanDefinitionMap for the registered Bean definition

The destruction logic is implemented by implementing Closeable:

    @Override
    public void close(a) throws IOException {
        // Execute the destruction method for the singleton Bean
        for(Map.Entry<String, BeanDefinition> e : beanDefinitionMap.entrySet()) {
            / / get BeanName
            String beanName = e.getKey();
            // Get the Bean definition
            BeanDefinition definition = e.getValue();

            // If it is a singleton Bean and the destruction method is not empty, then the destruction method is executed
            if(definition.isSingleton() && StringUtils.isNotBlank(definition.getDestroyMethodName())) {
                / / get a Bean
                Object instance = beanMap.get(beanName);
                if(instance == null) {continue; } Method m =null;
                try {
                    m = instance.getClass().getMethod(definition.getDestroyMethodName(), null);
                    m.invoke(instance, null);
                } catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); }}}}Copy the code

The whole code implementation:

/ * * *@ClassName DeafultBeanFactory
 * @Description: Bean factory implementation class *@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public class DefaultBeanFactory implements BeanFactory.BeanDefinitionRegistry.Closeable {

    /** Bean defines cache */
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    /** Bean cache */
    private Map<String, Object> beanMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String beanName) throws Exception {
        return doGetBean(beanName);
    }

    private Object doGetBean(String beanName) throws Exception {

        // Check whether the beanName object has been created
        Object bean = beanMap.get(beanName);
        if(bean ! =null) {
            return bean;
        }

        BeanDefinition bd = beanDefinitionMap.get(beanName);
        Objects.requireNonNull(bd, "Can't recruit ["+beanName+"> < span style =" max-width: 100%; clear: both;); Class<? > beanClass = bd.getBeanClass();if(beanClass ! =null) {
            // Build objects using constructors
            if (StringUtils.isBlank(bd.getFactoryMethodName())) {
                bean = createBeanByConstructor(bd);
            } else { // Build objects from static factoriesbean = createBeanByStaticFactory(bd); }}else { // Build objects from member factories
            bean = createBeanByFactoryBean(bd);
        }

        // Start the bean's life cycle
        if (StringUtils.isNotBlank(bd.getInitMethodName())) {
            doInitMethod(bean, bd);
        }

        // Processing of singleton beans
        if (bd.isSingleton()) {
            beanMap.put(beanName, bean);
        }

        return bean;
    }

    /** bean */
    private void doInitMethod(Object bean, BeanDefinition bd) throws Exception {
        Method method = bean.getClass().getMethod(bd.getInitMethodName(), null);
        method.invoke(bean, null);
    }

    /** Build objects from member factories */
    private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
        // Get the factory bean name
        String factoryBeanName = bd.getFactoryBeanName();
        // Get the factory bean
        Object factoryBean = getBean(factoryBeanName);
        // Get the factory method
        String factoryMethodName = bd.getFactoryMethodName();
        Method method = factoryBean.getClass().getMethod(factoryMethodName, null);
        Object object = method.invoke(factoryBean, null);
        return object;
    }

    /** Build objects from static factories */
    private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
        // Get the factory class nameClass<? > type = bd.getBeanClass();// Get the factory method name
        String factoryMethodName = bd.getFactoryMethodName();
        Method method = type.getMethod(factoryMethodName, null);
        Object object = method.invoke(type, null);
        return object;
    }

    /** Build the object */ from the constructor
    private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
        // Get the class nameClass<? > type = bd.getBeanClass();// Instantiate the bean
        Object bean = type.newInstance();
        return bean;
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {
        Objects.requireNonNull(beanName, "BeanName needs to be specified to register beans");
        Objects.requireNonNull(beanDefinition, "BeanDefinition needs to be specified to register beans");

        if(! beanDefinition.validate()) {throw new BeanDefinitionRegisterException("The name is ["+beanName+"】 beanName is not legal," + beanDefinition);
        }
        if (containsBeanDefinition(beanName)) {
            throw new BeanDefinitionRegisterException("The name is ["+beanName+"] beanName has been registered," + beanName);
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public void close(a) throws IOException {
        // Execute the destruction method for the singleton Bean
        for(Map.Entry<String, BeanDefinition> e : beanDefinitionMap.entrySet()) {
            String beanName = e.getKey();
            BeanDefinition definition = e.getValue();

            if(definition.isSingleton() && StringUtils.isNotBlank(definition.getDestroyMethodName())) {
                Object instance = beanMap.get(beanName);
                if(instance == null) {continue; } Method m =null;
                try {
                    m = instance.getClass().getMethod(definition.getDestroyMethodName(), null);
                    m.invoke(instance, null);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
Copy the code

Test the

public interface Boy {
    void sayLove(a);
}
Copy the code

Lad implementation classes: mainly validation via new

public class Lad implements Boy {

    @Override
    public void sayLove(a) {
        System.out.println("I love you, honey!+ hashCode());
    }

    // Initialize method
    public void init(a) {
        System.out.println("God give me a date!");
    }

   // Destruction method
    public void destroy(a) {
        System.out.println("Since ancient times love spare hate, this hate continuous no unique period!"); }}Copy the code

BoyFactory class: Validates static factory methods

public class BoyFactory {

    public static Boy getBean(a) {
        return newLad(); }}Copy the code

BoyFactoryBean implementation class: validates member factory methods

public class BoyFactoryBean {

    public Boy buildBoy(a) {
        return new Boy() {
            @Override
            public void sayLove(a) {
                System.out.println("I love you, girl!"+ hashCode()); }}; }}Copy the code

The test class:

/ * * *@ClassName Test
 * @Description: Test class *@Author TR
 * @Date 2021/3/25
 * @VersionV1.0 * /
public class TestDemo {

    static DefaultBeanFactory factory = new DefaultBeanFactory();

    /** Test constructor registration Bean */
    @Test
    public void testRegister(a) throws Exception {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        / / set beanClass
        definition.setBeanClass(Lad.class);
        // Set it to a singleton
        definition.setScope(BeanDefinition.SCOPE_SINGLETON);
        // Set the initialization method
        definition.setInitMethodName("init");
        // Set the destruction method
        definition.setDestroyMethodName("destroy");
        // Register the bean definition
        factory.registerBeanDefinition("lad", definition);
    }

    /** Test the static factory registered Bean */
    @Test
    public void testRegisterStaticFactoryMethod(a) throws Exception {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        / / set beanClass
        definition.setBeanClass(BoyFactory.class);
        // Set the factory method name
        definition.setFactoryMethodName("getBean");
        // Register the bean definition
        factory.registerBeanDefinition("staticFactoryBoy", definition);
    }

    /** Test member method registration Bean */
    @Test
    public void testRegisterFactoryMethod(a) throws Exception {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        // First get the factory Bean
        definition.setBeanClass(BoyFactoryBean.class);
        // Name of the factory Bean
        String fBeanName = "boyFactoryBean";
        // Register the factory Bean definition
        factory.registerBeanDefinition(fBeanName, definition);

        // Then set the factory method
        definition = new GenericBeanDefinition();
        // Set the name of the factory Bean
        definition.setFactoryBeanName(fBeanName);
        // Set the factory method
        definition.setFactoryMethodName("buildBoy");
        // Set this parameter to multiple
        definition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        // Register the bean definition
        factory.registerBeanDefinition("factoryBoy", definition);
    }

    @AfterClass
    public static void testGetBean(a) throws Exception {
        System.out.println("Constructor mode ------------");
        for (int i = 0; i < 3; i++) {
            Boy boy = (Boy) factory.getBean("lad");
            boy.sayLove();
        }

        System.out.println("Static factory method approach ------------");
        for (int i = 0; i < 3; i++) {
            Boy ab = (Boy) factory.getBean("staticFactoryBoy");
            ab.sayLove();
        }

        System.out.println("Factory method method ------------");
        for (int i = 0; i < 3; i++) {
            Boy ab = (Boy) factory.getBean("factoryBoy"); ab.sayLove(); } factory.close(); }}Copy the code

Output after execution:

You can see that the constructor gets a Bean that has the same hashCode, that is, a singleton; Member methods are set up for multiple instances and see that hashCode is different

This is the end of the handwritten IOC container. I hope this article will give you a deeper understanding of Spring’s IOC. Thank you for reading!

Links to other knowledge points in this article:

Nuwa creates a Thought-provoking Java design pattern: Factory Pattern

There is only one of your Java design patterns: the singleton pattern