依赖注入

什么是依赖注入

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

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

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

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

设计思路

  • 我们必须告诉容器:哪些类是由容器来创建的;哪些类是要从容器中获取的

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

代码实现

注解定义

  1. /**
  2. * 被标记的类需要由容器创建
  3. */
  4. @Retention(value = RetentionPolicy.RUNTIME)
  5. @Target(value = ElementType.TYPE)
  6. public @interface FBean {
  7.  
  8. }
  1. /**
  2. * 标记需要从容器获取的对象
  3. */
  4. @Retention(value = RetentionPolicy.RUNTIME)
  5. @Target(value = ElementType.FIELD)
  6. public @interface FAutowired {
  7.  
  8. }

对所有类进行扫描

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

  1. static List<Class> classList;
  2.  
  3. public static void scanClass() throws ClassNotFoundException {
  4. File currentFile = new File("");
  5. // 当前项目的绝对路径
  6. String path = currentFile.getAbsolutePath();
  7.  
  8. classList = new ArrayList<>();
  9. // 项目下src下的java文件
  10. File javaFile = new File(path + "/src/main/java");
  11. // 类所在的包
  12. File[] packageFiles = javaFile.listFiles();
  13. for (int i = 0; i < packageFiles.length; i++) {
  14. findClass(packageFiles[i], packageFiles[i].getName());
  15. }
  16. }
  17.  
  18. /**
  19. * 递归打开文件夹,寻找java文件,没有文件夹时结束递归
  20. *
  21. * @param file 当前找的文件
  22. * @param className 类名称
  23. * @throws ClassNotFoundException
  24. */
  25. private static void findClass(File file, String className) throws ClassNotFoundException {
  26.  
  27. if (file.isFile()) {
  28. // 将className最后的.java去掉
  29. int endIndex = className.lastIndexOf(".");
  30. String[] fileNames = file.getName().split("\\.");
  31. // 判断是否为java文件
  32. if ("java".equals(fileNames[1])) {
  33. // 反射获取类放入list中
  34. Class clazz = Class.forName(className.substring(0, endIndex));
  35. if (clazz.isAnnotationPresent(FBean.class)){
  36. classList.add(clazz);
  37. }
  38. }
  39. return;
  40. }
  41.  
  42. File[] files = file.listFiles();
  43. for (int i = 0; i < files.length; i++) {
  44. // 如果是package(文件夹),将文件名加入到类名称中,继续查看包里面的文件
  45. findClass(files[i], className + "." + files[i].getName());
  46. }
  47. }

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

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

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

  1. static final Map<Class, Object> objectMap = new HashMap<>();
  2.  
  3. static {
  4. try {
  5. // 先扫描类获取class
  6. scanClass();
  7.  
  8. for (int i = 0; i < classList.size(); i++) {
  9. // 对一个个class进行初始化
  10. constructClass(classList.get(i));
  11. }
  12. } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16.  
  17. public static Object constructClass(Class clazz) throws IllegalAccessException, InstantiationException {
  18.  
  19. if (clazz.isInterface() || clazz.isAnnotation()) {
  20. return null;
  21. }
  22.  
  23. if (objectMap.containsKey(clazz)) {
  24. return objectMap.get(clazz);
  25. }
  26. // 反射构造对象
  27. Object obj = clazz.newInstance();
  28.  
  29. Field[] fields = clazz.getDeclaredFields();
  30. for (Field field : fields) {
  31. // 需要容器注入其他对象,并且容器中没有,让容器继续构造对象
  32. if (field.isAnnotationPresent(FAutowired.class)) {
  33. if (!objectMap.containsKey(field.getType())) {
  34. // 递归构造
  35. constructClass(field.getType());
  36. }
  37. // 构造结束进行赋值
  38. field.setAccessible(true);
  39. field.set(obj, objectMap.get(field.getType()));
  40. }
  41. }
  42. // 每个对象构造完放进容器中
  43. objectMap.put(clazz, obj);
  44.  
  45. return objectMap.get(clazz);
  46. }
  • 这里没有考虑使用接口的情况(因为太难了)

    • 可以使用一个接口跟实现类对应的Map集合,field为接口类型时,构造实现类返回

进行测试

要交给容器的实体类

  1. @FBean
  2. public class StudentDao {
  3. public void query(){
  4. System.out.println("StudentDao:query()");
  5. }
  6. }

  1. @FBean
  2. public class TeacherDao {
  3.  
  4. @FAutowired
  5. private StudentDao studentDao;
  6.  
  7. public void query(){
  8. System.out.println("teacherDao:query()");
  9. }
  10.  
  11. public StudentDao getStudentDao() {
  12. return studentDao;
  13. }
  14.  
  15. public void setStudentDao(StudentDao studentDao) {
  16. this.studentDao = studentDao;
  17. }
  18. }

测试代码

  1. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  2. StudentDao studentDao = (StudentDao) IocUtils.objectMap.get(StudentDao.class);
  3.  
  4. TeacherDao teacherDao = (TeacherDao) IocUtils.objectMap.get(TeacherDao.class);
  5.  
  6. if (teacherDao.getStudentDao() == studentDao) {
  7. System.out.println("对象复用,依赖注入成功");
  8. }
  9. }

