6
头图

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

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

I. Introduction

old driver, how did your brick move so fast?

Is it energetic? Is it a technique? Is it the back door? In short, the old driver's code can always quickly complete each new requirement of the product, as if they are one family! But you are not the same. Not only the product manager but also the operating and testing ladies have to buy food for you. I beg you to fix the bugs quickly, otherwise there will be no time to go online.

So why other people's code can always be quickly expanded with new features, and your code can never be refactored and can only be rewritten, small changes with small requirements, big changes with large requirements, and no requirements? I don’t need to run, I can crash even when I run, and I was stunned by the operation and maintenance in the middle of the night: “Why do you have a slow database query again, which is dragging other people’s business!”

Some people say that 30-year-olds are still doing the same jobs as those who just graduated, so there is no improvement! This is too nonsense. It is the same thing, but the results are not necessarily the same. Some people can use ifelse to put together the product functions, or some people can disassemble the requirements into various functional modules, define interfaces, and abstract classes. , Realization and inheritance, using design patterns to build a set of code logic that can be quickly implemented when new requirements are added, and problems can be accurately located. It's like someone asked: "There are ten birds on the tree, one shot to death, how many more?" What would you think of? Is the gunshot loud, is the bird cage, is the bird tied to the tree, is there any bird disabled, is the bird killed, is the eye of the bird beating, counts as the pregnant bird in the belly, and the bird beating? Are you breaking the law? Are there other birds on the side of the tree, etc. These are all the experience honed by a professional technician in an industry. It is not something that can be absorbed by reading a few books a day or two and brushing a few brainwashing articles.

2. Goal

The Bean object handed over to Spring for management must be the Bean we created with the class? Is the created Bean always a singleton? Is it possible that it is a prototype pattern?

Under the integrated Spring framework, in the MyBatis framework that we use, its core function is to satisfy that users do not need to implement Dao interface classes, and they can perform CRUD operations on the database through xml or annotation configuration. Then in the implementation of such an ORM In the framework, how do you hand over a Bean object for database operations to Spring for management?

Because we can know when using Spring and MyBatis frameworks, we did not manually create any Bean objects that manipulate the database. Some are just an interface definition, and this interface definition can be injected into other attributes that need to use Dao. So, the core problem to be solved in this process is the need to complete the registration of complex and dynamically changing objects in the Spring container. In order to meet the needs of such an extended component development, we need to add this capability to the existing handwritten Spring framework.

Three, the plan

Regarding providing a Bean object that allows users to define complex Beans, the function is very good, and the significance is very great, because after doing this, Spring's ecological seed incubator is provided, and anyone's framework can complete itself on this standard. Access to services.

But this kind of functional logic design is not complicated, because the entire Spring framework has provided with various expansion capabilities during the development process. You only need to provide a connection processing interface call and corresponding functions at the right place. The logic can be realized. The goal here is to provide a function that can obtain objects from FactoryBean's getObject method twice, so that all object classes that implement this interface can expand their object functions. MyBatis is to implement a MapperFactoryBean class, providing SqlSession in the getObject method to execute the CRUD method operation overall design structure of 160e3b92d2ec2a is as follows:

  • The entire implementation process includes two parts, one is to solve the singleton or the prototype object, and the other is to deal with the getObject operation of obtaining the specific call object during the creation of the FactoryBean type object.
  • SCOPE_SINGLETON , SCOPE_PROTOTYPE , the creation and acquisition method of the object type, the main difference is AbstractAutowireCapableBeanFactory#createBean is placed in the memory after the object is created, if not, it will be recreated every time it is acquired.
  • After createBean performs operations such as object creation, attribute filling, dependency loading, pre-post processing, initialization, etc., it is necessary to start execution to determine whether the entire object is a FactoryBean object. If it is such an object, you need to continue execution to obtain the FactoryBean. getObject object in the object is now. factory.isSingleton() will be added throughout the getBean process to determine whether to use memory to store object information.

Fourth, realize

1. Engineering structure

