造一个方形的轮子4--依赖注入

造一个方形轮子文章目录:造一个方形的轮子

01、先把车正过来

在上一篇《造一个方形的轮子3--控制反转》的最后提出了一个问题,如果同一个接口有一个以上的实现类,那么在初始化的时候,实现相同接口的BeanObject对象,后一个放入Map容器中时会把前边的覆盖掉,这样肯定有问题,简单处理一下,在BeanObject类中添加一个next指针引用,把他改造成一个可以支持链表的形式。

BeanObject.java 添加:

    //......上略
    /**
     * 使用链表处理同一接口多个实现类的情况
     */
    private BeanObject next;

    public BeanObject getNext() {
        return next;
    }

    public void setNext(BeanObject next) {
        this.next = next;
    }
    //......下略

在原来的BeansInitUtil.loadClass()方法里修改以下代码:

                //......上略
                beanObject.setPackages(clzz.getPackage().toString());
                Object obj = clzz.newInstance();
                beanObject.setObject(obj);
                // 按接口设置bean
                for (Class aClass : beanObject.getInterfacs()) {
                    BeanObject tmp = map.get(aClass.getName());
                    if(tmp != null){
                        beanObject.setNext(tmp);
                    }
                    map.put(aClass.getName(), beanObject);
                }
                // 按类设置bean
                map.put(beanObject.getClassName(), beanObject);
                //......下略


可以看到在按接口全限定名保存容器时,由原来的直接保存改为先查询,如果原来有值(tmp不为空)说明已有其它实现类,被初始化,那么将当然beanObject的next指针指向原来保存的值就可以了(为什么要将当前beanObject的next指针指向已存在的tmp对象,而不是将tmp的next指针指向beanObject? )。

02、依赖注入准备

到目前为止bean的实例化已经完成了,现在把依赖注入的过程大概理一下:

1、找到每个bean中添加的注入注解的字段

2、按类型、接口、名称去容器中查找Bean实例

3、如果找到了且为一,则使用反射设值

4、如果没有找到或者找到多个没法确定使用哪一个,则抛出异常(启动失败)

好,先来实现第一步,改造一下BeanObject 把字段也保存起来,好在DI阶段使用,注解的这里就不自己实现了直接使用Java提供的@Resource, @Resource注解有一个name的字段,我们把他定义为指定的bean名字。

BeanObject.java 添加字段属性:

    //.....
    /**
     * 字段数组
     */
    private Field[] fields;
    public Field[] getFields() {
        return fields;
    }
    public void setFields(Field[] fields) {
        this.fields = fields;
    }

03、处理依赖注入

在BeansInitUtil.java中添加initDI()方法负责处理依赖注入:

    //....上略
    /**
     * 处理关系依赖
     * @param map Bean容器
     */
    private static void initDI(Map<String, BeanObject> map){
        // 循环所有Bean处理依赖
        for(Map.Entry entry : map.entrySet()){
            BeanObject beanObject = (BeanObject)entry.getValue();
            // 先判断是否有Resource注解
            for (Field field : beanObject.getFields()) {
                if(filterFieldAnnotation(field.getAnnotations())){
                    String name = getResourceName(field.getAnnotations());
                    BeanObject bean = null;
                    // 有指定bean名字按指定去取
                    if(name != null && !name.equals("")){
                        bean = map.get(firstToLowerCase(name));
                    } else {
                        // 没有指定按接口(如果有的话)或类型去取
                        Class fieldClass = field.getType();
                        bean = map.get(fieldClass.getName());
                        // 如果有next说明是有多个实现的接口,则要判断名字
                        if(bean != null && bean.getNext() != null){
                            String fieldName = field.getName();
                            while(bean != null){
                                if(firstToLowerCase(bean.getSimpleName()).equals(fieldName)){
                                    break;
                                }
                                bean = bean.getNext();
                            }
                            if(bean == null){
                                // 多于两个匹配的bean异常
                                log.error("无法确定的Bean依赖,field:{}, 存在多个依赖!", beanObject.getClassName()+"."+fieldName);
                                throw new SquareBeanInitException("无法确定的Bean依赖,存在多个依赖!");
                            }
                        }
                    }
                    if(bean == null){
                        // 找不到依赖bean异常
                        log.error("无法找到Bean依赖,field:{}", beanObject.getClassName()+"."+field.getName());
                        throw new SquareBeanInitException("无法找到Bean依赖");
                    }
                    // 注入依赖
                    try {
                        field.setAccessible(true);
                        field.set(beanObject.getObject(), bean.getObject());
                    } catch (IllegalAccessException e) {
                        log.error("Bean注入失败,field:{}", beanObject.getClassName()+"."+field.getName(), e);
                        throw new SquareBeanInitException("Bean注入失败");
                    }
                }
            }
        }
    }
    /** * 判断字段上加的注解是否需要做注入 */
    private static boolean filterFieldAnnotation(Annotation[] annotations){
        boolean b = false;
        for (Annotation annotation : annotations) {
            b = annotation instanceof Resource;
        }
        return b;
    }
    /**  获取注入注解上指定的Bean名字 */
    private static String getResourceName(Annotation[] annotations){
        String name = null;
        for (Annotation annotation : annotations) {
            name = ((Resource)annotation).name();
        }
        return name;
    }

    /** 首字段转大写*/
    public static String firstToUpperCase(String str){
        if(str == null || str.equals("")){
            return str;
        }
        char f = str.charAt(0);
        str = str.substring(1);
        if(f>'Z'){
            f = (char)(f-32);
        }
        return f+str;
    }

    /** 首字段转大写*/
    public static String firstToLowerCase(String str){
        if(str == null || str.equals("")){
            return str;
        }
        char f = str.charAt(0);
        str = str.substring(1);
        if(f<'a'){
            f = (char)(f+32);
        }
        return f+str;
    }
    //....下略

