davidFB

依赖注入

什么是依赖注入

使用一个会创建和查找依赖对象的容器,让它负责供给对象。

当a对象需要b对象时,不再是使用new创建,而是从容器中获取,对象与对象之间是松散耦合的关系,有利于功能复用。

依赖:应用程序依赖容器,需要的对象都从容器获取

注入:容器将对象注入到应用程序中

设计思路

  • 我们必须告诉容器:哪些类是由容器来创建的;哪些类是要从容器中获取的
    • 使用两个注解对类进行标记
  • 容器必须对所有类进行扫描,将标记过的类创建和注入
    • 扫描src文件夹下所有java为后缀的文件
    • 使用反射的方式查看类定义,构造对象
  • 一个能创建、获取对象的容器
    • 使用Map作为这个容器:Class类型为key,Object类型为value

代码实现

 注解定义

 

/**
 * 被标记的类需要由容器创建
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface FBean {

}

 

/**
 * 标记需要从容器获取的对象
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface FAutowired {

}

 

对所有类进行扫描

通过遍历当前项目的所有java文件,由类名(包名 + java文件名)获取class,使用一个List存放用注解标记过的class

 static List<Class> classList;

    public static void scanClass() throws ClassNotFoundException {
        File currentFile = new File("");
        // 当前项目的绝对路径
        String path = currentFile.getAbsolutePath();

        classList = new ArrayList<>();
        // 项目下src下的java文件
        File javaFile = new File(path + "/src/main/java");
        // 类所在的包
        File[] packageFiles = javaFile.listFiles();
        for (int i = 0; i < packageFiles.length; i++) {
            findClass(packageFiles[i], packageFiles[i].getName());
        }
    }

    /**
     * 递归打开文件夹,寻找java文件,没有文件夹时结束递归
     *
     * @param file      当前找的文件
     * @param className 类名称
     * @throws ClassNotFoundException
     */
    private static void findClass(File file, String className) throws ClassNotFoundException {

        if (file.isFile()) {
            // 将className最后的.java去掉
            int endIndex = className.lastIndexOf(".");
            String[] fileNames = file.getName().split("\\.");
            // 判断是否为java文件
            if ("java".equals(fileNames[1])) {
                // 反射获取类放入list中
                Class clazz = Class.forName(className.substring(0, endIndex));
                if (clazz.isAnnotationPresent(FBean.class)){
                    classList.add(clazz);
                }
            }
            return;
        }

        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            // 如果是package(文件夹),将文件名加入到类名称中,继续查看包里面的文件
            findClass(files[i], className + "." + files[i].getName());
        }
    }

 

使用反射构造容器里的对象

使用一个Map作为存储对象的容器,有注解标记的引用通过class属性获取容器里的对象

和上面扫描类的代码写在同一个工具类(IocUtils)中

    static final Map<Class, Object> objectMap = new HashMap<>();
    
    static {
        try {
            // 先扫描类获取class
            scanClass();

            for (int i = 0; i < classList.size(); i++) {
                // 对一个个class进行初始化
                constructClass(classList.get(i));
            }
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }

    public static Object constructClass(Class clazz) throws IllegalAccessException, InstantiationException {

        if (clazz.isInterface() || clazz.isAnnotation()) {
            return null;
        }

        if (objectMap.containsKey(clazz)) {
            return objectMap.get(clazz);
        }
        // 反射构造对象
        Object obj = clazz.newInstance();

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 需要容器注入其他对象,并且容器中没有,让容器继续构造对象
            if (field.isAnnotationPresent(FAutowired.class)) {
                if (!objectMap.containsKey(field.getType())) {
                    // 递归构造
                    constructClass(field.getType());
                }
                // 构造结束进行赋值
                field.setAccessible(true);
                field.set(obj, objectMap.get(field.getType()));
            }
        }
        // 每个对象构造完放进容器中
        objectMap.put(clazz, obj);

        return objectMap.get(clazz);
    }

 

  • 这里没有考虑使用接口的情况(因为太难了)
    • 可以使用一个接口跟实现类对应的Map集合,field为接口类型时,构造实现类返回

进行测试

要交给容器的实体类

@FBean
public class StudentDao {
    public void query(){
        System.out.println("StudentDao:query()");
    }
}
View Code
@FBean
public class TeacherDao {

    @FAutowired
    private StudentDao studentDao;

    public void query(){
        System.out.println("teacherDao:query()");
    }

    public StudentDao getStudentDao() {
        return studentDao;
    }

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
}
View Code

 

 

测试代码

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        StudentDao studentDao = (StudentDao) IocUtils.objectMap.get(StudentDao.class);

        TeacherDao teacherDao = (TeacherDao) IocUtils.objectMap.get(TeacherDao.class);

        if (teacherDao.getStudentDao() == studentDao) {
            System.out.println("对象复用,依赖注入成功");
        }
    }

测试结果

 

循环依赖

问题的产生

