纯手写IOC以及解决简单的循环依赖的问题

news/2025/1/18 6:48:21/

目录

  • 背景
  • 实现步骤
    • 实现思路
    • 类图
    • 代码
  • 原理:
    • 1、首先了解IOC中存储的Bean的生命周期
    • 2、找准时机进行属性注入
  • 升华:

背景

了解IOC和DI
配置IOC的方式(配置文件和注解)
上篇文章已经讲述了IOC的原理。以及如何使用IOC,现在我们就针对于IOC存在的循环依赖进行解决。同时也囊括了手写ioc(原理级别,没有使用已有的API)

实现步骤

正常情况下我们是通过注解标识然后获取想要被扫描的包并将带有标识的类放到ioc容器中,那我们是不是不使用注解也能实现这种效果呢,其实归根结底,是根据标识判断当前类是否被Spring管理,根据标识是否需要将字段对象进行注入,那好,我们既然知道Spring是定义了这一套规则出来,我们自己也能实现,我们使用注释代表注解,通过扫描特定注释实现一样的效果。那我们开始写吧。

实现思路

在这里插入图片描述

容器类内部逻辑

按照标识扫描将bean对象放置到容器中
在这里插入图片描述
在这里插入图片描述
自动装配

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

类图

在这里插入图片描述

代码

启动类

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;//扫描(com.example.demo)
public class DemoApplication {public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException, URISyntaxException {ApplicationChinese applicationContext =new ApplicationChinese();applicationContext.test();Object person = ApplicationChinese.getBean("person");Method method = person.getClass().getMethod("printInfo");method.invoke(person);}}

获取Bean对象的类

package com.example.demo;public class BeanDefinition {private Class beanClass;public Class getBeanClass() {return beanClass;}public void setBeanClass(Class beanClass) {this.beanClass = beanClass;}
}

容器类

