手写Spring框架(简单实现)

Spring风格的三级缓存管理:一个简单实现示例

在实现一个简单的Spring风格的依赖注入容器时,我们通常需要处理Bean的生命周期,包括实例化、属性注入以及完全初始化。为了处理复杂的依赖关系和循环依赖问题,我们可以使用三级缓存机制。本文将介绍如何实现这种机制,并展示相应的代码示例。

三级缓存的概念
  1. 一级缓存singletonObjects):存储完全初始化的单例Bean。
  2. 二级缓存earlySingletonObjects):存储实例化但尚未完成所有属性注入的Bean。
  3. 三级缓存singletonFactories):存储Bean工厂,用于延迟初始化和解决循环依赖。
完整的实现代码

以下是一个实现三级缓存的简单容器的代码示例。这个实现使用了dom4j库来解析XML配置文件,创建和管理Bean的生命周期。

package org.xtl.spring;import org.dom4j.*;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** ClassPathXmlApplicationContext 实现了一个简单的 Spring 风格的依赖注入容器。* 它使用三级缓存来处理 Bean 的创建和依赖注入,以支持复杂的依赖关系和循环依赖。*/
public class ClassPathXmlApplicationContext implements ApplicationContext {// 一级缓存:存放完全初始化的单例 Beanprivate final Map<String, Object> singletonObjects = new HashMap<>();// 二级缓存:存放实例化但未完成所有属性注入的单例 Beanprivate final Map<String, Object> earlySingletonObjects = new HashMap<>();// 三级缓存:存放 Bean 工厂,用于延迟初始化和解决循环依赖private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();/*** 构造函数,加载配置文件并初始化 Bean。** @param configLocation 配置文件的位置*/public ClassPathXmlApplicationContext(String configLocation) {try {// 读取配置文件InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);SAXReader reader = new SAXReader();Document document = reader.read(is);Element root = document.getRootElement();List<Element> beanElements = root.elements("bean");// 第一次遍历:实例化 Bean 并将 Bean 工厂放入三级缓存for (Element beanEle : beanElements) {String id = beanEle.attributeValue("id");String className = beanEle.attributeValue("class");Class<?> clazz = Class.forName(className);Constructor<?> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);// 使用 ObjectFactory 创建 Bean 实例ObjectFactory<?> singletonFactory = () -> {try {return constructor.newInstance();} catch (Exception e) {throw new RuntimeException("Failed to create bean instance", e);}};// 将工厂放入三级缓存singletonFactories.put(id, singletonFactory);}// 第二次遍历:进行属性填充和依赖注入for (Element beanEle : beanElements) {String id = beanEle.attributeValue("id");Class<?> clazz = Class.forName(beanEle.attributeValue("class"));// 从三级缓存获取 Bean 实例并将其放入 earlySingletonObjectsObject bean = getSingleton(id);List<Element> propertyEles = beanEle.elements("property");for (Element propertyEle : propertyEles) {String propertyName = propertyEle.attributeValue("name");String propertyValueStr = propertyEle.attributeValue("value");String propertyRefStr = propertyEle.attributeValue("ref");Object propertyValue = null;// 获取属性类型Class<?> propertyType = clazz.getDeclaredField(propertyName).getType();if (propertyValueStr != null) {// 将属性值转换为正确的类型propertyValue = changeType(propertyType.getSimpleName(), propertyValueStr);}if (propertyRefStr != null) {// 从 earlySingletonObjects 中获取属性值propertyValue = earlySingletonObjects.get(propertyRefStr);}// 设置属性String methodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);Method method = clazz.getDeclaredMethod(methodName, propertyType);method.setAccessible(true);method.invoke(bean, propertyValue);}// 将完全初始化的 Bean 放入一级缓存,并从二级缓存中移除singletonObjects.put(id, bean);earlySingletonObjects.remove(id);singletonFactories.remove(id);}} catch (Exception e) {throw new RuntimeException("Failed to initialize the application context", e);}}/*** 从缓存中获取 Bean 实例。** @param beanName Bean 的名称* @return Bean 实例*/private Object getSingleton(String beanName) {// 从一级缓存中获取Object singleton = singletonObjects.get(beanName);if (singleton == null) {// 从二级缓存中获取singleton = earlySingletonObjects.get(beanName);if (singleton == null) {// 从三级缓存中获取 Bean 工厂并创建实例ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);if (singletonFactory != null) {singleton = singletonFactory.getObject();// 将实例放入二级缓存earlySingletonObjects.put(beanName, singleton);singletonFactories.remove(beanName);}}}return singleton;}@Overridepublic Object getBean(String name) {return getSingleton(name);}@Override@SuppressWarnings("unchecked")public <T> T getBean(String name, Class<T> requiredType) {Object bean = getBean(name);if (requiredType.isInstance(bean)) {return (T) bean;}throw new ClassCastException("Bean is not of required type: " + requiredType.getName());}/*** 将字符串转换为指定的类型。** @param typeName 属性类型的名称* @param propertyValueStr 属性值的字符串表示* @return 转换后的属性值* @throws Exception 如果不支持该类型*/public static Object changeType(String typeName, String propertyValueStr) throws Exception {Object propertyValue = null;switch (typeName) {case "byte", "Byte":propertyValue = Byte.parseByte(propertyValueStr);break;case "short", "Short":propertyValue = Short.parseShort(propertyValueStr);break;case "int", "Integer":propertyValue = Integer.parseInt(propertyValueStr);break;case "float", "Float":propertyValue = Float.parseFloat(propertyValueStr);break;case "double", "Double":propertyValue = Double.parseDouble(propertyValueStr);break;case "boolean", "Boolean":propertyValue = Boolean.parseBoolean(propertyValueStr);break;case "long", "Long":propertyValue = Long.parseLong(propertyValueStr);break;case "char", "Character":propertyValue = propertyValueStr.charAt(0);break;case "String":propertyValue = propertyValueStr;break;default:throw new Exception("不支持该类型");}return propertyValue;}/*** ObjectFactory 接口用于创建 Bean 实例。** @param <T> Bean 的类型*/public interface ObjectFactory<T> {T getObject();}
}
代码说明
  1. 构造函数

    • 读取配置文件并解析所有的Bean定义。
    • 将Bean工厂放入三级缓存(singletonFactories)。
  2. 第一次遍历

    • 创建Bean的工厂并将其放入三级缓存,以延迟Bean的实例化。
  3. 第二次遍历

    • 从三级缓存中获取Bean实例,并将其放入二级缓存(earlySingletonObjects)。
    • 填充Bean的属性。如果属性是引用类型,从二级缓存中获取依赖Bean。
  4. 完成初始化

    • 将完全初始化的Bean从二级缓存移至一级缓存(singletonObjects)。
  5. changeType方法

    • 将属性值字符串转换为相应的类型(例如intString等)。
  6. ObjectFactory接口

    • 用于定义创建Bean实例的方法。