修改一下先前的StudentDao,让它引用TeacherDao,两个类就互相引用了

@FBean
public class StudentDao {
    @FAutowired
    private TeacherDao teacherDao;

    public TeacherDao getTeacherDao() {
        return teacherDao;
    }

    public void setTeacherDao(TeacherDao teacherDao) {
        this.teacherDao = teacherDao;
    }
    
    public void query(){
        System.out.println("StudentDao:query()");
    }
}
View Code

 

在原本构造对象的方法里面:

如果a类引用了容器的b类,a类在构造时,会让容器去构造b类,等b类构造完毕,a类才构造完毕。

当两个类互相引用时,a让容器构造b,b让容器构造a,最终造成死循环,可以使用上面的代码测试。

解决方案

原本只使用了一个Object_Map存储对象,现在再加上一个Complete_Map。

  • Object_Map是第一层,存储的是刚刚实例化的对象。
  • Complete_Map是第二层,存储属性填充完毕(引用的b、c、d、e全部构造好)的对象。

新的构造对象步骤

  1. a在实例化后,让容器去构造b,b实例化后,将b存入Object_Map中,继续a的构造流程。
  2. a从Object_Map拿到b,继续构造,最后存入Complete_Map。
  3. 轮到b构造时,使用Object_Map里面的b(也就是a已经引用的b),属性填充时,将Complete_Map里的a拿来用,构造完毕,存入Complete_Map

更新后的完整代码

Spring解决循环依赖的问题,为了AOP的实现使用了第三个Map。没有AOP的话,两个Map解决循环依赖的思路应该跟这差不太多。

主要修改constructClass这个方法,并且多了一个方法参数,所以调用方法的地方要改一下

package com.david.spring.ioc;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class IocUtils {
    // 用注解标记的class集合
    static final List<Class> classList = new ArrayList<>();
    // 构造好对象的map
    static Map<Class, Object> completeMap = new HashMap<>();
    // 为了解决循环依赖问题创建的map
    static final Map<Class, Object> objectMap = new HashMap<>();

    static {
        try {
            // 先扫描类获取class
            scanClass();
            for (int i = 0; i < classList.size(); i++) {
                // 对一个个class进行初始化,是需要完整构造
                constructClass(classList.get(i), true);
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param clazz 要构造的类
     * @param isInitial true表示类自己需要完整构造,false表示其他对象要求构造
     */
    public static void constructClass(Class clazz, boolean isInitial) throws InstantiationException, IllegalAccessException {

        if (clazz.isInterface() || clazz.isAnnotation() || completeMap.containsKey(clazz)) {
            return;
        }

        Object obj;
        if (!objectMap.containsKey(clazz)) {
            // 反射构造对象
            obj = clazz.newInstance();
            objectMap.put(clazz, obj);
        } else {

            obj = objectMap.get(clazz);
        }
        if (!isInitial) {
            return;
        }

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 需要容器注入其他对象,并且容器中没有,让容器继续构造对象
            if (field.isAnnotationPresent(FAutowired.class)) {

                field.setAccessible(true);

                if (completeMap.containsKey(field.getType())) {
                    field.set(obj, completeMap.get(field.getType()));
                    continue;

                } else if (!objectMap.containsKey(field.getType())) {
                    // 递归构造
                    constructClass(field.getType(), false);
                }
                // 构造结束进行赋值
                field.set(obj, objectMap.get(field.getType()));
            }
        }

        completeMap.put(clazz,obj);
    }

    /**
     * 扫描src文件夹下所有的以java为后缀的文件
     */
    public static void scanClass() throws ClassNotFoundException {
        File currentFile = new File("");
        // 当前项目的绝对路径
        String path = currentFile.getAbsolutePath();

        // 项目下src下的java文件
        File javaFile = new File(path + "/src/main/java");
        // 类所在的包
        File[] packageFiles = javaFile.listFiles();
        for (int i = 0; i < packageFiles.length; i++) {
            findClass(packageFiles[i], packageFiles[i].getName());
        }
    }

    /**
     * 递归打开文件夹,寻找java文件,没有文件夹时结束递归
     *
     * @param file      当前找的文件
     * @param className 类名称
     */
    private static void findClass(File file, String className) throws ClassNotFoundException {

        if (file.isFile()) {
            // 将className最后的.java去掉
            int endIndex = className.lastIndexOf(".");
            String[] fileNames = file.getName().split("\\.");
            // 判断是否为java文件
            if ("java".equals(fileNames[1])) {
                // 反射获取类放入list中
                Class clazz = Class.forName(className.substring(0, endIndex));
                if (clazz.isAnnotationPresent(FBean.class)) {
                    classList.add(clazz);
                }
            }
            return;
        }

        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            // 如果是package(文件夹),将文件名加入到类名称中,继续查看包里面的文件
            findClass(files[i], className + "." + files[i].getName());
        }
    }
}

 

分类:

技术点:

相关文章: