目录
- 背景
- 实现步骤
- 实现思路
- 类图
- 代码
- 原理:
- 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.");}
}
解决循环依赖:循环依赖指的是两个或多个对象彼此之间存在直接或间接的相互依赖关系。在传统的依赖注入模式中,由于对象的创建过程中需要解析它们的依赖关系,循环依赖可能导致无限递归或无法解析的问题。使用二级缓存可以在对象创建的过程中暂时存储已经创建的对象实例,避免了循环依赖造成的问题。
提高性能:二级缓存可以避免重复创建对象。在解析依赖关系时,如果发现某个对象已经在缓存中存在,可以直接从缓存中获取对象实例,而无需再次创建。这样可以减少对象创建的时间和资源消耗,提高系统的性能。
控制对象的生命周期:通过二级缓存,可以更好地控制对象的生命周期。对象的创建和销毁都可以在缓存中进行管理。当对象不再被需要时,可以从缓存中移除,释放相关的资源。这样可以更好地管理系统中的对象,避免内存泄漏等问题。
支持复杂的依赖关系:在大型应用程序中,对象之间的依赖关系可能非常复杂。使用二级缓存可以更好地处理这种情况下的循环依赖,使得依赖注入更加灵活和可扩展。