测试结果

循环依赖

问题的产生

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

  1. @FBean
  2. public class StudentDao {
  3. @FAutowired
  4. private TeacherDao teacherDao;
  5.  
  6. public TeacherDao getTeacherDao() {
  7. return teacherDao;
  8. }
  9.  
  10. public void setTeacherDao(TeacherDao teacherDao) {
  11. this.teacherDao = teacherDao;
  12. }
  13.  
  14. public void query(){
  15. System.out.println("StudentDao:query()");
  16. }
  17. }

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

如果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这个方法,并且多了一个方法参数,所以调用方法的地方要改一下

  1. package com.david.spring.ioc;
  2.  
  3. import java.io.File;
  4. import java.lang.reflect.Field;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9.  
  10. public class IocUtils {
  11. // 用注解标记的class集合
  12. static final List<Class> classList = new ArrayList<>();
  13. // 构造好对象的map
  14. static Map<Class, Object> completeMap = new HashMap<>();
  15. // 为了解决循环依赖问题创建的map
  16. static final Map<Class, Object> objectMap = new HashMap<>();
  17.  
  18. static {
  19. try {
  20. // 先扫描类获取class
  21. scanClass();
  22. for (int i = 0; i < classList.size(); i++) {
  23. // 对一个个class进行初始化,是需要完整构造
  24. constructClass(classList.get(i), true);
  25. }
  26. } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30.  
  31. /**
  32. *
  33. * @param clazz 要构造的类
  34. * @param isInitial true表示类自己需要完整构造,false表示其他对象要求构造
  35. */
  36. public static void constructClass(Class clazz, boolean isInitial) throws InstantiationException, IllegalAccessException {
  37.  
  38. if (clazz.isInterface() || clazz.isAnnotation() || completeMap.containsKey(clazz)) {
  39. return;
  40. }
  41.  
  42. Object obj;
  43. if (!objectMap.containsKey(clazz)) {
  44. // 反射构造对象
  45. obj = clazz.newInstance();
  46. objectMap.put(clazz, obj);
  47. } else {
  48.  
  49. obj = objectMap.get(clazz);
  50. }
  51. if (!isInitial) {
  52. return;
  53. }
  54.  
  55. Field[] fields = clazz.getDeclaredFields();
  56. for (Field field : fields) {
  57. // 需要容器注入其他对象,并且容器中没有,让容器继续构造对象
  58. if (field.isAnnotationPresent(FAutowired.class)) {
  59.  
  60. field.setAccessible(true);
  61.  
  62. if (completeMap.containsKey(field.getType())) {
  63. field.set(obj, completeMap.get(field.getType()));
  64. continue;
  65.  
  66. } else if (!objectMap.containsKey(field.getType())) {
  67. // 递归构造
  68. constructClass(field.getType(), false);
  69. }
  70. // 构造结束进行赋值
  71. field.set(obj, objectMap.get(field.getType()));
  72. }
  73. }
  74.  
  75. completeMap.put(clazz,obj);
  76. }
  77.  
  78. /**
  79. * 扫描src文件夹下所有的以java为后缀的文件
  80. */
  81. public static void scanClass() throws ClassNotFoundException {
  82. File currentFile = new File("");
  83. // 当前项目的绝对路径
  84. String path = currentFile.getAbsolutePath();
  85.  
  86. // 项目下src下的java文件
  87. File javaFile = new File(path + "/src/main/java");
  88. // 类所在的包
  89. File[] packageFiles = javaFile.listFiles();
  90. for (int i = 0; i < packageFiles.length; i++) {
  91. findClass(packageFiles[i], packageFiles[i].getName());
  92. }
  93. }
  94.  
  95. /**
  96. * 递归打开文件夹,寻找java文件,没有文件夹时结束递归
  97. *
  98. * @param file 当前找的文件
  99. * @param className 类名称
  100. */
  101. private static void findClass(File file, String className) throws ClassNotFoundException {
  102.  
  103. if (file.isFile()) {
  104. // 将className最后的.java去掉
  105. int endIndex = className.lastIndexOf(".");
  106. String[] fileNames = file.getName().split("\\.");
  107. // 判断是否为java文件
  108. if ("java".equals(fileNames[1])) {
  109. // 反射获取类放入list中
  110. Class clazz = Class.forName(className.substring(0, endIndex));
  111. if (clazz.isAnnotationPresent(FBean.class)) {
  112. classList.add(clazz);
  113. }
  114. }
  115. return;
  116. }
  117.  
  118. File[] files = file.listFiles();
  119. for (int i = 0; i < files.length; i++) {
  120. // 如果是package(文件夹),将文件名加入到类名称中,继续查看包里面的文件
  121. findClass(files[i], className + "." + files[i].getName());
  122. }
  123. }
  124. }

Spring学习:简单实现一个依赖注入和循环依赖的解决的更多相关文章

  1. Spring源码学习笔记9——构造器注入及其循环依赖

    Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...

  2. Spring学习之第一个AOP程序

    IOC和AOP是Spring的两大基石,AOP(面向方面编程),也可称为面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP). 在进行 OOP 开发时,都是基于对 ...

  3. [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...

  4. 再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

    开心一刻 一天,侄子和我哥聊天,我坐在旁边听着 侄子:爸爸,你爱我妈妈吗? 哥:这话说的,不爱能有你吗? 侄子:确定有我不是因为荷尔蒙吗? 哥:因为什么荷尔蒙,因为爱情! 侄子:那我妈花点钱,你咋老说 ...

  5. Spring学习(二)三种方式的依赖注入

    1.前言 上一篇讲到第一个Spring项目的创建.以及bean的注入.当然.注入的方式一共有三种.本文将展开细说. 1.set注入:本质是通过set方法赋值 1.创建老师类和课程类 1.Course ...

  6. Spring学习之第一个hello world程序

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development a ...

  7. Spring学习笔记(3)——Bean的注入方式

    依赖注入 依赖注入支持属性注入.构造函数注入.工厂注入. 属性注入: 属性注入即通过setXxx()方法注入Bean的属性值或依赖对象 属性注入要求Bean提供一个默认的构造函数(无参构造函数),并为 ...

  8. Spring学习之第一个Spring MVC程序(IDEA开发环境)

    回顾Java平台上Web开发历程来看,从Servlet出现开始,到JSP繁盛一时,然后是Servlet+JSP时代,最后演化为现在Web开发框架盛行的时代.一般接触到一个新的Web框架,都会想问这个框 ...

  9. Spring.Net 简单实例-02(属性注入)

    说明:接续Spring.Net 简单实例-01(IOC) 话不多说看操作 1:为UserInfo添加属性 2: 修改App.config中代码 <?xml version="1.0&q ...

随机推荐

  1. LuoguB2147 求 f(x,n) 题解

    Content 求给定 \(x,n\),求 \(f(x,n)=\sqrt{n+\sqrt{(n-1)+\sqrt{(n-2)+\sqrt{\dots+2+\sqrt{1+x}}}}}\) 的值. So ...

  2. mpstat 查看多核CPU负载状态

    mpstat是Multiprocessor Statistics的缩写,是实时系统监控工具.其报告与CPU的一些统计信息,这些信息存放在/proc/stat文件中.在多CPUs系统里,其不但能查看所有 ...

  3. WinFrm中多线程操作窗体属性

    首先声明一个委托. delegate void SetTextCallback(string text); 然后再写一个事件. private void SetInfo(string text) { ...

  4. Vue-Router(一)

    Vue-Router(一) 简介 vue-router是Vuejs的官方推荐路由,让用 Vue.js 构建单页应用变得非常容易.目前Vue路由最新的版本是4.x版本. vue-router是基于路由和 ...

  5. 平衡二叉树(c++实现)续

    !!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!! 作者:mohist --- 欢迎指正--- 题外话:上一篇关于平衡二叉树文章中,我都没说自己是怎么理解的.别人终 ...

  6. c/c++一些常用的内置宏

    关于 本文演示环境: win10 + VS2017 Note 市面上的编译器五花八门,但是通常都支持: __DATE__,__FILE__,__LINE__ 和 __TIME__ 这个4个宏 VS20 ...

  7. 【LeetCode】355. Design Twitter 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  8. 【LeetCode】646. Maximum Length of Pair Chain 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 贪心算法 日期 题目地址:https://leetc ...

  9. Problem 2233 ~APTX4869

    Problem 2233 ~APTX4869 Accept: 55    Submit: 176Time Limit: 1000 mSec    Memory Limit : 32768 KB Pro ...

  10. Docker 与 K8S学习笔记(五)—— 容器的操作(下篇)

    上一篇我们学习了容器的启动和常用的进入容器的方式,今天我们来看看如何控制容器起停以及容器删除操作. 一.stop.kill.start和restart stop.kill命令都可以停止运行的容器,二者 ...