美文网首页
简单实现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