small-spring-step-09
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── config
    │           │   │   │   ├── 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
    │           │   │   │   ├── FactoryBeanRegistrySupport.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── Aware.java
    │           │   │   ├── BeanClassLoaderAware.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── BeanFactoryAware.java
    │           │   │   ├── BeanNameAware.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── FactoryBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   └── ListableBeanFactory.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   ├── ApplicationContextAwareProcessor.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   ├── ApplicationContextAware.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

Spring singleton, prototype and FactoryBean function realization class relationship, as shown in Figure 10-2

图 10-2

  • The entire class diagram above shows whether the instantiation of the added Bean is a singleton or a prototype mode and the realization of FactoryBean.
  • In fact, the entire implementation process is not complicated, it is just extended in the existing AbstractAutowireCapableBeanFactory class and the inherited abstract class AbstractBeanFactory.
  • But this time we added a layer of FactoryBeanRegistrySupport to the DefaultSingletonBeanRegistry class inherited from AbstractBeanFactory. This class is mainly used to handle the support operations related to FactoryBean registration in the Spring framework.

2. Bean's scope definition and xml analysis

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

public class BeanDefinition {

    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;

    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

    private Class beanClass;

    private PropertyValues propertyValues;

    private String initMethodName;

    private String destroyMethodName;

    private String scope = SCOPE_SINGLETON;

    private boolean singleton = true;

    private boolean prototype = false;
    
    // ...get/set
}
  • Singleton and prototype are two newly added attribute information in the BeanDefinition class this time, which are used to fill the scope of the Bean object parsed from spring.xml into the attributes.

cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
      
        for (int i = 0; i < childNodes.getLength(); i++) {
            // 判断元素
            if (!(childNodes.item(i) instanceof Element)) continue;
            // 判断对象
            if (!"bean".equals(childNodes.item(i).getNodeName())) continue;

            // 解析标签
            Element bean = (Element) childNodes.item(i);
            String id = bean.getAttribute("id");
            String name = bean.getAttribute("name");
            String className = bean.getAttribute("class");
            String initMethod = bean.getAttribute("init-method");
            String destroyMethodName = bean.getAttribute("destroy-method");
            String beanScope = bean.getAttribute("scope");

            // 获取 Class,方便获取类中的名称
            Class<?> clazz = Class.forName(className);
            // 优先级 id > name
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            // 定义Bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethodName);

            if (StrUtil.isNotEmpty(beanScope)) {
                beanDefinition.setScope(beanScope);
            }
            
            // ...
            
            // 注册 BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }

}
  • In the parsing XML processing class XmlBeanDefinitionReader, the parsing of the scope in the Bean object configuration is newly added, and this attribute information is filled into the Bean definition. beanDefinition.setScope(beanScope)

3. Judge singleton and prototype patterns when creating and modifying objects

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);

        // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
        if (beanDefinition.isSingleton()) {
            addSingleton(beanName, bean);
        }
        return bean;
    }

    protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
        // 非 Singleton 类型的 Bean 不执行销毁方法
        if (!beanDefinition.isSingleton()) return;

        if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
            registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
        }
    }
    
    // ... 其他功能
}
  • The difference between singleton mode and prototype mode is whether it is stored in memory or not. If it is in prototype mode, it will not be stored in memory. The object is recreated every time it is retrieved. In addition, non-Singleton type Beans do not need to execute the destruction method.
  • So the code here will have two modifications, one is to determine whether to add to addSingleton(beanName, bean); in createBean, and the other is to determine whether to add to addSingleton(beanName, bean); in registerDisposableBeanIfNecessary to destroy the registration if (!beanDefinition.isSingleton()) return; .

4. Define the FactoryBean interface

cn.bugstack.springframework.beans.factory.FactoryBean

public interface FactoryBean<T> {

    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();

}
  • Three methods need to be provided in FactoryBean to get the object, object type, and whether it is a singleton object. If it is a singleton object, it will still be placed in memory.

5. Implement a FactoryBean registration service

cn.bugstack.springframework.beans.factory.support.FactoryBeanRegistrySupport

