1
头图

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

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

I. Introduction

there any way to keep the code alive?

Some people say: Everyone is a product manager. Did you know that everyone can be a code farmer? Like:

  • Programming is; defining attributes, creating methods, calling display
  • Java and PHP are like men and women. The former cares about the architecture of modules, and the latter cares about the color. I like it
  • Write carefully, but don’t format it
  • The three treasures for the first docking with the product: bricks, shovel, and kitchen knives, which are guaranteed to be useful, usable, and easy to use.
  • From one line of code to one ton of code, development is getting harder and harder and the barriers are getting higher and higher

In fact, it is not difficult to learn to write code, but it is difficult to learn to write good code. In terms of ease of reading, your code must have accurate naming and clear comments, in terms of ease of use, your code must have design pattern packaging to make external service calls easier, and in terms of ease of extension, your code must be done The realization of good services and functions are layered. Under the constraints of easy-to-read, easy-to-use, easy-to-expand, and more coding standards, it is also necessary to meet the delivery results after the development is completed; high availability, high performance, high concurrency, and at the same time you will receive existing Projects are constantly emerging from new demands from product managers.

🎙What should I do? Know that you are building bricks, but don't know which pigsty you are building!

Even if the coded bricks are used to build pig pens, they have to expand the area, change the tanks, and add feed for the pigs, so there is no guarantee that the code you write will not increase the demand. So what if the newly added demand destroys your original ifelse , which encapsulates a perfect 500 lines, and removes the heavy cover?

Brother, leave a way for the code! Does your code use the definition interface, does the interface inherit the interface, does the interface be implemented by the abstract class, and does the class inherited from the class implement the interface method? These operations are all for the purpose of making your program logic hierarchical, partitioned, and Partition, to isolate the core logic layer from the business encapsulation layer. When there is a business change, you only need to complete the assembly in the business layer, and the underlying core logic services do not need to change frequently, and the interfaces they add are more atomic. It does not have business semantics. So this way of implementation can leave a way for your code to stay alive. If you don’t understand too much, you can take a look at "Re-learning Java Design Patterns" and the current "Hand- Spring" 160d3f5441c433, there are a lot of design pattern application practices

2. Goal

When the Bean object created by our class is handed over to the Spring container for management, this class object can be given more usage capabilities. Just as we have added to the class object in the previous chapter the modification of the registered Bean definition before the attribute information modification before instantiation and the pre- and post-processing during the instantiation process. The realization of these additional capabilities allows us to Class objects in existing projects are expanded accordingly.

In addition, we also hope to perform some operations during the Bean initialization process. For example, help us do some data loading and execution, link registry leaks RPC interface, and perform operations such as link disconnection and memory destruction when the Web program is closed. If we don't have Spring, we can also implement it through constructors, static methods, and manual calls, but after all, such a processing method is not as good as handing such operations to the Spring container to manage it. So you will see the following operations in spring.xml:

  • Need to meet the user can configure the initialization and destruction methods in xml, and can also be handled by the way of implementation classes, such as the InitializingBean and DisposableBean interfaces that we use when using Spring.
    -In fact, there is another way to handle the initialization operation in the form of annotations, but the logic of annotations has not yet been implemented, and such functions will be improved in the future.

Three, design

It is possible that in the face of a huge framework like Spring, the exposed interface definitions or xml configuration, and the completion of a series of extensibility operations, make the Spring framework look very mysterious. In fact, for the additional processing operations added during the initialization of the Bean container, it is nothing more than pre-executing a defined interface method or reflecting the method configured in the xml in the class. In the end, you only need to implement it according to the interface definition, and there will be Spring The container only makes calls during processing. The overall design structure is as follows:

  • Add spring.xml configuration init-method、destroy-method two notes, in the course of the configuration file that is loaded in the notes together define configuration attributes to BeanDefinition of them. In this way, in the project of the initializeBean initialization operation, the method information configured in the Bean definition attribute can be called by reflection. In addition, if it is the way of interface implementation, you can directly call the method defined by the corresponding interface through the Bean object, ((InitializingBean) bean).afterPropertiesSet() . The effect of the two methods is the same.
  • In addition to the initialization operations, destroy-method and DisposableBean interfaces will execute the registration and destruction method information in the disposableBeans attribute of the DefaultSingletonBeanRegistry class at the completion stage of the Bean object initialization. This is for subsequent unified operations. There is also the use of an adapter here, because there are two ways to call reflection and interface directly. Therefore, you need to use an adapter for packaging. In the code explanation below, refer to the specific implementation of DisposableBeanAdapter
    -The destruction method needs to be operated before the virtual machine is closed, so here you need to use a registration hook operation, such as: Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!"))); This code you can execute the test , and you can manually call the ApplicationContext.close method to close the container .

