Handwritten spring Chapter 5 - simplify user operations and complete bean container initialization based on xml

preface

Through the previous article, we completed the initialization of the bean container, but we can observe that the steps of completing one container initialization are very cumbersome. It's better to inject only a few beans. Once there is a scenario where dozens or hundreds of beans need to be injected, so that users need to add beans one by one through code, I think this scheme is insulting.

This demand

In this regard, we want to make our framework more user-friendly. This article hopes to enable users to inject the required bean s into our container through simple configuration.

Design ideas

Extension point analysis

For this framework extension, we need to complete the transformation of various configuration file information into bean definition objects, and then complete bean injection.
Therefore, we need to determine whether we need to create new class objects and code extension schemes. From the perspective of demand analysis, we need to complete the following steps:

	1. Get the configuration file
	2. Get profile information
	3. Save to bean In the definition class
	4. Inject into the container

It can be seen that our fourth step is completed. We only need to complete the first three steps. In terms of the first three steps, considering the variety of configuration files, only one object is not enough. We need to define an interface Resource to make sure that each Resource object needs to do the behavior. For Resource objects, file objects need to return file information. Everyone's files may be in various formats. We need to unify the form, In order to standardize the operation, we think that the file stream is the most common return format. Finally, it is determined that the Resource class needs a method InputStream getInputStream() throws IOException;
Now that we have determined that each configuration file will return a file stream, we can load various resources. Because we define all resources as resources, we can define an interface for uniform specification. This class is named ResourceLoader and determine the behavior of Resource loading, At this time, we consider that no matter what kind of resources are loaded, I need to pass a path to the Resource loader, so we can determine the unified loading behavior of the Resource loader Resource getResource(String location); Finally, use DefaultResourceLoader to load different files.
After the resource loading is completed, we need to read the resource information, store the information in the bean definition class object, and finally inject it into the container. We need to define a bean definition loader. Loading the bean definition object will convert the resource information in the resource loader into a bean definition object and then save it to the container. Due to the amount of code, it is not convenient to express. The author expounds the bean definition loader interface definition in the form of comments

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.core.io.Resource;
import cn.shark.springframework.beans.factory.core.io.ResourceLoader;

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    void loadBeanDefinitions(Resource resource) throws BeansException;

    /**
     *
     * The following two interfaces are required for expansion,
     * Subsequent bean definitions may obtain bean definition information through file paths or multiple resource objects
     * So write these two interface definitions
     *
     */


    void loadBeanDefinitions(Resource... resource) throws BeansException;

    void loadBeanDefinitions(String location) throws BeansException;
}

After completing the bean definition of the loader interface definition, we need an abstract class AbstractBeanDefinitionReader to register the Bean component outside the non interface function. That is, all bean defines initialization of the common component information of the loader, and then implements the specific function to the concrete implementation of class management, which is the essence of the spring framework design.

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry,new DefaultResourceLoader());
    }

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }


    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

Class diagram

Code structure

├─src
│  ├─main
│  │  ├─java
│  │  │  └─cn
│  │  │      └─shark
│  │  │          └─springframework
│  │  │              └─beans
│  │  │                  └─factory
│  │  │                      ├─config
│  │  │                      ├─core
│  │  │                      │  └─io
│  │  │                      ├─support
│  │  │                      ├─util
│  │  │                      └─xml
│  │  └─resources
│  └─test
│      ├─java
│      │  └─cn
│      │      └─shark
│      │          └─springframework
│      │              └─bean
│      └─resources
└─target
    ├─classes
    │  ├─cn
    │  │  └─shark
    │  │      └─springframework
    │  │          └─beans
    │  │              └─factory
    │  │                  ├─config
    │  │                  ├─core
    │  │                  │  └─io
    │  │                  ├─support
    │  │                  ├─util
    │  │                  └─xml
    │  └─META-INF
    ├─generated-sources
    │  └─annotations
    ├─generated-test-sources
    │  └─test-annotations
    └─test-classes
        ├─cn
        │  └─shark
        │      └─springframework
        │          └─bean
        └─META-INF

Code example

Resource

package cn.shark.springframework.beans.factory.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface Resource {
    InputStream getInputStream() throws IOException;
}

Resource

package cn.shark.springframework.beans.factory.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface Resource {
    InputStream getInputStream() throws IOException;
}

ClassPathResource

package cn.shark.springframework.beans.factory.core.io;


import cn.hutool.core.lang.Assert;
import cn.shark.springframework.beans.factory.util.ClassUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ClassPathResource implements Resource {

    private final String path;

    private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path,"Path must not be null");
        this.path = path;
        this.classLoader =  (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is = classLoader.getResourceAsStream(path);

        if (is == null) {
            throw new FileNotFoundException(
                    this.path + " cannot be opened because it does not exist");
        }

        return is;
    }
}

FileSystemResource