public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {

    /**
     * Cache of singleton objects created by FactoryBeans: FactoryBean name --> object
     */
    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    protected Object getCachedObjectForFactoryBean(String beanName) {
        Object object = this.factoryBeanObjectCache.get(beanName);
        return (object != NULL_OBJECT ? object : null);
    }

    protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
        if (factory.isSingleton()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                object = doGetObjectFromFactoryBean(factory, beanName);
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        } else {
            return doGetObjectFromFactoryBean(factory, beanName);
        }
    }

    private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
        try {
            return factory.getObject();
        } catch (Exception e) {
            throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
        }
    }

}
  • The FactoryBeanRegistrySupport class mainly deals with the registration operations of such objects as FactoryBean. The reason why it is placed in such a separate class is to achieve that modules in different domains are only responsible for the functions they need to complete, so as to avoid the expansion of the class due to expansion. maintain.
  • The cache operation factoryBeanObjectCache is also defined here, which is used to store singleton type objects to avoid repeated creation. in daily use, and it is basically a singleton object
  • doGetObjectFromFactoryBean is a specific method of obtaining FactoryBean#getObject(). Because it has both cache processing and object acquisition, it additionally provides getObjectFromFactoryBean for logical packaging. The operation method of this part is not very similar to the business logic development you do daily. fetch data from Redis, if it is empty, fetch it from the database and write it to Redis

6. Extend AbstractBeanFactory to create object logic

cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

    protected <T> T doGetBean(final String name, final Object[] args) {
        Object sharedInstance = getSingleton(name);
        if (sharedInstance != null) {
            // 如果是 FactoryBean,则需要调用 FactoryBean#getObject
            return (T) getObjectForBeanInstance(sharedInstance, name);
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        Object bean = createBean(name, beanDefinition, args);
        return (T) getObjectForBeanInstance(bean, name);
    }  
   
    private Object getObjectForBeanInstance(Object beanInstance, String beanName) {
        if (!(beanInstance instanceof FactoryBean)) {
            return beanInstance;
        }

        Object object = getCachedObjectForFactoryBean(beanName);

        if (object == null) {
            FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
            object = getObjectFromFactoryBean(factoryBean, beanName);
        }

        return object;
    }
        
    // ...
}
  • First, modify the DefaultSingletonBeanRegistry originally inherited by AbstractBeanFactory to inherit FactoryBeanRegistrySupport. Because of the need to extend the ability to create FactoryBean objects, it is thought of a chain service, cut out a segment to handle additional services, and link the chain again.
  • The newly added function here is mainly in the doGetBean method, adding the (T) getObjectForBeanInstance(sharedInstance, name) to get the FactoryBean.
  • Make specific instanceof judgments in the getObjectForBeanInstance method, and also get the object from the FactoryBean cache. If it does not exist, call FactoryBeanRegistrySupport#getObjectFromFactoryBean to perform specific operations.

Five, test

1. Prepare in advance

cn.bugstack.springframework.test.bean.IUserDao

public interface IUserDao {

    String queryUserName(String uId);

}
  • In this chapter, we delete UserDao and define an IUserDao interface. What we do is to do a proxy operation of a custom object through FactoryBean.

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    private String uId;
    private String company;
    private String location;
    private IUserDao userDao;

    public String queryUserInfo() {
        return userDao.queryUserName(uId) + "," + company + "," + location;
    }

    // ...get/set
}
  • In UserService, an original UserDao attribute is newly modified to IUserDao, and we will inject proxy objects into this attribute later.

2. Define the FactoryBean object

cn.bugstack.springframework.test.bean.ProxyBeanFactory

public class ProxyBeanFactory implements FactoryBean<IUserDao> {

    @Override
    public IUserDao getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {

            Map<String, String> hashMap = new HashMap<>();
            hashMap.put("10001", "小傅哥");
            hashMap.put("10002", "八杯水");
            hashMap.put("10003", "阿毛");
            
            return "你被代理了 " + method.getName() + ":" + hashMap.get(args[0].toString());
        };
        return (IUserDao) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserDao.class}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return IUserDao.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • This is the name of the proxy class ProxyBeanFactory that implements the interface FactoryBean. It mainly simulates the original function of UserDao, similar to the proxy operation in the MyBatis framework.
  • What is provided in getObject() is a proxy object of InvocationHandler. When a method is called, the function of the proxy object is executed.

3. Configuration file

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

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

    <bean id="proxyUserDao" class="cn.bugstack.springframework.test.bean.ProxyBeanFactory"/>

