美文网首页
实现DI框架

实现DI框架

作者: 御风_2fd9 | 来源:发表于2021-02-27 18:18 被阅读0次

    本文基于极客时间《设计模式之美》中的自定义DI框架而来,原文提供了大体设计,本文将示例进行了补全和完善。
    本文的补充点:

    1. 使用Dom4j,实现了配置文件的解析,并转化成BeanDefinition对象集合
    2. 解决了反射根据有参构造函数创建对象的类型问题

    DI,Dependency Injection,即依赖注入,spring使用这种方式创建对象放到一个容器中,业务代码需要使用,直接从容器中拿。
    分离了对象的创建和使用,使代码解藕,业务代码更加清晰。
    底层使用工厂模式,封装了复杂对象的创建过程,有些对象的创建过程繁琐,有些对象需要依赖其它对象,这些创建过程可以通通交给spring即可。

    一、实现思路

    三步走:

    1. 定义配置文件。
      容器不只是创建某类对象,不能在代码中写死,所以将需要被spring创建的对象放到配置文件中,也是隔离变化的体现。
    2. 解析配置文件。
      采用Dom4j技术,解析xml配置文件。
      定义BeanDefinition对象,用于接收解析后的对象信息。
    3. 将解析得到的对象信息,通过反射生成对象,并放到容器中。

    二、具体实现

    1. 代码结构


      image.png

    pom依赖

     <dependencies>
            <dependency>
                <groupId>dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
            </dependency>
        </dependencies>
    
    1. 具体代码
      2.1 需要被创建的对象
    package org.example.model;
    import lombok.Data;
    
    @Data
    public class RateLimiter {
        private RedisCounter redisCounter;
        public RateLimiter(RedisCounter redisCounter) {
            this.redisCounter = redisCounter;
        }
        public void test() {
            System.out.println("Hello World!");
        }
    }
    
    package org.example.model;
    
    import lombok.Data;
    
    @Data
    public class RedisCounter {
        private String ipAddress;
        private int port;
    
        public RedisCounter(String ipAddress, int port) {
            this.ipAddress = ipAddress;
            this.port = port;
        }
    }
    

    2.2 配置文件applicationContext.xml

    <beans>
        <bean id="rateLimiter" class="org.example.model.RateLimiter">
            <constructor-arg ref="redisCounter"/>
        </bean>
    
        <bean id="redisCounter" class="org.example.model.RedisCounter" scope="prototype">
            <constructor-arg type="String" value="127.0.0.1"/>
            <constructor-arg type="int" value="1234"/>
        </bean>
    </beans>
    

    2.3 上下文对象

    package org.example.context;
    
    public interface ApplicationContext {
        Object getBean(String beanId);
    }
    
    
    package org.example.context;
    
    import org.example.BeanDefinition;
    import org.example.factory.BeansFactory;
    import org.example.parser.BeanConfigParser;
    import org.example.parser.XmlBeanConfigParser;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    
    /**
     * 1.组装beansFactory和beanConfigParser
     * 2.串联执行流程,读取配置文件,通过beanConfigParser解析到beanDefinitions
     * 3.beansFactory根据beanDefinitions加载对象
     */
    public class ClassPathXmlApplicationContext implements ApplicationContext {
        private BeansFactory beansFactory;
        private BeanConfigParser beanConfigParser;
    
        public ClassPathXmlApplicationContext(String configLocation) {
            this.beansFactory = new BeansFactory();
            this.beanConfigParser = new XmlBeanConfigParser();
            loadBeanDefinitions(configLocation);
        }
    
        /**
         * 配置文件解析,并将配置信息读取转化到BeanDefinition集合
         * @param configLocation
         */
        private void loadBeanDefinitions(String configLocation) {
            InputStream in = null;
            try {
                in = this.getClass().getResourceAsStream("/"+configLocation);
                if (in==null){
                    throw new RuntimeException("找不到配置文件:"+configLocation);
                }
                List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
                beansFactory.addBeanDefinitions(beanDefinitions);
            } catch (RuntimeException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (in!=null) {
                        in.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public Object getBean(String beanId) {
            return beansFactory.getBean(beanId);
        }
    }
    

    2.4 工厂类

    package org.example.factory;
    
    import org.example.BeanDefinition;
    
    import java.lang.reflect.InvocationTargetException;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 1.提供获取Bean对象的方法
     * 2.根据beanDefinitions创建对象
     */
    public class BeansFactory {
        private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
        private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
    
        public Object getBean(String beanId){
            BeanDefinition beanDefinition = beanDefinitions.get(beanId);
            if (beanDefinition==null){
                throw new RuntimeException("bean is not defined,"+beanId);
            }
            return createBean(beanDefinition);
        }
    
        public void addBeanDefinitions(List<BeanDefinition> beanDefinitions) {
            for (BeanDefinition beanDefinition : beanDefinitions) {
                this.beanDefinitions.putIfAbsent(beanDefinition.getId(),beanDefinition);
            }
    
            for (BeanDefinition beanDefinition : beanDefinitions) {
                if (!beanDefinition.isLazyInit()&&beanDefinition.isSingleton()){
                    createBean(beanDefinition);
                }
            }
    
        }
    
        private Object createBean(BeanDefinition beanDefinition) {
            if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {
                return singletonObjects.get(beanDefinition.getId());
            }
    
            Object bean = null;
            try {
                Class beanClass = Class.forName(beanDefinition.getClassName());
                List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();
                if (args.isEmpty()) {
                    bean = beanClass.newInstance();
                } else {
                    Class[] argClasses = new Class[args.size()];
                    Object[] argObjects = new Object[args.size()];
                    for (int i = 0; i < args.size(); ++i) {
                        BeanDefinition.ConstructorArg arg = args.get(i);
                        if (!arg.getIsRef()) {
                            argClasses[i] = arg.getType();
                            argObjects[i] = arg.getValue();
                        } else {
                            BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getRef());
                            if (refBeanDefinition == null) {
                                throw new RuntimeException("Bean is not defined: " + arg.getValue());
                            }
                            argClasses[i] = Class.forName(refBeanDefinition.getClassName());
                            argObjects[i] = createBean(refBeanDefinition);
                        }
                    }
                    //反射,根据有参构造方法创建对象
                    bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
                }
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
    
            if (bean != null && beanDefinition.isSingleton()) {
                singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
                return singletonObjects.get(beanDefinition.getId());
            }
            return bean;
        }
    }
    
    

    2.5 配置文件解析类

    package org.example.parser;
    import org.example.BeanDefinition;
    import java.io.InputStream;
    import java.util.List;
    
    public interface BeanConfigParser {
        List<BeanDefinition> parse(InputStream inputStream);
    
        List<BeanDefinition> parser(String content);
    }
    
    package org.example.parser;
    
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    import org.example.BeanDefinition;
    import org.example.enums.ClassEnums;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 配置文件内容解析成BeanDefinition集合对象
     */
    public class XmlBeanConfigParser implements BeanConfigParser {
    
        @Override
        public List<BeanDefinition> parse(InputStream inputStream) {
            List<BeanDefinition> beanDefinitions = new ArrayList<>();
    
            SAXReader saxReader = new SAXReader();
            Document document = null;
            try {
                document = saxReader.read(inputStream);
                //获取bean节点集合
                Element root = document.getRootElement();
                List<Element> elements = root.selectNodes("//bean");
                if (elements == null || elements.size() == 0) {
                    throw new RuntimeException("无bean标签");
                }
    
                for (Element element : elements) {
                    BeanDefinition beanDefinition = new BeanDefinition();
                    //获取id和class属性值
                    String id = element.attributeValue("id");
                    String className = element.attributeValue("class");
                    System.out.println("id:" + id + ",clazz:" + className);
                    List<Element> childElements = element.elements("constructor-arg");
                    List<BeanDefinition.ConstructorArg> constructorArgs = new ArrayList<>();
                    for (Element childElement : childElements) {
                        BeanDefinition.ConstructorArg constructorArg = new BeanDefinition.ConstructorArg();
                        String ref = childElement.attributeValue("ref");
                        String typeClassName = childElement.attributeValue("type");
                        Class typeClazz = null;
                        if (typeClassName != null) {
                            typeClazz = ClassEnums.getClass(typeClassName);
                        }
    
                        constructorArg.setRef(ref);
                        constructorArg.setType(typeClazz);
    
                        if (childElement.attributeValue("value") != null) {
                            if ("int".equals(typeClassName)) {//坑点!如果是int类型,需要将类型转换,转化为Integer类型
                                constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
                            } else {
                                constructorArg.setValue(childElement.attributeValue("value"));
                            }
                        }
    
                        constructorArgs.add(constructorArg);
                    }
                    if (element.attributeValue("scope")!=null){
                        beanDefinition.setScope(element.attributeValue("scope"));
                    }
                    beanDefinition.setId(id);
                    beanDefinition.setClassName(className);
                    beanDefinition.setConstructorArgs(constructorArgs);
                    beanDefinitions.add(beanDefinition);
                }
    
            } catch (DocumentException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(beanDefinitions);
            return beanDefinitions;
        }
        @Override
        public List<BeanDefinition> parser(String content) {
            return null;
        }
    }
    
    

    2.6 接收配置文件解析数据的对象

    package org.example;
    
    import lombok.Data;
    
    import java.util.List;
    
    /**
     * 配置文件中bean节点的映射对象
     */
    @Data
    public class BeanDefinition {
        private String id;
        private String className;
        private List<ConstructorArg> constructorArgs;
        private String scope = "singleton";
        private boolean lazyInit = false;
    
        public boolean isSingleton() {
            return "singleton".equals(this.scope);
        }
    
        @Data
        public static class ConstructorArg {
            private String ref;
            private Class type;
            private Object value;
    
            public boolean getIsRef() {
                return ref != null || "".equals(ref);
            }
        }
    }
    
    

    2.7 基本数据类型的字节码枚举类。

    package org.example.enums;
    
    public enum ClassEnums {
        STRING("String", String.class),
        INT("int", Integer.TYPE);
    
        private String type;
        private Class typeClazz;
    
        ClassEnums(String type, Class typeClazz) {
            this.type = type;
            this.typeClazz = typeClazz;
        }
    
        public static Class getClass(String type) {
            for (ClassEnums classEnum : ClassEnums.values()) {
                if (classEnum.type.equalsIgnoreCase(type)){
                    return classEnum.typeClazz;
                }
            }
            throw new RuntimeException("ClassEnums没有该类型");
        }
    }
    

    三、说明

    1. 为什么要定义ClassEnums类?
      因为在BeansFactory中,使用反射,对有参构造方法实例化对象,getConstructor方法需要接受参数的字节码数组。
      我们需要得到配置文件中的“int”、“String”对应的字节码对象。
      不能使用Class.forName("String")的方式获取,所以就采用了枚举的方式
     //反射,根据有参构造方法创建对象
                    bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
    

    Class源码

     @CallerSensitive
        public Constructor<T> getConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            return getConstructor0(parameterTypes, Member.PUBLIC);
        }
    

    使用反射,根据有参数构造方法创建对象,示例

    package org.example.model;
    import lombok.Data;
    import java.lang.reflect.InvocationTargetException;
    
    @Data
    public class RedisCounter {
        private String ipAddress;
        private int port;
    
        public RedisCounter(String ipAddress, int port) {
            this.ipAddress = ipAddress;
            this.port = port;
        }
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
            Class clazz = RedisCounter.class;
            Object[] values = {"123","8888"};
            Class[] classes = {String.class,Integer.TYPE};//{String.class,Integer.type};
            RedisCounter redisCounter = (RedisCounter) clazz.getConstructor(classes).newInstance(values);
            System.out.println(redisCounter);
    
            System.out.println(classes);
        }
    }
    

    四、踩坑

    RedisCounter对象中port属性是int类型,所以需要在使用方式创建对象时,将从配置文件获取的值转化成Integer类型。
    说明:xml配置文件的属性值必须带双引号,所以这步转化是必不可少的。

    if (childElement.attributeValue("value") != null) {
                           if ("int".equals(typeClassName)) {//坑点!如果是int类型,需要将类型转换,转化为Integer类型
                               constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
                           } else {
                               constructorArg.setValue(childElement.attributeValue("value"));
                           }
                       }
    

    五、收获

    1. 掌握了dom4j
    2. 学会了工厂模式设计思想
    3. 深刻理解了DI框架设计原理
    4. 加深了对面向对象设计思想的理解
      比如,使用BeanDefinition接收配置解析信息,ClassPathXmlApplicationContext组装对象,串联流程,让类的职责、代码流程更加清晰

    参考:
    https://time.geekbang.org/column/article/198614
    https://blog.csdn.net/zhouyingge1104/article/details/83069886

    相关文章

      网友评论

          本文标题:实现DI框架

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