package com.example.demo;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class ApplicationChinese {private static Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();private static Map<String, Object> singletonObjects = new HashMap<>();public void test() throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException, URISyntaxException {// 读取文件内容String filePath = "C:\\Users\\Administrator\\Desktop\\IOCByhand\\demo\\src\\main\\java\\com\\example\\demo\\DemoApplication.java";String fileContent = getFileContent(filePath);System.out.println(fileContent);// 提取包名String packageName = extractPackageName(fileContent);System.out.println(packageName);// 扫描并实例化组件scanAndInstantiateComponents(packageName);System.out.println(getObjectMap());// 进行自动装配initAutowired();}public Map<String, BeanDefinition> getObjectMap() {return beanDefinitionMap;}public void scanAndInstantiateComponents(String basePackage) throws ClassNotFoundException, URISyntaxException {// 将包路径转换为文件路径String basePackagePath = basePackage.replace(".", File.separator);// 构建基础包目录对象File basePackageDir = new File(System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator + basePackagePath);if (basePackageDir.exists() && basePackageDir.isDirectory()) {// 递归扫描子包并实例化组件scanAndInstantiateComponentsRecursive(basePackageDir, basePackage);}}public static String getFileContent(String filePath) {StringBuilder fileContentBuilder = new StringBuilder();try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {String line;while ((line = reader.readLine()) != null) {fileContentBuilder.append(line).append(System.lineSeparator());}} catch (IOException e) {e.printStackTrace();}return fileContentBuilder.toString();}public static String extractPackageName(String fileContent) {// 使用正则表达式提取包名Pattern pattern = Pattern.compile("//扫描\\((.*?)\\)");Matcher matcher = pattern.matcher(fileContent);if (matcher.find()) {String packageName = matcher.group(1);return packageName;}return null;}private void scanAndInstantiateComponentsRecursive(File directory, String basePackage) throws ClassNotFoundException, URISyntaxException {// 获取目录下的文件列表File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {// 如果是目录,则递归扫描子包String packageName = basePackage + "." + file.getName();scanAndInstantiateComponentsRecursive(file, packageName);} else if (file.getName().endsWith(".java")) {// 如果是 Java 文件,则读取文件内容进行判断String absolutePath = file.getAbsolutePath();String fileContent = getFileContent(absolutePath);
//                    String absolutePath = "C:\\Users\\Username\\Documents\\project\\src\\com\\example\\Main.java";
//                    String basePackage = "com.example";// 截取 com 到 .java 之间的文本部分int startIndex = absolutePath.indexOf("com");int endIndex = absolutePath.lastIndexOf(".java");String textBetween = absolutePath.substring(startIndex, endIndex);// 将 \\ 转换为 .String convertedText = textBetween.replace("\\", ".");if (containsComponentAnnotation(fileContent)) {String className = getClassName(file);String convertedWord = convertFirstLetterToLowercase(className);BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setBeanClass(Class.forName(convertedText));beanDefinitionMap.put(convertedWord, beanDefinition);}}}}}private static String convertFirstLetterToLowercase(String word) {if (word.length() > 0) {char firstChar = word.charAt(0);char convertedChar = Character.toLowerCase(firstChar);return convertedChar + word.substring(1);}return word;}private String getClassName(File file) {String fileName = file.getName();return fileName.substring(0, fileName.lastIndexOf('.'));}private boolean containsComponentAnnotation(String fileContent) {// 检查文件内容中是否包含组件注解String[] lines = fileContent.split("\\r?\\n");for (int i = 1; i < lines.length; i++) {String currentLine = lines[i].trim();String previousLine = lines[i - 1].trim();if (currentLine.contains("class") && previousLine.contains("//部件")) {return true;}}return false;}// 接下来是 initAutowired 方法的注释修改private void initAutowired() throws InstantiationException, IllegalAccessException, IOException {for (String beanName : beanDefinitionMap.keySet()) {getBean(beanName);}}static Object getBean(String beanName) throws InstantiationException, IllegalAccessException, IOException {// 检查一级缓存,如果对象已存在,则直接返回Object singletonObject = singletonObjects.get(beanName);if (singletonObject != null) {return singletonObject;}// 创建对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);Class<?> beanClass = beanDefinition.getBeanClass();Object bean = beanClass.newInstance();// 添加到一级缓存singletonObjects.put(beanName, bean);// 解决循环依赖,进行属性注入populateBean(bean, beanDefinition);return bean;}private static void populateBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException, IOException {// 获取对象的类和属性信息Class<?> beanClass = beanDefinition.getBeanClass();String beanPath = beanClass.getName();String replacedText = beanPath.replace(".", "\\");Object instance = beanClass.newInstance();//遍历所有java文件  读取到//autowired   读取下边的字段   获得匹配的FieldString folderPath = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator + replacedText + ".java";String fieldName = processJavaFiles(folderPath);Field[] fields = beanClass.getDeclaredFields();for (Field field : fields) {// 判断属性是否被 //autowired 注解修饰String substring = null;String type = String.valueOf(field.getType());int lastDotIndex = type.lastIndexOf(".");if (lastDotIndex != -1 && lastDotIndex < type.length() - 1) {substring = type.substring(lastDotIndex + 1);}if (substring.equals(fieldName)) {field.setAccessible(true);fieldName = field.getName();Object dependency = getBean(fieldName);field.set(bean, dependency);}}}private static String processJavaFiles(String filePath) throws IOException {String firstWord;try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {String line;boolean foundAutowired = false;while ((line = br.readLine()) != null) {if (line.contains("//注入")) {foundAutowired = true;} else if (foundAutowired) {firstWord = extractFirstWord(line);System.out.println("In file: " + filePath + " first word in the next line: " + firstWord);return firstWord;}}} catch (IOException e) {e.printStackTrace();}return null;}private static String extractFirstWord(String line) {String[] words = line.trim().split("\\s+");if (words.length > 0) {return words[1];}return "";}}

Person类

package com.example.demo;
//部件
public class Person {//注入private Fruit fruit;public void setFruit(Fruit fruit) {this.fruit = fruit;}public void printInfo() {
//        this.fruit.printInfo();System.out.println("Person: I am a person.");}
}

蔬菜类

package com.example.demo;//部件
public class Fruit {//注入private Person person;public void setPerson(Person person) {this.person = person;}public void printInfo() {
//        this.person.printInfo();System.out.println("Fruit: I am a fruit.");}
}

结果:
在这里插入图片描述

原理:

1、首先了解IOC中存储的Bean的生命周期

大家可以先看一下官网:

在这里插入图片描述
可以简单理解为 实例化,填充属性,初始化三个阶段。

2、找准时机进行属性注入

提前暴露对象的引用:当存在循环依赖时,需要在适当的时机将正在创建的对象提前暴露给其他对象使用,而不是等待对象完全创建后再进行注入。这可以通过将正在创建的对象的引用暂存到一个缓存中来实现。当依赖关系解析完成后,再将缓存中的对象引用注入到其他对象中

升华:

拓展:
(1) 一级缓存模型
在这里插入图片描述
(2) 二级缓存模型
在这里插入图片描述

使用二级缓存解决循环依赖的问题

在这里插入图片描述

public class Client {public static void main(String[] args) {ApplicationContext context = new ApplicationContext();Person person = (Person) context.getBean("person");person.printInfo();Fruit fruit = (Fruit) context.getBean("fruit");fruit.printInfo();}
}
public class ApplicationContext {//完整对象private Map<String, Object> singletonObjects = new HashMap<>();//半成品private Map<String, Object> earlySingletonObjects = new HashMap<>();public Object getBean(String beanName) {// 检查一级缓存Object singletonObject = singletonObjects.get(beanName);if (singletonObject != null) {return singletonObject;}// 检查二级缓存singletonObject = earlySingletonObjects.get(beanName);if (singletonObject != null) {return singletonObject;}// 创建对象Object bean = createBean(beanName);// 添加到二级缓存earlySingletonObjects.put(beanName, bean);// 解决循环依赖populateBean(beanName, bean);// 添加到一级缓存singletonObjects.put(beanName, bean);// 从二级缓存移除earlySingletonObjects.remove(beanName);return bean;}private Object createBean(String beanName) {// 创建对象的逻辑,此处省略// 这里假设根据beanName创建对应的对象if (beanName.equals("person")) {return new Person();} else if (beanName.equals("fruit")) {return new Fruit();}return null;}private void populateBean(String beanName, Object bean) {// 解决循环依赖的逻辑if (beanName.equals("person")) {Fruit fruit = (Fruit) getBean("fruit");((Person) bean).setFruit(fruit);} else if (beanName.equals("fruit")) {Person person = (Person) getBean("person");((Fruit) bean).setPerson(person);}}
}
public class Person {private Fruit fruit;public Person() {}public void setFruit(Fruit fruit) {this.fruit = fruit;}public void printInfo() {System.out.println("Person: I am a person.");}
}
package IOC2023年7月1日.TwoMap;public class Fruit {private Person person;public Fruit() {}public void setPerson(Person person) {this.person = person;}public void printInfo() {System.out.println("Fruit: I am a fruit.");}
}

解决循环依赖:循环依赖指的是两个或多个对象彼此之间存在直接或间接的相互依赖关系。在传统的依赖注入模式中,由于对象的创建过程中需要解析它们的依赖关系,循环依赖可能导致无限递归或无法解析的问题。使用二级缓存可以在对象创建的过程中暂时存储已经创建的对象实例,避免了循环依赖造成的问题。

提高性能:二级缓存可以避免重复创建对象。在解析依赖关系时,如果发现某个对象已经在缓存中存在,可以直接从缓存中获取对象实例,而无需再次创建。这样可以减少对象创建的时间和资源消耗,提高系统的性能。

控制对象的生命周期:通过二级缓存,可以更好地控制对象的生命周期。对象的创建和销毁都可以在缓存中进行管理。当对象不再被需要时,可以从缓存中移除,释放相关的资源。这样可以更好地管理系统中的对象,避免内存泄漏等问题。

支持复杂的依赖关系:在大型应用程序中,对象之间的依赖关系可能非常复杂。使用二级缓存可以更好地处理这种情况下的循环依赖,使得依赖注入更加灵活和可扩展。


http://www.ppmy.cn/news/787877.html

相关文章

win10中鼠标点右键或者重命名文件夹系统卡顿解决方法

在cmd中输入命令SFC/Scannow扫描后重启&#xff0c;2021-8-18亲测有效。 在扫描前是否需要执行下列链接的操作目前还未得以验证&#xff1a; http://www.xitongcheng.com/jiaocheng/win10_article_49284.html

win10 远程桌面卡顿_主编教你win10系统使用远程桌面卡顿的步骤

今天小编分享一下win10系统使用远程桌面卡顿问题的处理方法&#xff0c;在操作win10电脑的过程中常常不知道怎么去解决win10系统使用远程桌面卡顿的问题&#xff0c;有什么好的方法去处理win10系统使用远程桌面卡顿呢&#xff1f;今天本站小编教您怎么处理此问题,其实只需要1)同…

win10 远程桌面卡顿_win10系统使用远程桌面卡顿的解决教程

有关win10系统使用远程桌面卡顿的操作方法想必大家有所耳闻。但是能够对win10系统使用远程桌面卡顿进行实际操作的人却不多。其实解决win10系统使用远程桌面卡顿的问题也不是难事&#xff0c;小编这里提示两点&#xff1a;1)同时按下win10系统电脑键盘上的winR快捷键打开电脑的…

win10 远程桌面卡顿_win10系统使用远程桌面卡顿的设置教程

电脑操作系统在使用的时候经常会被一些问题所困扰&#xff0c;例如很多用户都遇见过win10系统使用远程桌面卡顿的问题&#xff0c;大部分用户如果第一次碰到win10系统使用远程桌面卡顿的现象&#xff0c;因此大伙都会不知所措&#xff0c;怎么才可以完善的治理win10系统使用远程…

win10 远程桌面卡顿_Win10系统远程桌面连接缓慢卡顿解决措施

远程桌面连接可以让我们更好的操控其他电脑&#xff0c;但是在使用过程中难免会遇到这样或那样的问题&#xff0c;而在win10系统中出现的问题也是层出不穷&#xff0c;有用户在win10下启用远程连接电脑窗口变得缓慢卡顿&#xff0c;出现这种现象主要与系统的设置是有很大关系的…

win10卡顿优化

第一招、优化电源模式&#xff0c;高性能电源模式 电源模式基本分为三种&#xff1a;平衡模式、节能模式和高性能模式。 一般我们的电脑系统装完后&#xff0c;默认都是平衡模式&#xff0c;所以如果我们办公打游戏的话&#xff0c;完全可以使用高性能电源模式&#xff0c;他…

计算机win7卡顿如何解决方法,win7卡顿严重解决方法_win7运行卡顿严重最流畅设置方法-win7之家...

在使用win7系统电脑的时间一长&#xff0c;出现的电脑故障也就会越多&#xff0c;这大多数都是用户自己所造成的&#xff0c;例如有用户的win7系统在运行过程中总是会出现严重卡顿的情况&#xff0c;这让许多用户都感到很难受&#xff0c;那么win7卡顿严重怎么解决呢&#xff1…

win10系统文件拖拽卡顿_win10系统运行会卡顿的解决方法

很多小伙伴都遇到过win10系统运行会卡顿的困惑吧,一些朋友看过网上零散的win10系统运行会卡顿的处理方法,并没有完完全全明白win10系统运行会卡顿是如何解决的,今天小编准备了简单的解决办法,只需要按照1,右键点击“此电脑”,点击属性 2,点击“高级系统设置”的顺序即…