Handwritten simple spring --9 -- object scope and FactoryBean

Posted by ToddAtWSU on Sat, 25 Dec 2021 18:31:56 +0100

1, Target

Does the Bean object managed by Spring have to be the Bean created by our class? Will the created Bean always be a singleton? Can't it be a prototype pattern? In the MyBatis framework we use under the collection Spring framework, its core function is to enable users to perform CRUD operations on the database by means of xml or annotation configuration without implementing Dao interface classes. In this ORM framework, how can a Bean object for database operations be handed over to Spring for management. When we use Spring and MyBatis frameworks, we can know that we do not manually create any Bean objects that operate the database. Some are just an interface definition, and this interface definition can be injected into other attributes that need to use Dao. Then the core problem to be solved in this process, It is necessary to register complex and dynamically changing proxy objects into the Spring container. In order to meet the needs of such an extension component development, we need to add this capability to the existing handwritten Spring framework.

2, Scheme

As for providing a Bean object that allows users to define complex beans, the function points are very good and of great significance, because after doing so, Spring's ecological seed incubator is provided, and anyone's framework can access their own services on this standard. However, the design of such functional logic is not complicated, because the whole Spring framework has provided the connection of various extension capabilities in the development process. You only need to provide a continuous processing interface call and corresponding functional logic implementation in the appropriate position, The goal here is to provide an external function that can obtain objects from the getObject method of FactoryBean twice, so that all object classes that implement this interface can expand their own object functions. MyBatis implements a MapperFactoryBean class that provides SqlSession in the getObject method to perform CRUD method operations. The overall design structure is as follows:

  1. The whole implementation process includes two parts: one is to solve the singleton or prototype object, and the other is to deal with the getObject operation of obtaining the specific calling object during the creation of FactoryBean type object.
  2. SCOPE_SINGLETON,SCOPE_PROTOTYPE is the creation and acquisition method of object type. The main difference is
    AbstractAutowireCapableBeanFactory#createBean whether to put the object into memory after it is created. If not, it will be recreated every time it is obtained.
  3. After the createBean performs operations such as object creation, attribute filling, dependency loading, pre and post processing, initialization, etc., it must start execution to judge whether the whole object is a FactoryBean object. If it is such an object, it needs to continue to obtain the getObject object object in the specific object of FactoryBean. A judgment factory of singleton type will be added in the whole getBean process Issingleton() is used to decide whether to store object information in memory.

4, 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

Spring singleton, prototype and FactoryBean function implementation class relationship, as shown in the figure

  1. The whole class diagram above shows whether the instantiation of the added Bean is a singleton or a prototype pattern and the implementation of FactoryBean.
  2. In fact, the whole implementation process is not complicated, but in the existing AbstractAutowireCapableBeanFactory class and the inherited abstract class
    AbstractBeanFactory.
  3. However, this time, we add a layer of FactoryBeanRegistrySupport to the DefaultSingletonBeanRegistry class inherited by AbstractBeanFactory. This class mainly handles the support operation of FactoryBean registration in the Spring framework.
  1. Scope definition and xml parsing of Bean
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
}
  1. singleton and prototype are two newly added attribute information in the BeanDefinition class, which are used to The scope of the Bean object parsed in XML is filled into the attribute.
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        for (int i = 0; i < childNodes.getLength(); i++) {
            // Judgment element
            if (!(childNodes.item(i) instanceof Element)) continue;
            // Judgment object
            if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
            // Parse label
            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");
            // Get Class to get the name in the Class
            Class<?> clazz = Class.forName(className);
            // Priority ID > name
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }
            // Define Bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethodName);
            if (StrUtil.isNotEmpty(beanScope)) {
                beanDefinition.setScope(beanScope);
            }
            // ...
            // Register BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }
}
  • In the parsing XML processing class XmlBeanDefinitionReader, the scope parsing in the Bean object configuration is added, and the attribute information is filled into the Bean definition. beanDefinition.setScope(beanScope)
  1. Judge singleton and prototype patterns when creating and modifying objects
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);
            // Populate the Bean with properties
            applyPropertyValues(beanName, bean, beanDefinition);
            // Execute the initialization method of Bean and the pre and post processing methods of BeanPostProcessor
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }
        // Register the Bean object that implements the DisposableBean interface
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
        // Judge SCOPE_SINGLETON,SCOPE_PROTOTYPE
        if (beanDefinition.isSingleton()) {
            addSingleton(beanName, bean);
        }
        return bean;
    }
    protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
        // Bean s of non Singleton type do not execute the destroy method
        if (!beanDefinition.isSingleton()) return
        if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
            registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
        }
    }
    // ...  Other functions
}
  • The difference between the Singleton mode and the prototype mode is whether to store it in memory. If it is the prototype mode, it will not be stored in memory. The object will be recreated every time it is obtained. In addition, beans of non Singleton type do not need to execute the destruction method.
  • Therefore, there are two modifications to the code here. One is to determine whether to add addsingleton (bean name, bean) in createBean;, The other is registerdisposablebeaninifnecessary. Destroy the judgment if (!beanDefinition.isSingleton()) return; in the registration;.
  1. Define FactoryBean interface
public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

