关于java:如何实现一个简易版的-Spring-如何实现-Setter-注入

21次阅读

共计 9986 个字符,预计需要花费 25 分钟才能阅读完成。

前言

之前在 上篇 提到过会实现一个简易版的 IoCAOP,明天它终于来了。。。置信对于应用 Java 开发语言的敌人们都应用过或者据说过 Spring 这个开发框架,绝大部分的企业级开发中都离不开它,通过 官网 能够理解到其生态十分宏大,针对不同方面的开发提供了一些解决方案,能够说 Spring 框架的诞生是对 Java 开发人员的一大福利,自 2004 年公布以来,Spring 为了解决一些企业开发中的痛点先后引入了很多的个性和性能,其中最重要的就是咱们常常听到的 IoCAOP 个性,因为波及到的常识和细节比拟多,会分为几篇文章来介绍,明天这篇(也是第一篇)咱们来看看如何实现基于 XML 配置形式的 Setter 注入

<!–more–>

准备常识

既然是通过 XML 配置文件的形式,首先第一件事就是要读取 XML 文件而后转换为咱们须要的数据结构,解析 XML 文件有但不限于这些形式(JDOM、XOM、dom4j),这里应用的是简略易上手的 dom4j,所你得对其基础知识有一些简略理解,其实都是一些很简略的办法根底应用而已,第二个就是你要有一些 Spring 框架的应用教训,这里实现的简易版实质上是对 Spring 的一个精简后的外围局部的简略实现,是的,没错,你只须要有了这些根底准备常识就能够了。

根底数据结构形象

在开始编码实现前先要做一些简略的构思和设计,首先在 Spring 中把一个被其治理的对象称之为 Bean,而后其它的操作都是围绕这个 Bean 来开展设计的,所以为了能在程序中对立并且标准的示意一个 Bean 的定义,于是第一个接口 BeanDefinition 就进去了,本次须要的一些根本信息蕴含 Bean 的名称、所属类名称、是否单例、作用域等,如下所示:

当初 BeanDefinition 有了,接下来就是要依据这个 BeanDefinition 去创立出对应的 Bean 实例了,很显然这须要一个 Factory 工厂接口去实现这个创立的工作,这个创立 Bean 的接口命名为 BeanFactory,其提供依据不同条件去创立绝对应的 Bean 实例性能(比方 beanId),然而创立的前提是须要先注册这个 BeanDefinition,而后依据肯定条件再从中去获取 BeanDefinition,依据 繁多职责 准则,这个性能应该由一个新的接口去实现,次要是做注册和获取 BeanDefinition 的工作,故将其命名为 BeanDefinitionRegistry,咱们须要的 BeanDefinition 要从哪里获取呢?很显然咱们是基于 XML 配置的形式,当然是从 XML 配置文件中获取到的,同样依据繁多职责准则,也须要一个类去实现这个事件,将其命名为 XMLBeanDefinitionReader,这部分的整体构造如下所示:

接下来面临的一个问题就是,像 XML 这种配置文件资源要如何示意呢,这些配置对于程序来说是一种资源,能够对立形象为 Resource,而后提供一个返回资源对应流(InputStream)对象接口,这种资源能够从我的项目中获取、本地文件获取甚至是从近程获取,它们都是一种 Resource,构造如下:

最初就是要一个提供去组合调用下面的那些类去实现 XML 配置文件解析为 BeanDefinition 并注入到容器中了的性能,负责这程序上下文的职责,将其命名为 ApplicationContext,这里同样也能够依据 Resource 的类型分为多种不同的类,比方:FileSystmXmlApplicationContextClassPathXmlApplicationContext 等,这些外部都有一个将配置文件转换为 Resource 的过程,能够应用 模板办法 形象出一个公共父类抽象类,如下所示:

总结以上剖析后果,得出初步类图设计如下:

最终要实现 Setter 注入这个指标,能够将其合成为以下两个步骤:

  1. XML 配置文件中的 <bean> 标签解析为 BeanDefinition 并注入到容器中
  2. 实现 Setter 注入

上面咱们分为这两个局部来别离讲述如何实现。

配置文件解析

假如有如下内容的配置文件 applicationcontext-config1.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="orderService" class="cn.mghio.service.version1.OrderService" />

</beans>

最终须要解析出一个 idorderService 类型为 cn.mghio.service.version1.OrderServiceBeanDefinition,翻译成测试类的话也就是须要让如下测试类能够运行通过:

/**
 * @author mghio
 */
public class BeanFactoryTest {

    private Resource resource;
    private DefaultBeanFactory beanFactory;
    private XmlBeanDefinitionReader reader;

    @BeforeEach
    public void beforeEach() {resource = new ClassPathResource("applicationcontext-config1.xml");
        beanFactory = new DefaultBeanFactory();
        reader = new XmlBeanDefinitionReader(beanFactory);
    }