</beans>
  • In the configuration file, we inject the proxy object proxyUserDao into userDao of userService. Compared with the previous chapter, the UserDao implementation class is removed, and

4. Unit testing (single case & prototype)

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

    // 2. 获取Bean对象调用方法
    UserService userService01 = applicationContext.getBean("userService", UserService.class);
    UserService userService02 = applicationContext.getBean("userService", UserService.class);
    
    // 3. 配置 scope="prototype/singleton"
    System.out.println(userService01);
    System.out.println(userService02);    

    // 4. 打印十六进制哈希
    System.out.println(userService01 + " 十六进制哈希:" + Integer.toHexString(userService01.hashCode()));
    System.out.println(ClassLayout.parseInstance(userService01).toPrintable());

}
  • In the spring.xml configuration file, scope="prototype" is set so that the object obtained every time should be a new object.
  • It is judged whether the object is a hash value of a class object that will be printed, so we print the hexadecimal hash.

test result

cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@2f7c7260
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3 十六进制哈希:1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984 object internals:
 OFFSET  SIZE                                             TYPE DESCRIPTION                                               VALUE
      0     4                                                  (object header)                                           01 b3 75 03 (00000001 10110011 01110101 00000011) (58045185)
      4     4                                                  (object header)                                           1b 00 00 00 (00011011 00000000 00000000 00000000) (27)
      8     4                                                  (object header)                                           9f e1 01 f8 (10011111 11100001 00000001 11111000) (-134094433)
     12     4                                 java.lang.String UserService.uId                                           (object)
     16     4                                 java.lang.String UserService.company                                       (object)
     20     4                                 java.lang.String UserService.location                                      (object)
     24     4   cn.bugstack.springframework.test.bean.IUserDao UserService.userDao                                       (object)
     28     1                                          boolean UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$BOUND        true
     29     3                                                  (alignment/padding gap)                                  
     32     4                          net.sf.cglib.proxy.NoOp UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$CALLBACK_0   (object)
     36     4                                                  (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total


Process finished with exit code 0

  • The small string behind the object is the hexadecimal hash value. From the result of the hash value stored in the object header, there is also a corresponding value. It's just that the result is reversed.
  • In addition, you can see cabb984@1b0375b3 and cabb984@2f7c7260. The ending hexadecimal hash values of these two objects are not the same, so our prototype mode is effective.

5. Unit Testing (Proxy Object)

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

    // 2. 调用代理方法
    UserService userService = applicationContext.getBean("userService", UserService.class);
    System.out.println("测试结果:" + userService.queryUserInfo());
}
  • There are not too many differences in the invocation of FactoryBean, because all the differences have been configured by spring.xml. Of course you can directly call the object cn.bugstack.springframework.test.bean.ProxyBeanFactory

test result

测试结果:你被代理了 queryUserName:小傅哥,腾讯,深圳

Process finished with exit code 0
  • From the test results, our proxy class ProxyBeanFactory has perfectly replaced the function of UserDao.
  • Although it seems that the realization of this is not complicated, or even a bit simple. But this is the design of a little bit of the core content, which solves all the problems of interaction and linking with other frameworks that need to be combined with Spring. If you are interested in such content, you can also read Xiao Fu "Middleware Design and Development"

Six, summary

  • In the entire development process of the Spring framework, the various functional interface classes in the early stage were expanded as if they were expanded, but in the later stage when the functions are improved, it is not so difficult. On the contrary, after in-depth understanding, you will find that the function supplements are relatively simple. . Only need to expand the corresponding service realization within the scope of the encountered field.
  • When you have carefully read about the implementation of FactoryBean and the use of the test process, when you need to use FactoryBean to develop corresponding components in the future, you will be very clear about how it creates its own complex Bean object and when it is initialized and invoked. If you encounter problems, you can quickly troubleshoot, locate and resolve them.
  • If you feel that these classes, interfaces, implementations, and inheritance are very complicated in the process of learning, you will not be able to react to them for a while. So your best way is to draw these class diagrams, sort out the structure of the implementation, and see what each class is doing. Seeing can only be known, hands-on can learn!

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

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