FactoryBean needs to provide three methods to obtain the object, object type, and whether it is a singleton object. If it is a singleton object, it will still be put into memory.

  1. Implement a FactoryBean registration service
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);
        }
    }

}
  1. The FactoryBeanRegistrySupport class mainly deals with the registration of FactoryBean objects. The reason why it is placed in such a separate class is to ensure that different domain modules are only responsible for the functions they need to complete, so as to avoid the expansion of classes that are difficult to maintain.
  2. Similarly, the cache operation factoryBeanObjectCache is also defined here to store singleton objects and avoid repeated creation. In daily use, they are basically created singleton objects
  3. doGetObjectFromFactoryBean is a specific method to obtain FactoryBean#getObject(). Because there are both cache processing and object acquisition, getObjectFromFactoryBean is additionally provided for logical packaging. The operation mode of this part is not very similar to your daily business logic development. Get data from Redis. If it is empty, get it from the database and write it to Redis
  1. Extending AbstractBeanFactory to create object logic
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) {
            // If it is a FactoryBean, you need to call 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;
    }
        
    // ...
}
  1. First, modify the DefaultSingletonBeanRegistry originally inherited by AbstractBeanFactory to inherit FactoryBeanRegistrySupport. Because the ability to create FactoryBean objects needs to be extended, it is like cutting a segment from a chain service to handle additional services, and then linking the chain.
  2. The newly added function here is to call (T) getObjectForBeanInstance(sharedInstance, name) to get the
    Operation of FactoryBean.
  3. Make specific instanceof judgment in the getObjectForBeanInstance method. In addition, it will get objects from the cache of FactoryBean. If they do not exist, call FactoryBeanRegistrySupport#getObjectFromFactoryBean to perform specific operations.

4, Testing

  1. Prepare in advance
public interface IUserDao {
    String queryUserName(String uId);
}
  1. In this chapter, we delete UserDao and define an IUserDao interface. This is to perform proxy operation of a custom object through FactoryBean.
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
}
  1. In UserService, an original UserDao attribute is newly modified to IUserDao. Later, we will inject a proxy object into this attribute.
  1. Defining FactoryBean objects
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", "Little brother Fu");
            hashMap.put("10002", "Eight cups of water");
            hashMap.put("10003", "A Mao");
            return "You're represented " + 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;
    }
}
  1. This is the name of the proxy class ProxyBeanFactory that implements the interface FactoryBean. It mainly simulates the original functions of UserDao, similar to the proxy operation in the MyBatis framework.
  2. getObject() provides a proxy object of InvocationHandler. When there is a method call, the function of the proxy object will be executed.
  1. 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="tencent"/>
        <property name="location" value="Shenzhen"/>
        <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 replaced with a proxy class

  1. Unit test (single case & prototype)
@Test
public void test_prototype() {
    // 1. Initialize BeanFactory
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    applicationContext.registerShutdownHook();   
    // 2. Get Bean object and call method
    UserService userService01 = applicationContext.getBean("userService", UserService.class);
    UserService userService02 = applicationContext.getBean("userService", UserService.class);
    // 3. Configure scope="prototype/singleton"
    System.out.println(userService01);
    System.out.println(userService02);    
    // 4. Print hex hash
    System.out.println(userService01 + " Hex hash:" + Integer.toHexString(userService01.hashCode()));
    System.out.println(ClassLayout.parseInstance(userService01).toPrintable());
}
  1. In spring In the XML configuration file, scope = "prototype" is set, so that each obtained object should be a new object.
  2. Here we judge whether the object is a hash value of a class object that will see the print, so we print the hexadecimal hash.
  1. Unit test (proxy object)
@Test
public void test_factory_bean() {
    // 1. Initialize BeanFactory
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    applicationContext.registerShutdownHook(); 

    // 2. Call proxy method
    UserService userService = applicationContext.getBean("userService", UserService.class);
    System.out.println("Test results:" + userService.queryUserInfo());
}

There are not many different calls to FactoryBean, because all the differences have been made by spring XML configuration is in. Of course, you can call spring directly XML configured object

  1. From the test results, our proxy class ProxyBeanFactory has perfectly replaced the function of UserDao.
  2. Although it seems that this implementation is not complex, even a little simple. But this is the design of a little core content, which solves the problem of interactive links with other frameworks that need to be combined with Spring. If you are interested in this kind of content, you can also read little Foucault's middleware design and development

5, Summary

  1. In the whole development process of Spring framework, the expansion of various function interface classes in the early stage seems to be expanding, but it is not so difficult to improve the functions in the later stage. On the contrary, after in-depth understanding, you will feel that the supplement of functions is relatively simple. You only need to expand the corresponding service implementation within the scope of the encountered domain.
  2. When you carefully read about the implementation of FactoryBean and the use of testing process, and then need to use FactoryBean to develop corresponding components in the future, you will be very clear about how it creates its own complex Bean objects and when it initializes and invokes them. You can also quickly troubleshoot, locate and solve problems.
  3. If you feel that these classes, interfaces, implementations and inheritance are very complex in the process of learning, your brain can't react for a while and a half. Then your best way is to draw these class diagrams, sort out the implementation structure, and see what each class is doing. Look, you can only know and learn by doing!

Topics: Spring