实现DI框架

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

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

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

一、实现思路

三步走:

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

二、具体实现

  1. 代码结构


    image.png

pom依赖

 
        
            dom4j
            dom4j
            1.1
        
        
            org.projectlombok
            lombok
            1.18.16
        
    
  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


    
        
    

    
        
        
    

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 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 beanDefinitions = new ConcurrentHashMap<>();
    private ConcurrentHashMap 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 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 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 parse(InputStream inputStream);

    List 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 parse(InputStream inputStream) {
        List beanDefinitions = new ArrayList<>();

        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(inputStream);
            //获取bean节点集合
            Element root = document.getRootElement();
            List 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 childElements = element.elements("constructor-arg");
                List 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 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 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 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框架)