    @Test
    public void testGetBeanFromXmlFile() {reader.loadBeanDefinition(resource);
        BeanDefinition bd = beanFactory.getBeanDefinition("orderService");

        assertEquals("cn.mghio.service.version1.OrderService", bd.getBeanClassNam());
        OrderService orderService = (OrderService) beanFactory.getBean("orderService");
        assertNotNull(orderService);
    }

    @Test
    public void testGetBeanFromXmlFileWithInvalidBeanId() {assertThrows(BeanCreationException.class, () -> beanFactory.getBean("notExistsBeanId"));
    }

    @Test
    public void testGetFromXmlFilWithFileNotExists() {resource = new ClassPathResource("notExists.xml");
        assertThrows(BeanDefinitionException.class, () -> reader.loadBeanDefinition(resource));
    }

}

能够看到这外面的要害就是如何去实现 XmlBeanDefinitionReader 类的 loadBeanDefinition 从配置中加载和注入 BeanDefinition,思考剖析后不然发现这里次要是两步,第一步是解析 XML 配置转换为 BeanDefinition,这就须要上文提到的 dom4j 提供的能力了,第二步将解析进去的 BeanDefinition 注入到容器中,通过组合应用 BeanDefinitionRegistry 接口提供注册 BeanDefinition 的能力来实现。读取 XML 配置的类 XmlBeanDefinitionReader 的代码实现很快就能够写进去了,该类局部代码如下所示:

/**
 * @author mghio
 */
public class XmlBeanDefinitionReader {

    private static final String BEAN_ID_ATTRIBUTE = "id";
    private static final String BEAN_CLASS_ATTRIBUTE = "class";

    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {this.registry = registry;}

    @SuppressWarnings("unchecked")
    public void loadBeanDefinition(Resource resource) {try (InputStream is = resource.getInputStream()) {SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(is);
            Element root = document.getRootElement();  // <beans>
            Iterator<Element> iterator = root.elementIterator();
            while (iterator.hasNext()) {Element element = iterator.next();
                String beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);
                String beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);
                BeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);
                this.registry.registerBeanDefinition(beanId, bd);
            }
        } catch (DocumentException | IOException e) {throw new BeanDefinitionException("IOException parsing XML document:" + configurationFile, e);
        }
    }
}

而后当调用 BeanFactorygetBean 办法时就能够依据 Bean 的全限定名创立一个实例进去了 (PS:临时不思考实例缓存),办法实现次要代码如下:

public Object getBean(String beanId) {BeanDefinition bd = getBeanDefinition(beanId);
    if (null == bd) {throw new BeanCreationException("BeanDefinition does not exists, beanId:" + beanId);
    }
    ClassLoader classLoader = this.getClassLoader();
    String beanClassName = bd.getBeanClassNam();
    try {Class<?> clazz = classLoader.loadClass(beanClassName);
        return clazz.newInstance();} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {throw new BeanCreationException("Created bean for" + beanClassName + "fail.", e);
    }
}

到这里配置文件解析方面的工作已实现,接下来看看要如何实现 Setter 注入。

如何实现 Setter 注入

首先实现基于 XML 配置文件的 Setter 注入实质上也是解析 XML 配置文件,而后再调用对象属性的 setXXX 办法将配置的值设置进去,配置文件 applicationcontext-config2.xml 如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="stockDao" class="cn.mghio.dao.version2.StockDao"/>

    <bean id="tradeDao" class="cn.mghio.dao.version2.TradeDao"/>

    <bean id="orderService" class="cn.mghio.service.version2.OrderService">
        <property name="stockDao" ref="stockDao"/>
        <property name="tradeDao" ref="tradeDao"/>
        <property name="num" value="2"/>
        <property name="owner" value="mghio"/>
        <property name="orderTime" value="2020-11-24 18:42:32"/>
    </bean>

</beans>

咱们之前应用了 BeanDefinition 去形象了 <bean> 标签,这里面临的第一个问题就是要如何去表白配置文件中的 <property> 标签,其中 ref 属性示意一个 beanIdvalue 属性示意一个值(值类型为:IntegerStringDate 等)。察看后能够发现,<property> 标签实质上是一个 K-V 格局的数据(name 作为 Keyrefvalue 作为 Value),将这个类命名为 PropertyValue,很显著一个 BeanDefinition 会有多个 PropertyValue,构造如下:

这里的 value 有两种不同的类型,一种是示意 Beanid 值,运行时会解析为一个 Bean 的援用,将其命名为 RuntimeBeanReference,还有一种是 String 类型,运行时会解析为不同的类型,将其命名为 TypeStringValue。第二个问题就是要如何将一个类型转换为另一个类型呢?比方将下面配置中的字符串 2 转换为整型的 2、字符串 2020-11-24 18:42:32 转换为日期,这类通用的问题前辈们曾经开发好了类库解决了,这里咱们应用 commons-beanutils 库提供的 BeanUtils.copyProperty(final Object bean, final String name, final Object value) 办法即可。而后只需在之前 XmlBeanDefinitionReader 类的 loadBeanDefinition 办法解析 XML 配置文件的时解析 <bean> 标签下的 <property> 标签并设置到 BeanDefinitionpropertyValues 属性中;DefaultBeanFactory 中的 getBean 办法分为实例化 Bean 和读取向实例化实现的 Bean 应用 Setter 注入配置文件中配置属性对应的值。XmlBeanDefinitionReaderloadBeanDefinition() 办法代码批改为:

public void loadBeanDefinition(Resource resource) {try (InputStream is = resource.getInputStream()) {SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(is);
        Element root = document.getRootElement();  // <beans>
        Iterator<Element> iterator = root.elementIterator();
        while (iterator.hasNext()) {Element element = iterator.next();
            String beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);
            String beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);
            BeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);
            parsePropertyElementValue(element, bd);  // parse <property>
            this.registry.registerBeanDefinition(beanId, bd);
        }
    } catch (DocumentException | IOException e) {throw new BeanDefinitionException("IOException parsing XML document:" + resource, e);
    }
}

private void parsePropertyElementValue(Element element, BeanDefinition bd) {Iterator<Element> iterator = element.elementIterator(PROPERTY_ATTRIBUTE);
    while (iterator.hasNext()) {Element propertyElement = iterator.next();
        String propertyName = propertyElement.attributeValue(NAME_ATTRIBUTE);
        if (!StringUtils.hasText(propertyName)) {return;}

        Object value = parsePropertyElementValue(propertyElement, propertyName);
        PropertyValue propertyValue = new PropertyValue(propertyName, value);
        bd.getPropertyValues().add(propertyValue);
    }

}

private Object parsePropertyElementValue(Element propertyElement, String propertyName) {String elementName = (propertyName != null) ?
            "<property> element for property'" + propertyName + "'" :"<constructor-arg> element";

    boolean hasRefAttribute = propertyElement.attribute(REF_ATTRIBUTE) != null;
    boolean hasValueAttribute = propertyElement.attribute(VALUE_ATTRIBUTE) != null;

    if (hasRefAttribute) {String refName = propertyElement.attributeValue(REF_ATTRIBUTE);
        RuntimeBeanReference ref = new RuntimeBeanReference(refName);
        return ref;
    } else if (hasValueAttribute) {String value = propertyElement.attributeValue(VALUE_ATTRIBUTE);
        TypedStringValue valueHolder = new TypedStringValue(value);
        return valueHolder;
    } else {throw new RuntimeException(elementName + "must specify a ref or value");
    }
}

DefaultBeanFactorygetBean 办法也减少 Bean 属性注入操作,局部代码如下:

public Object getBean(String beanId) {BeanDefinition bd = getBeanDefinition(beanId);
    // 1. instantiate bean
    Object bean = instantiateBean(bd);
    // 2. populate bean
    populateBean(bd, bean);
    return bean;
}

private Object instantiateBean(BeanDefinition bd) {ClassLoader classLoader = this.getClassLoader();
    String beanClassName = bd.getBeanClassName();
    try {Class<?> clazz = classLoader.loadClass(beanClassName);
        return clazz.newInstance();} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {throw new BeanCreationException("Created bean for" + beanClassName + "fail.", e);
    }
}

private void populateBean(BeanDefinition bd, Object bean) {List<PropertyValue> propertyValues = bd.getPropertyValues();
    if (propertyValues == null || propertyValues.isEmpty()) {return;}

    BeanDefinitionResolver resolver = new BeanDefinitionResolver(this);
    SimpleTypeConverted converter = new SimpleTypeConverted();
    try {for (PropertyValue propertyValue : propertyValues) {String propertyName = propertyValue.getName();
            Object originalValue = propertyValue.getValue();
            Object resolvedValue = resolver.resolveValueIfNecessary(originalValue);

            BeanUtils.copyProperty(bean, propertyName, resolvedValue);
        }
    } catch (Exception e) {throw new BeanCreationException("Failed to obtain BeanInfo for class [" + bd.getBeanClassName() + "]");
    }
}

至此,简略的 Setter 注入性能已实现。

总结

本文简略概述了基于 XML 配置文件形式的 Setter 注入简略实现过程,整体实现 Setter 注入的思路就是先设计一个数据结构去表白 XML 配置文件中的标签数据(比方下面的 PropertyValue),而后再解析配置文件填充数据并利用这个数据结构实现一些性能(比方 Setter 注入等 )。感兴趣的敌人能够到这里 mghio-spring 查看残缺代码。

正文完
 0