Fourth, realize

1. Engineering structure

small-spring-step-07
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── factory
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.java
    │           │   │   │   └── SingletonBeanRegistry.java
    │           │   │   ├── support
    │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   │   ├── AbstractBeanDefinitionReader.java
    │           │   │   │   ├── AbstractBeanFactory.java
    │           │   │   │   ├── BeanDefinitionReader.java
    │           │   │   │   ├── BeanDefinitionRegistry.java
    │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   │   ├── DefaultListableBeanFactory.java
    │           │   │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   │   ├── DisposableBeanAdapter.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   └── ListableBeanFactory.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   └── ConfigurableApplicationContext.java
    │           ├── core.io
    │           │   ├── ClassPathResource.java 
    │           │   ├── DefaultResourceLoader.java 
    │           │   ├── FileSystemResource.java 
    │           │   ├── Resource.java 
    │           │   ├── ResourceLoader.java 
    │           │   └── UrlResource.java
    │           └── utils
    │               └── ClassUtils.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── UserDao.java
                │   └── UserService.java
                └── ApiTest.java

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

The class relationship between Spring application context and Bean object extension mechanism, as shown in Figure 8-4

图 8-4

  • The entire class diagram structure described above is the initialization method and destruction method in the process of instantiating the newly added Bean.
  • Because we have implemented two ways of initialization and destruction methods, xml configuration and definition interfaces, there are both InitializingBean, DisposableBean and XmlBeanDefinitionReader to load spring.xml configuration information into BeanDefinition.
  • In addition, the interface ConfigurableBeanFactory defines the destroySingletons destruction method, and the parent class DefaultSingletonBeanRegistry inherited by AbstractBeanFactory implements the destroySingletons method defined by the ConfigurableBeanFactory interface. This method of design may be unused by programmers. Whoever implements the interface is used to complete the implementation class, instead of handing the implementation of the interface to the inherited parent class. So this one is quite interesting, it is a good way to isolate hierarchical services
  • Finally, it is about registering the hook to the virtual machine to ensure that the destruction operation is performed before the virtual machine is shut down. Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!")));

2. Define the interface of initialization and destruction methods

cn.bugstack.springframework.beans.factory.InitializingBean

public interface InitializingBean {

    /**
     * Bean 处理了属性填充后调用
     * 
     * @throws Exception
     */
    void afterPropertiesSet() throws Exception;

}

cn.bugstack.springframework.beans.factory.DisposableBean

public interface DisposableBean {

    void destroy() throws Exception;

}
  • InitializingBean, DisposableBean, the two interface methods are still relatively common, in some components that need to be implemented in conjunction with Spring, these two methods are often used to initialize and destroy some parameters. Such as interface exposure, database data reading, configuration file loading and so on.

3. New initialization and destruction of Bean attribute definition

cn.bugstack.springframework.beans.factory.config.BeanDefinition

public class BeanDefinition {

    private Class beanClass;

    private PropertyValues propertyValues;

    private String initMethodName;
    
    private String destroyMethodName;
    
    // ...get/set
}
  • Two new attributes are added to BeanDefinition: initMethodName and destroyMethodName. These two attributes are used to configure the init-method="initDataMethod" destroy-method="destroyDataMethod" operation in the Bean object configured in spring.xml, and the effect of the final implementation of the interface is the same. just a direct call of the interface method, and the other is a method reflection call