package cn.shark.springframework.beans.factory.core.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileSystemResource implements Resource {

    private final String path;

    private final File file;

    public FileSystemResource(String path, File file) {
        this.path = path;
        this.file = file;
    }

    public FileSystemResource(String path) {
        this.path = path;
        this.file = new File(path);
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

    public String getPath() {
        return path;
    }
}

UrlResource

package cn.shark.springframework.beans.factory.core.io;

import cn.hutool.core.lang.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

public class UrlResource implements Resource {


    private final URL url;

    public UrlResource(URL url) {
        Assert.notNull(url, "url must be not null");
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection con = this.url.openConnection();
        try {
            return con.getInputStream();
        } catch (IOException ex) {
            if (con instanceof HttpURLConnection) {
                ((HttpURLConnection) con).disconnect();
            }
            throw ex;
        }

    }
}

ResourceLoader

package cn.shark.springframework.beans.factory.core.io;

public interface ResourceLoader {

    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String location);
}

DefaultResourceLoader

package cn.shark.springframework.beans.factory.core.io;

import cn.hutool.core.lang.Assert;

import java.net.MalformedURLException;
import java.net.URL;

public class DefaultResourceLoader implements ResourceLoader {
    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must be not null");
        if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
        } else {
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            } catch (MalformedURLException e) {
                return new FileSystemResource(location);
            }
        }
    }
}

BeanDefinitionReader

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.factory.core.io.Resource;
import cn.shark.springframework.beans.factory.core.io.ResourceLoader;

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    void loadBeanDefinitions(Resource resource) throws BeansException;

    /**
     *
     * The following two interfaces are required for expansion,
     * Subsequent bean definitions may obtain bean definition information through file paths or multiple resource objects
     * So write these two interface definitions
     *
     */


    void loadBeanDefinitions(Resource... resource) throws BeansException;

    void loadBeanDefinitions(String location) throws BeansException;
}

AbstractBeanDefinitionReader

package cn.shark.springframework.beans.factory.support;

import cn.shark.springframework.beans.factory.core.io.DefaultResourceLoader;
import cn.shark.springframework.beans.factory.core.io.ResourceLoader;

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry,new DefaultResourceLoader());
    }

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }


    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

XmlBeanDefinitionReader

package cn.shark.springframework.beans.factory.xml;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.shark.springframework.beans.BeansException;
import cn.shark.springframework.beans.PropertyValue;
import cn.shark.springframework.beans.factory.config.BeanDefinition;
import cn.shark.springframework.beans.factory.config.BeanReference;
import cn.shark.springframework.beans.factory.core.io.Resource;
import cn.shark.springframework.beans.factory.core.io.ResourceLoader;
import cn.shark.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import cn.shark.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.sun.deploy.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.io.InputStream;

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try {
            try (InputStream inputStream = resource.getInputStream()) {
                doLoadBeanDefinitions(inputStream);
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new BeansException("IOException parsing XML document from " + resource, e);
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }


    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        Document doc = XmlUtil.readXML(inputStream);
        Element root = doc.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

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

            Class<?> clazz = Class.forName(className);

            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            BeanDefinition beanDefinition = new BeanDefinition(clazz);

            for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
                if (!(bean.getChildNodes().item(j) instanceof Element)) {
                    continue;
                }

                if (!("property".equals((bean.getChildNodes().item(j).getNodeName())))) {
                    continue;
                }


                Element property = (Element) bean.getChildNodes().item(j);
                String attrName = property.getAttribute("name");
                String attrValue = property.getAttribute("value");
                String attrRef = property.getAttribute("ref");

                Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;

                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);


            }
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
            }

            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }

    }
}

test

Create spring.xml under resource

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

    <bean id="userDao" class="cn.shark.springframework.bean.UserDao"/>

    <bean id="userService" class="cn.shark.springframework.bean.UserService">
        <property name="uId" value="10001"/>
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

Test code

package cn.shark.springframework;

import cn.shark.springframework.bean.UserDao;
import cn.shark.springframework.bean.UserService;
import cn.shark.springframework.beans.PropertyValue;
import cn.shark.springframework.beans.PropertyValues;
import cn.shark.springframework.beans.factory.config.BeanDefinition;
import cn.shark.springframework.beans.factory.config.BeanReference;
import cn.shark.springframework.beans.factory.support.DefaultListableBeanFactory;
import cn.shark.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.junit.Test;

public class ApiTest {

    @Test
    public void test() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));

        PropertyValues propertyValues = new PropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("uId", "1"));
        propertyValues.addPropertyValue(new PropertyValue("userDao", new BeanReference("userDao")));

        BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues);

        beanFactory.registerBeanDefinition("userService", beanDefinition);

        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo();
    }


    @Test
    public void test_xml() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        XmlBeanDefinitionReader beanDefinitionReader= new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");

        UserService userService= (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo();




    }
}

summary

It can be seen that standardized project design, through a large number of single duty principle interface decoupling and aggregation association relation combination object, guarantees the demand expansion without changing the original code logic, which is the essence of spring framework design.

reference

UML Foundation (with drawing tutorial)
Chapter 6 of Spring hand column: Swallow mountains and rivers, design and implement resource loader, parse and register Bean objects from Spring.xml

Keywords: Java Spring xml source code

Added by dotwebbie on Sat, 20 Nov 2021 07:25:28 +0200