美文网首页
简单实现Spring的IoC功能

简单实现Spring的IoC功能

作者: 坠尘_ae94 | 来源:发表于2020-11-19 20:27 被阅读0次

简化版Spring

Github上有个Spring功能简化的项目:https://github.com/code4craft/tiny-spring ,简单包含了Spring的IoC以及AOP功能。本文简单介绍了它的IoC实现,AOP以后有空再说。

tiny-spring是为了学习Spring的而开发的,可以认为是一个Spring的精简版。Spring的代码很多,层次复杂,阅读起来费劲。我尝试从使用功能的角度出发,参考Spring的实现,一步一步构建,最终完成一个精简版的Spring。有人把程序员与画家做比较,画家有门基本功叫临摹,tiny-spring可以算是一个程序的临摹版本-从自己的需求出发,进行程序设计,同时对著名项目进行参考。

下面开始进入tiny-spring的项目构建。

从XML中读取Bean信息

从XML中读取信息自然需要XML文件的输入流InputStream,因此我们对InputStream流的获取进行约束:

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

增加一个子接口ResourceLoader继承Resource接口:

public interface ResourceLoader extends Resource {
    Resource getResource(String location);
}

最后,使用实现类获取指定位置文件的InputStream

public class UrlResourceLoader implements ResourceLoader {

    private URL url;

    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

    @Override
    public Resource getResource(String location) {
        URL resource = this.getClass().getClassLoader().getResource(location);
        url = resource;
        return this;
    }
}

测试看看:

    @Test
    public void test() throws IOException {
        ResourceLoader resourceLoader = new UrlResourceLoader();
        Resource resource = resourceLoader.getResource("tinyioc.xml");
        InputStream inputStream = resource.getInputStream();
        Assert.assertNotNull(inputStream);
    }

可以看到,获取指定XML文件的输入流可以通过测试。

文件读取没有问题后,剩下的就是解析以及保存读取的内容了。

BeanDefinition

Spring使用BeanDefinition接口定义作为Bean实例和XML配置文件之间的中间层。

为简便起见,tiny-springBeanDefinition定义为一个类:

public class BeanDefinition {

    private Object bean;

    private Class beanClass;

    private String beanClassName;

    private PropertyValues propertyValues = new PropertyValues();

    public BeanDefinition() {
    }

    //Getter、Setter
}

PropertyValues属性则封装了从XML中读取的属性:

public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
    public PropertyValues() {
    }
    public void addPropertyValue(PropertyValue pv) {
        //TODO:这里可以对于重复propertyName进行判断,直接用list没法做到
        this.propertyValueList.add(pv);
    }
    public List<PropertyValue> getPropertyValues() {
        return this.propertyValueList;
    }
}

其实就是key-value:

public class PropertyValue {
    private final String name;
    private final Object value;
    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
    //Getter、Setter
}

上面说过如何从指定文件获取InputStream

UrlResourceLoader只是提供了获取InputStream的方法,保存从XML中读取转化出的BeanDefinition还需要一个类进行保存,tiny-spring为从XML中加载BeanDefinition安排了接口BeanDefinitionReader

public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}

从指定文件中读取配置信息。它的实现类AbstractBeanDefinitionReader则扩充了该接口的功能,同时保存从XML中读取出的BeanDefinition

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }
    //Getter、Setter
}

这里的registry我觉得可以与后续BeanFactory的beanDefinitionMap整合在一起,不需要将BeanDefinition的集合存两个地方。

registry属性将BeanDefinitionname作为key, BeanDefinition自身作为value保存在Map中。

最后,由XmlBeanDefinitionReader实现具体的方法:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        doLoadBeanDefinitions(inputStream);
    }
}

接下来就很明显了,doLoadBeanDefinitions需要对XML文件做详细的处理,包括将属性名以及属性值构建为PropertyValues、获取一个Bean对另一个Bean的引用等。

说到这Bean引用,当前的工具类不能解决一个Bean对另一个Bean的引用,所以这里还需要引入一个代表类引用的类BeanReference

public class BeanReference {
    private String name;
    private Object bean;
    //Getter、Setter
}

这样,tiny-spring就可以完成XML配置到BeanDefinition的转换了。

BeanFactory

众所周知,BeanFactory是Spring的核心容器接口。如果是借助BeanFactory操作Bean的话,用的最多的一个方法就是getBean()

比如:

HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");

即从Spring容器中获取名为"helloWorldService"的Bean实例。

如果想要模仿Spring的话,一个BeanFactory接口必不可少,它代表着Spring对外暴露的接口:

public interface BeanFactory {
    Object getBean(String name) throws Exception;
}

有了BeanFactory自然需要实现类,比如保存实例化后的Bean集合。考虑到对于XML的处理已经由XmlBeanDefinitionReader等类完成,并已经生成了BeanDefinition集合。因此,BeanFactory及其实现类的重点在于完成PropertyValue的赋值以及Bean的实例化,包括处理依赖注入等。

getBean()方法无疑是最重要的一个方法,不妨借助抽象类AbstractBeanFactory对其做一个初步的实现:

    @Override
    public Object getBean(String name) throws Exception {
        // 尝试从缓存中获取BeanDefinition(BeanDefinition中维护了Bean的实例)
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition == null) {
            throw new IllegalArgumentException("No bean named " + name + " is defined");
        }
        Object bean = beanDefinition.getBean();
        if (bean == null) {
            // 实例化Bean
            bean = doCreateBean(beanDefinition);
            beanDefinition.setBean(bean);
        }
        return bean;
    }

实例化Bean方法:

    protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
        Object bean = createBeanInstance(beanDefinition);
        beanDefinition.setBean(bean);
        applyPropertyValues(bean, beanDefinition);
        return bean;
    }
    protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
        return beanDefinition.getBeanClass().newInstance();
    }
    /**
     * 空方法,留给子类实现
     * @param bean
     * @param beanDefinition
     * @throws Exception
     */
    protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
    }

applyPropertyValues类似于模板设计模式,即确定步骤的执行顺序,但某些步骤的具体实现还未知。

来一个子类AutowireCapableBeanFactory实现该方法:

public class AutowireCapableBeanFactory extends AbstractBeanFactory {

    protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
        for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
            Object value = propertyValue.getValue();
            // 处理Bean引用
            if (value instanceof BeanReference) {
                BeanReference beanReference = (BeanReference) value;
                value = getBean(beanReference.getName());
            }

            try {
                // 执行类的set方法
                Method declaredMethod = bean.getClass().getDeclaredMethod(
                        "set" + propertyValue.getName().substring(0, 1).toUpperCase()
                                + propertyValue.getName().substring(1), value.getClass());
                declaredMethod.setAccessible(true);

                declaredMethod.invoke(bean, value);
            } catch (NoSuchMethodException e) {
                Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
                declaredField.setAccessible(true);
                declaredField.set(bean, value);
            }
        }
    }
}

测试

        // 1.读取配置
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
        xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

        // 2.初始化BeanFactory并注册bean
        AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
            beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
        }

        // 3.获取bean
        HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
        Assert.assertNotNull(helloWorldService);

没问题,详细代码可以访问tiny-spring项目获取。

小结

Spring源码实在庞大,新手很难一下子适应,尤其是它错综复杂的继承以及实现关系,看多了头疼。

tiny-spring构建了简单的IoC以及AOP实现,像咱这样的新手看看还挺不错。

参考

  1. tiny-spring
  2. tiny-spring 分析
  3. 模板方法模式(模板方法设计模式)详解

相关文章

网友评论

      本文标题:简单实现Spring的IoC功能

      本文链接:https://www.haomeiwen.com/subject/hynliktx.html