實現(xiàn)DI框架

本文基于極客時間《設(shè)計模式之美》中的自定義DI框架而來,原文提供了大體設(shè)計,本文將示例進行了補全和完善。
本文的補充點:

  1. 使用Dom4j,實現(xiàn)了配置文件的解析,并轉(zhuǎn)化成BeanDefinition對象集合
  2. 解決了反射根據(jù)有參構(gòu)造函數(shù)創(chuàng)建對象的類型問題

DI,Dependency Injection,即依賴注入,spring使用這種方式創(chuàng)建對象放到一個容器中,業(yè)務(wù)代碼需要使用,直接從容器中拿。
分離了對象的創(chuàng)建和使用,使代碼解藕,業(yè)務(wù)代碼更加清晰。
底層使用工廠模式,封裝了復(fù)雜對象的創(chuàng)建過程,有些對象的創(chuàng)建過程繁瑣,有些對象需要依賴其它對象,這些創(chuàng)建過程可以通通交給spring即可。

一、實現(xiàn)思路

三步走:

  1. 定義配置文件。
    容器不只是創(chuàng)建某類對象,不能在代碼中寫死,所以將需要被spring創(chuàng)建的對象放到配置文件中,也是隔離變化的體現(xiàn)。
  2. 解析配置文件。
    采用Dom4j技術(shù),解析xml配置文件。
    定義BeanDefinition對象,用于接收解析后的對象信息。
  3. 將解析得到的對象信息,通過反射生成對象,并放到容器中。

二、具體實現(xiàn)

  1. 代碼結(jié)構(gòu)


    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 需要被創(chuàng)建的對象
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.串聯(lián)執(zhí)行流程,讀取配置文件,通過beanConfigParser解析到beanDefinitions
 * 3.beansFactory根據(jù)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);
    }

    /**
     * 配置文件解析,并將配置信息讀取轉(zhuǎn)化到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.根據(jù)beanDefinitions創(chuàng)建對象
 */
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);
                    }
                }
                //反射,根據(jù)有參構(gòu)造方法創(chuàng)建對象
                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;

/**
 * 配置文件內(nèi)容解析成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節(jié)點集合
            Element root = document.getRootElement();
            List<Element> elements = root.selectNodes("http://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類型,需要將類型轉(zhuǎn)換,轉(zhuǎn)化為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 接收配置文件解析數(shù)據(jù)的對象

package org.example;

import lombok.Data;

import java.util.List;

/**
 * 配置文件中bean節(jié)點的映射對象
 */
@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 基本數(shù)據(jù)類型的字節(jié)碼枚舉類。

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中,使用反射,對有參構(gòu)造方法實例化對象,getConstructor方法需要接受參數(shù)的字節(jié)碼數(shù)組。
    我們需要得到配置文件中的“int”、“String”對應(yīng)的字節(jié)碼對象。
    不能使用Class.forName("String")的方式獲取,所以就采用了枚舉的方式
 //反射,根據(jù)有參構(gòu)造方法創(chuàng)建對象
                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);
    }

使用反射,根據(jù)有參數(shù)構(gòu)造方法創(chuàng)建對象,示例

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類型,所以需要在使用方式創(chuàng)建對象時,將從配置文件獲取的值轉(zhuǎn)化成Integer類型。
說明:xml配置文件的屬性值必須帶雙引號,所以這步轉(zhuǎn)化是必不可少的。

if (childElement.attributeValue("value") != null) {
                       if ("int".equals(typeClassName)) {//坑點!如果是int類型,需要將類型轉(zhuǎn)換,轉(zhuǎn)化為Integer類型
                           constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
                       } else {
                           constructorArg.setValue(childElement.attributeValue("value"));
                       }
                   }

五、收獲

  1. 掌握了dom4j
  2. 學(xué)會了工廠模式設(shè)計思想
  3. 深刻理解了DI框架設(shè)計原理
  4. 加深了對面向?qū)ο笤O(shè)計思想的理解
    比如,使用BeanDefinition接收配置解析信息,ClassPathXmlApplicationContext組裝對象,串聯(lián)流程,讓類的職責、代碼流程更加清晰

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容