9
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

Catalogue of "Spring Handbook Column"

I. Introduction

growth of technology is the continuous engraving of the details of the scene design!

When do you think your technology has been rapidly improved? After writing more CRUD? Don't even think about it, it's absolutely impossible! No matter how much CRUD writes, it can only satisfy you as a brick-moving tool, and the speed of hitting less logical pipeline code, and the programming ability, except for the initial from unskilled to proficient, there are very few other things. Promoted.

Then you might think what is the improvement of programming ability? In fact, the improvement of more programming ability is your architectural control of complex scenarios and the continuous use of large-scale traffic impact verification for each technical implementation detail. Whether you can ensure the stable operation of the system and determine how much you have seen , How much has been learned, how much has been improved!

Finally, when you receive a product request, start thinking about the design of the program data structure, the algorithm logic realization of the core functions, the use of the overall service design pattern, the system architecture, the application cluster deployment structure, then That is when your programming ability is really improved!

2. Goal

The goal of this chapter is mainly to solve the pit buried by previous chapter What of pit is that? In fact, it is a pit about the instantiation of Bean objects with constructors.

In the previous chapter, we expanded the function of the Bean container and handed the instantiated object to the container for unified processing, but we did not consider whether the object class contains a constructor in the code of our instantiated object, which means that if we go to the instance If an object with a constructor is used, then an exception will be thrown.

How to verify? In fact, it is enough to add UserService with a constructor containing parameter information, as follows:

public class UserService {

    private String name;

    public UserService(String name) {
        this.name = name;
    }  

    // ...
}

The error is as follows:

java.lang.InstantiationException: cn.bugstack.springframework.test.bean.UserService

    at java.lang.Class.newInstance(Class.java:427)
    at cn.bugstack.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)
    ...

The main reason for this phenomenon is that the beanDefinition.getBeanClass().newInstance(); does not consider the parameters of the constructor, so this pit is waiting for you here! Then our goal is obvious, to fill this hole!

Three, design

The technical design of filling this hole mainly considers two parts. One is how to transfer the input parameter information of the constructor to the instantiation operation in a reasonable way from the string process, and the other is how to instantiate the object containing the constructor.

图 4-1

  • Refer to the implementation of the Spring Bean container source code, add the Object getBean(String name, Object... args) interface to the BeanFactory, so that the input parameter information of the constructor can be passed in when the Bean is obtained.
  • Another core content is what method is used to create a Bean object with a constructor? There are two ways to choose, one is based on Java's own method DeclaredConstructor , and the other is to use Cglib to dynamically create Bean objects. Cglib is implemented based on the bytecode framework ASM, so you can also directly create objects through the ASM operation instruction code

Fourth, realize

1. Engineering structure

small-spring-step-03
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── factory
    │           │   │   ├── BeanDefinition.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   ├── InstantiationStrategy.java
    │           │   │   └── SimpleInstantiationStrategy.java
    │           │   └── BeanFactory.java
    │           └── BeansException.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   └── UserService.java
                └── ApiTest.java

project source code : public account "bugstack wormhole stack", reply: Spring column, get the complete source code

Spring Bean container class relationship, as shown in Figure 4-2

图 4-2

This chapter "filling the hole" is mainly to add the InstantiationStrategy instantiation strategy interface to the existing project, and supplement the corresponding getBean input parameter information, so that the input parameters of the constructor can be passed and instantiated smoothly when external calls are made.

2. Add the getBean interface

cn.bugstack.springframework.beans.factory.BeanFactory

public interface BeanFactory {

    Object getBean(String name) throws BeansException;

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

}
  • In BeanFactory, we overload a getBean method that contains the input parameter information args, so that it is convenient to pass the input parameters to the constructor for instantiation.

3. Define the instantiation strategy interface

cn.bugstack.springframework.beans.factory.support.InstantiationStrategy

public interface InstantiationStrategy {

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

}
  • Add the necessary input parameter information in the instantiate method of the instantiated interface, including: beanDefinition, beanName, ctor, args
  • The Constructor may be a little unfamiliar to you. It is the Constructor class under the java.lang.reflect package, which contains some necessary class information. The purpose of this parameter is to get the constructor corresponding to the input parameter information.
  • And args is a specific input parameter information, which will be used in the final instantiation.

4. JDK instantiation

cn.bugstack.springframework.beans.factory.support.SimpleInstantiationStrategy

public class SimpleInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
        Class clazz = beanDefinition.getBeanClass();
        try {
            if (null != ctor) {
                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);
        }
    }

}
  • First obtain the Class information through beanDefinition, which is passed in when the Bean is defined.
  • Next, determine whether the ctor is empty, if it is empty, it means instantiation without a constructor, otherwise, it needs instantiation with a constructor.
  • Here we focus on instantiation with a constructor, the instantiation method is clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args); , and the input parameter information is passed to newInstance for instantiation.

5. Cglib instantiation

cn.bugstack.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy

public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        });
        if (null == ctor) return enhancer.create();
        return enhancer.create(ctor.getParameterTypes(), args);
    }

}
  • In fact, Cglib is also very convenient to create a bean with a constructor. Here we have simplified the processing. If you read the Spring source code, you will also see implementations such as CallbackFilter, but our current method will not affect the creation.

6. Create a policy call

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

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(beanDefinition, beanName, args);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

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

}
  • InstantiationStrategy instantiationStrategy creating objects is defined in the AbstractAutowireCapableBeanFactory abstract class. Here we choose the Cglib implementation class.
  • Next, extract the createBeanInstance method. In this method, you need to pay attention to how many constructors you have. You can get all your constructors through beanClass.getDeclaredConstructors(), which is a collection.
  • Next, we need to cyclically compare the match between the constructor set and the args parameter information 060b6ed380f3b7. Here we compare the method is relatively simple, just a quantitative comparison, and the actual Spring
    The source code also needs to compare the input parameter types, otherwise an exception will be thrown in the case of the same number of different input parameter types.

Five, test

1. Prepare in advance

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    private String name;

    public UserService(String name) {
        this.name = name;
    }

    public void queryUserInfo() {
        System.out.println("查询用户信息:" + name);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("");
        sb.append("").append(name);
        return sb.toString();
    }
}
  • The only thing that is added to UserService here is a constructor with name as a parameter, so that we can verify whether such an object can be instantiated.

2. Test cases

cn.bugstack.springframework.test.ApiTest

@Test
public void test_BeanFactory() {
    // 1.初始化 BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

    // 2. 注入bean
    BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
    beanFactory.registerBeanDefinition("userService", beanDefinition);

    // 3.获取bean
    UserService userService = (UserService) beanFactory.getBean("userService", "小傅哥");
    userService.queryUserInfo();
}
  • In this unit test, in addition to the three steps: Bean Factory, Registering Bean, and Obtaining Bean, it also adds an additional object acquisition and call. The main test here is to verify whether the singleton object is correctly stored in the cache.
  • In addition, unlike the test process in the previous chapter, we passed UserService.class to BeanDefinition instead of directly using the new UserService() operation as in the previous chapter.

3. Test results

查询用户信息:小傅哥

Process finished with exit code 0
  • From the test results, the biggest change is that objects with constructors can be instantiated.
  • You can try to use two different instantiation strategies to instantiate. SimpleInstantiationStrategy , CglibSubclassingInstantiationStrategy

4. Operation case

Here we put several different methods of instantiation operations into the unit test, so that everyone can compare and learn.

4.1 No constructor
@Test
public void test_newInstance() throws IllegalAccessException, InstantiationException {
    UserService userService = UserService.class.newInstance();
    System.out.println(userService);
}
  • This method of instantiation is also the method we used directly when we implemented the Spring Bean container in the previous chapter.
4.2 Verify that the constructor is instantiated
@Test
public void test_constructor() throws Exception {
    Class<UserService> userServiceClass = UserService.class;
    Constructor<UserService> declaredConstructor = userServiceClass.getDeclaredConstructor(String.class);
    UserService userService = declaredConstructor.newInstance("小傅哥");
    System.out.println(userService);
}
  • From the simplest operation point of view, if a class with a constructor function needs to be instantiated, you need to use getDeclaredConstructor obtain the constructor function, and then instantiate it by passing parameters.
4.3 Get constructor information
@Test
public void test_parameterTypes() throws Exception {
    Class<UserService> beanClass = UserService.class;
    Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
    Constructor<?> constructor = declaredConstructors[0];
    Constructor<UserService> declaredConstructor = beanClass.getDeclaredConstructor(constructor.getParameterTypes());
    UserService userService = declaredConstructor.newInstance("小傅哥");
    System.out.println(userService);
  • In fact, the core point in this case is to get all the constructors in a class, which is actually the use of this method beanClass.getDeclaredConstructors()
4.4 Cglib instantiation
@Test
public void test_cglib() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(UserService.class);
    enhancer.setCallback(new NoOp() {
        @Override
        public int hashCode() {
            return super.hashCode();
        }
    });
    Object obj = enhancer.create(new Class[]{String.class}, new Object[]{"小傅哥"});
    System.out.println(obj);
}
  • This case demonstrates the use of very simple, but about the use of Cglib in the Spring container is very much, you can also learn about the extended knowledge of Cglib.

Six, summary

  • The main purpose of this chapter is to improve the instantiation operation, increase the InstantiationStrategy instantiation strategy interface, and add two new instantiation classes. The name and implementation of this part of the class is basically a reduced version of the Spring framework. You can also find the corresponding code from the Spring source code during the learning process.
  • As we can see from our continuous improvement and increasing requirements, when your code structure is designed more reasonable, it is very easy and convenient to extend the class responsibilities of different attributes without causing confusion in the class structure due to the increase in requirements. . Therefore, in the process of realizing our own business requirements, we must also consider a good scalability and split the responsibilities of good classes as much as possible.
  • Hands-on is the fastest way to learn. Don't let your eyes feel like you can see it, but the hands-on operation is useless. I also hope that readers in need can do it with their hands, incorporate your ideas into the code that can be implemented, and see if what they think is consistent with what they do.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。