4. Execute the initialization method of the Bean object

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

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    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);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        // ...

        addSingleton(beanName, bean);
        return bean;
    }

    private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
        // 1. 执行 BeanPostProcessor Before 处理
        Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);

        // 执行 Bean 对象的初始化方法
        try {
            invokeInitMethods(beanName, wrappedBean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e);
        }

        // 2. 执行 BeanPostProcessor After 处理
        wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        return wrappedBean;
    }

    private void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) throws Exception {
        // 1. 实现接口 InitializingBean
        if (bean instanceof InitializingBean) {
            ((InitializingBean) bean).afterPropertiesSet();
        }

        // 2. 配置信息 init-method {判断是为了避免二次执行销毁}
        String initMethodName = beanDefinition.getInitMethodName();
        if (StrUtil.isNotEmpty(initMethodName)) {
            Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);
            if (null == initMethod) {
                throw new BeansException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");
            }
            initMethod.invoke(bean);
        }
    }

}
  • The createBean in the abstract class AbstractAutowireCapableBeanFactory is a method used to create Bean objects. In this method, we have previously extended the BeanFactoryPostProcessor and BeanPostProcessor operations. Here we continue to improve the processing actions of the initialization method of the Bean object.
  • In the method invokeInitMethods, it is mainly divided into two parts to perform operations that implement the InitializingBean interface and process the afterPropertiesSet method. The other is to determine whether the configuration information init-method exists, and perform reflection to call initMethod.invoke(bean). Both of these methods can handle the initialization operation in loading the Bean object during the initialization of the Bean object, allowing users to add additional actions they want.

5. Define the destruction method adapter (interface and configuration)

cn.bugstack.springframework.beans.factory.support.DisposableBeanAdapter

public class DisposableBeanAdapter implements DisposableBean {

    private final Object bean;
    private final String beanName;
    private String destroyMethodName;

    public DisposableBeanAdapter(Object bean, String beanName, BeanDefinition beanDefinition) {
        this.bean = bean;
        this.beanName = beanName;
        this.destroyMethodName = beanDefinition.getDestroyMethodName();
    }

    @Override
    public void destroy() throws Exception {
        // 1. 实现接口 DisposableBean
        if (bean instanceof DisposableBean) {
            ((DisposableBean) bean).destroy();
        }

        // 2. 配置信息 destroy-method {判断是为了避免二次执行销毁}
        if (StrUtil.isNotEmpty(destroyMethodName) && !(bean instanceof DisposableBean && "destroy".equals(this.destroyMethodName))) {
            Method destroyMethod = bean.getClass().getMethod(destroyMethodName);
            if (null == destroyMethod) {
                throw new BeansException("Couldn't find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");
            }
            destroyMethod.invoke(bean);
        }
        
    }

}
  • You may be wondering why there is an adapter class here, because there are two or more ways to destroy it. Currently, there are implementing interface DisposableBean and configuration information destroy-method. The destruction actions of these two methods are the operation actions performed by AbstractApplicationContext after registering the virtual machine hook and before the virtual machine is shut down.
  • So when the destruction is executed, I don't want to pay attention to the methods of destroying all those types. In its use, it is more hoped that there is a unified interface for destruction, so here is a new adaptation class for unified processing.

6. Register the destruction method object when creating a Bean

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

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    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);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        // 注册实现了 DisposableBean 接口的 Bean 对象
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        addSingleton(beanName, bean);
        return bean;
    }

    protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
        if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
            registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
        }
    }

}
  • When creating an instance of a Bean object, you need to save the destruction method to facilitate the subsequent execution of the destruction action to call.
  • Then the specific method information of this destruction method will be registered in the newly added Map<String, DisposableBean> disposableBeans attribute in DefaultSingletonBeanRegistry, because the method of this interface can finally be called by the close method of the class AbstractApplicationContext through getBeanFactory().destroySingletons() .
  • When registering the destruction method, it will be handed over to DisposableBeanAdapter to destroy the adapter class for unified processing based on the interface type and configuration type. A class that implements an interface can be judged by instanceof or call the interface method

7. The virtual machine closes the hook registration and calls the destroy method

cn.bugstack.springframework.context.ConfigurableApplicationContext

public interface ConfigurableApplicationContext extends ApplicationContext {

    void refresh() throws BeansException;

    void registerShutdownHook();

    void close();

}
  • First we need to define the method of registering a virtual machine hooks in ConfigurableApplicationContext interface registerShutdownHook and methods performed manually closed close .

