手写spring第五章-简化用户操作,基于xml完成bean容器初始化

前言

通过上一篇文章,我们完成bean容器的初始化,但是我们可以观察到完成一次容器初始化步骤十分繁琐,要是仅仅注入几个bean还好。一旦出现需要注入几十个或者上百个bean的场景,使得用户需通过代码去一个个添加bean的方式,我认为这种方案简直是在侮辱人。

本次需求

对此,我们要让我们的框架更加人性化一点,我们本篇文章希望做到能够通过简单的配置的方式,就能让用户将需要的bean注入到我们的容器中。

设计思路

扩展点分析

对于本次框架扩展,我们需要完成将各种配置文件信息转化为bean定义对象,再完成bean的注入。
所以,我们需要确定是否需要建立新的类对象,以及代码扩展方案。从需求的分析来看,我们需要完成以下步骤:

	1. 拿到配置文件
	2. 获取配置文件信息
	3. 存到bean定义类中
	4. 注入容器中

可以看出我们第四步是完成了,我们只需完成前三步即可。就前三步而言,我们考虑到配置文件多种多样,所以仅仅一个对象是不够,我们需要定一个接口Resource确定一下每个资源对象都需要做到行为,对于Resource对象而言,文件对象都需要返回文件信息,大家文件按格式可能多种多样,我们需要进行统一形式,才能规范操作,所以我们认为文件流是最通用的返回格式,最终确定Resource类需要有一个方法InputStream getInputStream() throws IOException;
既然我们确定了每种配置文件都会返回文件流,那么我们就可以进行各种各样的资源加载了,因为我们将所有的资源都定义为Resource,那么我们资源加载就可以定义一个接口进行统一规范,这个类我们就命名为ResourceLoader,并确定资源加载的行为,这时候我们考虑到无论加载何种资源,我都需要传给资源加载器一个路径,那么我们就可以去确定这个资源加载器的统一加载行为Resource getResource(String location);最终再用DefaultResourceLoader完成不同文件的加载即可。
完成了资源加载,我们就需要完成资源信息的读取,以及将信息存到bean定义类对象中,最后再注入到容器中。我们我们需要定义一个bean定义加载器,这个bean定义对象加载会将资源加载器中的资源信息转化为bean定义对象再存到容器中。由于代码量的原因不方便表述,笔者就以注释的形式阐述bean定义加载器接口定义

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;

    /**
     *
     * 以下两个接口是处于扩展需要,
     * 后续bean定义可能会通过文件路径或者多个资源对象才能获取到bean定义信息
     * 所以就编写这两个接口定义
     *
     */


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

    void loadBeanDefinitions(String location) throws BeansException;
}

完成bean定义加载器接口定义后,我们需要一个抽象类AbstractBeanDefinitionReader非接口功能外的注册Bean组件填充,即所有bean定义加载器通用的组件信息初始化,然后将具体功能实现给具体实现类处理,这就是spring框架设计的思想精华

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

在这里插入图片描述

类图

在这里插入图片描述

代码结构

├─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

代码示例

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;

    /**
     *
     * 以下两个接口是处于扩展需要,
     * 后续bean定义可能会通过文件路径或者多个资源对象才能获取到bean定义信息
     * 所以就编写这两个接口定义
     *
     */


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

    }
}

测试

resource下建立spring.xml

<?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>

测试代码

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




    }
}

总结

可以看出规范的项目设计,通过大量单一职责原则的接口解耦配合聚合关联关系组合对象,保证了需求扩展无需改动原有的代码逻辑,这就是spring框架设计的精华。

参考文献

UML基础(附绘制教程)
《Spring 手撸专栏》第 6 章:气吞山河,设计与实现资源加载器,从Spring.xml解析和注册Bean对象

猜你喜欢

转载自blog.csdn.net/shark_chili3007/article/details/120234542