通过上述代码和说明,你可以实现一个简单的三级缓存机制,处理Bean的创建和依赖注入。这种实现方式可以有效地管理Bean的生命周期,并解决循环依赖等复杂问题。希望这篇博客能帮助你理解和实现三级缓存机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/51425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

虚幻5|制作一个木桩,含血量及伤害数字

一&#xff0c;基础设置 1.创建Actor蓝图类 2.编辑胶囊体和网格体的碰撞预设 3.打开敌人的角色蓝图&#xff0c;编辑飙血特效 二&#xff0c;创建敌人血量的ui&#xff0c;命名为敌人血量&#xff0c;如下 1. 2&#xff0c;打开后&#xff0c;添加一个画布画板和进度条&#…

ArkUI-布局(一)

ArkUI-布局 布局概述布局元素的组成如何选择布局布局位置对子元素的约束 线性布局部分属性与使用方式 层叠布局部分属性与使用方式 弹性布局基本概念布局方向布局换行主轴对齐方式交叉轴对齐方式容器设置交叉轴对齐方式子元素设置交叉轴对齐方式内容对齐 自适应拉伸 布局概述 …

C++基础面试题 | 什么是C++的列表初始化?

青山相待&#xff0c;白云相爱&#xff0c;梦不到紫罗袍共黄金带。 - 《山坡羊道情》(宋方壶) 2024.8.26 回答重点 C11中引入了列表初始化&#xff0c;其语法是用花括号{}来初始化变量或对象。列表初始化可以应用于&#xff1a; 基础数据类型用户自定义类型&#xff08;类、结…

云端集中管控边缘服务:利用 EMQX ECP 在 K8s 上快速部署 NeuronEX

随着物联网、边缘计算技术的发展&#xff0c;实现边缘服务的快速部署对于分布式计算环境至关重要。它不仅可以显著降低延迟、节省带宽资源、增强数据的安全性和隐私保护&#xff0c;同时还能改善用户体验&#xff0c;支持动态变化的工作负载需求&#xff0c;提供更高的灵活性和…

Linux 实时调度器:带宽限制

文章目录 1. 前言2. 概念3. 实时进程 的 带宽限制3.1 实时进程 带宽限制 初始化3.2 启动 实时进程 带宽 监测定时器3.3 累加 实时进程 消耗的带宽3.4 查看 实时进程 带宽消耗情况3.5 小结 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的…