cn.bugstack.springframework.context.support.AbstractApplicationContext

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    // ...

    @Override
    public void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
    }

    @Override
    public void close() {
        getBeanFactory().destroySingletons();
    }

}
  • This mainly reflects the realization of the method of registering the hook and closing. The Runtime.getRuntime().addShutdownHook mentioned above, you can try to verify it. It can also be used in the design of some middleware and monitoring systems, such as monitoring server downtime and performing standby machine startup operations.

Five, test

1. Prepare in advance

cn.bugstack.springframework.test.bean.UserDao

public class UserDao {

    private static Map<String, String> hashMap = new HashMap<>();

    public void initDataMethod(){
        System.out.println("执行:init-method");
        hashMap.put("10001", "小傅哥");
        hashMap.put("10002", "八杯水");
        hashMap.put("10003", "阿毛");
    }

    public void destroyDataMethod(){
        System.out.println("执行:destroy-method");
        hashMap.clear();
    }

    public String queryUserName(String uId) {
        return hashMap.get(uId);
    }

}

cn.bugstack.springframework.test.bean.UserService

public class UserService implements InitializingBean, DisposableBean {

    private String uId;
    private String company;
    private String location;
    private UserDao userDao;

    @Override
    public void destroy() throws Exception {
        System.out.println("执行:UserService.destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行:UserService.afterPropertiesSet");
    }

    // ...get/set
}
  • UserDao, modified the previous method of initializing data using static static blocks, and changed to provide two more elegant operation methods, initDataMethod and destroyDataMethod, for processing.
  • UserService, to implement the two methods destroy() and afterPropertiesSet() of the interface InitializingBean and DisposableBean, handle the actions of the corresponding initialization and destruction methods. afterPropertiesSet, the method name is very good, execute

2. Configuration file

basic configuration, no BeanFactoryPostProcessor, BeanPostProcessor, implementation class

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao" init-method="initDataMethod" destroy-method="destroyDataMethod"/>

    <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
        <property name="uId" value="10001"/>
        <property name="company" value="腾讯"/>
        <property name="location" value="深圳"/>
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>
  • The configuration file is mainly newly added, init-method="initDataMethod" destroy-method="destroyDataMethod" , such two configurations. From the study of the source code, we can know that these two configurations are to be added to the BeanDefinition definition class and then written into the beanDefinitionMap property in the class DefaultListableBeanFactory.

3. Unit Testing

@Test
public void test_xml() {
    // 1.初始化 BeanFactory
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    applicationContext.registerShutdownHook();      

    // 2. 获取Bean对象调用方法
    UserService userService = applicationContext.getBean("userService", UserService.class);
    String result = userService.queryUserInfo();
    System.out.println("测试结果:" + result);
}
  • A new addition to the test method is the action of registering the hook. applicationContext.registerShutdownHook();

test result

执行:init-method
执行:UserService.afterPropertiesSet
测试结果:小傅哥,腾讯,深圳
执行:UserService.destroy
执行:destroy-method

Process finished with exit code 0
  • From the test results, we can see that our newly added initial and destruction methods can already output the results as expected.

Six, summary

  • This article mainly completes the implements InitializingBean, DisposableBean for initial and destroying and configuring init-method="initDataMethod" destroy-method="destroyDataMethod" in AbstractAutowireCapableBeanFactory complete the initial method in 060d3f5441d66a and the specific implementation process of AbstractApplicationContext
  • Through the implementation of this article, we can see that the operation of the Bean by the Spring framework is becoming more and more perfect, and the scalability is also continuously enhanced. You can either perform the BeanFactoryPostProcessor operation before the Bean registration is completed and instantiate, or you can perform the pre- and post-processing operations during the Bean instantiation process, and now you can perform the Bean's initialization method and destruction method. So a simple Bean object has been given various extension capabilities.
  • In the process of learning and practicing the Spring framework, special attention should be paid to its grasp and use of interfaces and abstract classes, especially when encountering similarities, when A inherits B and implements C, the interface method of C is inherited by the parent class of A B implementation, such operations are quite interesting. It can also be reused in normal business system development to process some complex logic functional layers, so that the program can be extended and easy to maintain.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

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