Spring学习:简单实现一个依赖注入和循环依赖的解决
依赖注入
什么是依赖注入
使用一个会创建和查找依赖对象的容器,让它负责供给对象。
当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()");
}
}


@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;
}
}
测试代码
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()");
}
}
在原本构造对象的方法里面:
如果a类引用了容器的b类,a类在构造时,会让容器去构造b类,等b类构造完毕,a类才构造完毕。
当两个类互相引用时,a让容器构造b,b让容器构造a,最终造成死循环,可以使用上面的代码测试。
解决方案
原本只使用了一个Object_Map存储对象,现在再加上一个Complete_Map。
- Object_Map是第一层,存储的是刚刚实例化的对象。
- Complete_Map是第二层,存储属性填充完毕(引用的b、c、d、e全部构造好)的对象。
新的构造对象步骤
- a在实例化后,让容器去构造b,b实例化后,将b存入Object_Map中,继续a的构造流程。
- a从Object_Map拿到b,继续构造,最后存入Complete_Map。
- 轮到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());
}
}
}
Spring学习:简单实现一个依赖注入和循环依赖的解决的更多相关文章
- Spring源码学习笔记9——构造器注入及其循环依赖
Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...
- Spring学习之第一个AOP程序
IOC和AOP是Spring的两大基石,AOP(面向方面编程),也可称为面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP). 在进行 OOP 开发时,都是基于对 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...
- 再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?
开心一刻 一天,侄子和我哥聊天,我坐在旁边听着 侄子:爸爸,你爱我妈妈吗? 哥:这话说的,不爱能有你吗? 侄子:确定有我不是因为荷尔蒙吗? 哥:因为什么荷尔蒙,因为爱情! 侄子:那我妈花点钱,你咋老说 ...
- Spring学习(二)三种方式的依赖注入
1.前言 上一篇讲到第一个Spring项目的创建.以及bean的注入.当然.注入的方式一共有三种.本文将展开细说. 1.set注入:本质是通过set方法赋值 1.创建老师类和课程类 1.Course ...
- Spring学习之第一个hello world程序
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development a ...
- Spring学习笔记(3)——Bean的注入方式
依赖注入 依赖注入支持属性注入.构造函数注入.工厂注入. 属性注入: 属性注入即通过setXxx()方法注入Bean的属性值或依赖对象 属性注入要求Bean提供一个默认的构造函数(无参构造函数),并为 ...
- Spring学习之第一个Spring MVC程序(IDEA开发环境)
回顾Java平台上Web开发历程来看,从Servlet出现开始,到JSP繁盛一时,然后是Servlet+JSP时代,最后演化为现在Web开发框架盛行的时代.一般接触到一个新的Web框架,都会想问这个框 ...
- Spring.Net 简单实例-02(属性注入)
说明:接续Spring.Net 简单实例-01(IOC) 话不多说看操作 1:为UserInfo添加属性 2: 修改App.config中代码 <?xml version="1.0&q ...
随机推荐
- Dom 解析XML
xml文件 <?xml version="1.0" encoding="UTF-8"?><data> <book id=&q ...
- epx中设置断掉调试
以前总听师傅们说,做pwn题,多调试,多调试. 师傅都说用gdb,但是我刚接触linux程序调试的时候用的是pwndbg,后来就用顺手了.但是调试一些简单程序还好,直接用pwndbg打开.但是这年头简 ...
- 工厂为什么要进行计划排产,APS高级计划排程系统的优势作用是什么?
我们每个人的指挥中心是大脑,大脑对我们身体发出各种各样的指令,不停的告诉我们身体去干什么. 那么,一个制造企业的指挥中心是哪里?工厂每天都会接到各种各样的订单,通过几百上千的工人,使用各种设备来生产. ...
- 删除其他列Table.SelectColumns(Power Query 之 M 语言)
数据源: "姓名""基数""个人比例""个人缴纳""公司比例""公司缴纳"&qu ...
- Codeforces GYM 100876 J - Buying roads 题解
Codeforces GYM 100876 J - Buying roads 题解 才不是因为有了图床来测试一下呢,哼( 题意 给你\(N\)个点,\(M\)条带权边的无向图,选出\(K\)条边,使得 ...
- 超链接 a 标签点击时,弹出提示框,可以按照如下来写
onclick="return confirm('确定删除该条记录?')" 加上这条记录后,就可以在访问href属性指向的链接时,有弹出提示
- 【LeetCode】1056. Confusing Number 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 日期 题目地址:https://leetcode ...
- 【LeetCode】146. LRU Cache 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典+双向链表 日期 题目地址:https://le ...
- 【LeetCode】1. Two Sum 两数之和
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:two sum, 两数之和,题解,leetcode, 力 ...
- 【LeetCode】63. Unique Paths II 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/unique-pa ...