JAVA毕业设计165—基于Java+Springboot+vue3的二手房交易管理系统(源代码+数据库+11000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的二手房交易管理系统(源代码数据库11000字论文)165 一、系统介绍 本项目前后端分离(还有ssm版本)&#xff0c;分为用户、卖家、管理员三种角色 1、用户&a…

TOMCAT-企业级WEB应用服务器

一 WEB技术 1.1 HTTP协议和B/S 结构 HTTP&#xff08;HyperText Transfer Protocol&#xff09;协议即超文本传输协议&#xff0c;是用于在万维网&#xff08;WWW&#xff09;上传输超文本内容的基础协议。 一、HTTP 协议的特点 1、简单快速 客户向服务器请求服务时&#…

leetcode234. 回文链表(java实现)

题目描述&#xff1a; 本道题的思路可以使用集合先存储链表的值&#xff0c;然后进行判断即可。 总体思路比较简单。 代码实现&#xff1a; class Solution {public boolean isPalindrome(ListNode head) {List<Integer> res new ArrayList();ListNode cur head;whil…

Python lambda(匿名函数)

Python 使用 lambda 来创建匿名函数。 lambda 函数是一种小型、匿名的、内联函数&#xff0c;它可以具有任意数量的参数&#xff0c;但只能有一个表达式。 匿名函数不需要使用 def 关键字定义完整函数。 lambda 函数通常用于编写简单的、单行的函数&#xff0c;通常在需要函…

代谢组数据分析(十八):随机森林构建代谢组诊断模型

介绍 使用随机森林算法和LASSO特征选择构建了一种胃癌(GC)诊断预测模型。参与者(队列1,n=426)通过随机分层抽样分为发现数据集(n=284)和测试集(n=142)。接下来,在发现数据集上执行LASSO回归,以选择能够识别胃癌患者的较少数量的特征。我们将L1约束的系数设置为0.01…

OpenCV绘图函数(3)判断点一条直线是否在一个矩形范围内的函数clipLine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 裁剪线段与图像矩形相交的部分。 cv::clipLine 函数计算出完全位于指定矩形内的线段部分。如果线段完全位于矩形之外&#xff0c;则返回 false。…

2024年小红书图文制作超火爆风格,1单19.9!趣味性插画AI表情包项目思路

今天我想跟大家分享一个每天只需花20分钟就能轻松赚钱的AI表情包项目。 这个项目在小红书上非常受欢迎&#xff0c;因为它符合小红书的用户习惯&#xff0c;而且操作简单。下面我来详细讲讲如何利用软件快速创作有趣的插画&#xff01; 项目简介 这个项目的原理很简单&#x…

工厂模式和策略模式区别以及使用

1. 简介 1.1. 概述 1.1.1. 工厂模式 工厂模式的核心思想是将对象的创建过程封装起来,使得客户端(Caller)不需要直接与具体类(Concrete Class)交互,而是通过一个接口(Interface)来与它们交互。这样做的好处是,当需要更改对象的创建方式时,只需修改工厂方法即可,无需…

计算机网络-2-tcpip协议

1.说说 TCP/IP 四层模型&#xff1f; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;模型是一种用于描述互联网通信的协议层次结构。它分为四个主要层次&#xff0c;每个层次都定义了不同的协议来实现特定的功能。下面是TCP/IP模型各层的常用…

AES对称加密算法

1. 简介 AES是一种对称加密算法, 它有3种类型: AES-128: 密钥为128位(16字节)的AES, 加密10轮AES-192: 密钥为192位(24字节)的AES, 加密12轮AES-256: 密钥为256位(32字节)的AES, 加密14轮 密钥长度越长, 加密的强度越大, 当然与此同时开销也越大。每种类型下都有几种操作模式…

Python序列化与反序列化:pickle库使用详解

pickle是Python中一个用于对象序列化与反序列化的模块。它可以将Python对象转换成字节流&#xff0c;这样这些对象就可以容易地存储到文件中&#xff0c;或者通过网络传输。同样地&#xff0c;pickle也可以将这些字节流重新转换成原来的Python对象。 pickle库的主要功能 将Py…

给自己复盘的tjxt笔记day9

优惠券管理 开发流程 需求分析&#xff0c;接口统计&#xff0c;数据库设计&#xff0c;创建分支&#xff0c;创建新模块&#xff08;依赖&#xff0c;配置&#xff0c;启动类&#xff09;&#xff0c;生成代码&#xff0c;引入枚举状态 优惠券管理 增删改查的业务代码&#…

NC 数组中的最长连续子序列

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定无序数组…

前端算法 === 力扣 111 二叉树的最小深度

目录 问题描述 DFS&#xff08;深度优先搜索&#xff09;方案 BFS&#xff08;广度优先搜索&#xff09;方案 总结 力扣&#xff08;LeetCode&#xff09;上的题目111是关于二叉树的最小深度问题。这个问题可以通过深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&…

python 实现二维矩阵运算的函数算法

二维矩阵运算的函数算法介绍 在二维矩阵运算中&#xff0c;除了之前提到的加法和乘法之外&#xff0c;还有其他一些常见的运算&#xff0c;如矩阵的转置、求逆&#xff08;针对方阵且行列式不为零&#xff09;、点积&#xff08;也称为内积&#xff0c;但通常不直接用于整个矩…