看注释就可以了,基本就是按照上边说的逻辑一步一步做的,这里主要的关注点在,同一个接口有多个实现的时候,怎么去确认要注入的bean,上边代码里使用的是如果有多个实例则按需要注入字段的名字去匹配。

在BeansInitUtil.java的init()方法中添加调用initDI()。

    //...上略
    public static Map<String, BeanObject> init(Class clazz){
        Map<String, BeanObject> beansMap = new HashMap<>();
        String path = clazz.getResource("").getPath();
        log.info("===bean init path:{}", path);
        File root = new File(path);
        // 处理控制反转
        initFile(root, beansMap);
        // 处理依赖注入
        initDI(beansMap);
        return beansMap;
    }
    //...下略

04、验证依赖注入

为了验证效果我们先在com.jisuye.service包下添加几个类

ClassDI.java

package com.jisuye.service;
import com.jisuye.annotations.Service;
@Service
public class ClassDi {
    public String exe(String name){
        return "Class DI "+name;
    }
}

Def.java

package com.jisuye.service;
public interface Def {
    String exe(String name);
}

添加两个Def接口实现类

DefImpl.java

package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class DefImpl implements Def {
    @Override
    public String exe(String name) {
        return "Interface DI... "+name;
    }
}

Def2Impl.java

package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class Def2Impl implements Def {
    @Override
    public String exe(String name) {
        return "def2 "+name;
    }
}

修改AbcImpl.java(上一篇创建的类) 添加需要注入的引入

package com.jisuye.service.impl;
//import ...
@Service
public class AbcImpl implements Abc {
    // 名字对不上会报异常
    @Resource
    private Def defImpl;
    // 名字对不上可以使用注解中指定bean名字的方式
    @Resource(name = "def2Impl")
    private Def defByName;
    // 注入Class类实例
    @Resource
    private ClassDi classDi;
    @Override
    public int test(String name) {
        System.out.println(defImpl.exe(name));
        System.out.println(defByName.exe(name));
        System.out.println(classDi.exe(name));
        return 0;
    }
}

在SquareApplication.run()方法中添加调用查看注入是否成功

 public static void run(Class clzz, String[] args) {
        try {
            long startTime = System.currentTimeMillis();
            classesPathUtil = new ClassesPathUtil(clzz);
            // 加载配置
            loadYaml(classesPathUtil.getProjectPath());
            // 初始化参数
            setArgs(args);
            // 输出banner
            printBanner(classesPathUtil.getProjectPath());
            Map<String, BeanObject> map = BeansInitUtil.init(clzz);
            log.info("beans size is:{}", map.size());
            //查看bean是否注入成功
            Abc abc = (Abc)(map.get("com.jisuye.service.Abc").getObject());
            abc.test("ixx");
            tomcat = new Tomcat();
  //....下略

查看控制台输出:

20:42:07.749 [main] INFO com.jisuye.core.SquareApplication - beans size is:10
Interface DI... ixx
def2 ixx
Class DI ixx
....

ok 可以看到对应的属性都已经注入成功

05、翻车时间

重新思考一下,上一篇只考虑了同一接口的不同实现类,会造成BeanObject覆盖问题,但其实按类型简称(不带包名)以及按注解上设置的Bean名字去初始化Bean的时候都会有覆盖问题,比如不同包下的相同的类,或者在注解上设置了相同Bean名字的类,想一下要怎么处理呢?

本篇代码地址: https://github.com/iuv/square/tree/square4

本文作者: ixx
本文链接: http://jianpage.com/2019/06/29/square4
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

推荐阅读更多精彩内容