上一篇:设计模式实战:自定义SpringIOC(理论分析)

自定义SpringIOC(亲手实践)

上一篇文章,我们介绍了SpringIOC容器的核心组件及其作用,下面我们来动手仿写一个SpringIOC容器,让我们对SpringIOC容器理解地更加透彻!Start Go Go Go!

自定义SpringIOC

对下面的配置文件进行解析,并自定义SpringIOC,对涉及到的对象进行管理。

<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="courseService" class="com.hopeful.service.impl.CourseServiceImpl"><property name="courseDao" ref="courseDao"></property></bean><bean id="courseDao" class="com.hopeful.dao.impl.CourseDaoImpl"></bean>
</beans>

1) 创建与Bean相关的pojo类

  • PropertyValue类: 用于封装 bean 的属性,体现到上面的配置文件就是封装 bean 标签的子标签 property 标签数据。

/*** 该类用来封装bean标签下的property子标签的属性*      1.name属性*      2.ref属性*      3.value属性: 给基本数据类型及string类型数据赋的值**/
public class PropertyValue {private String name;private String ref;private String value;public PropertyValue() {}public PropertyValue(String name, String ref, String value) {this.name = name;this.ref = ref;this.value = value;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getRef() {return ref;}public void setRef(String ref) {this.ref = ref;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}
}
  • MutablePropertyValues类: 一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。
package com.mashibing.framework.beans;import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** 该类用来存储和遍历多个PropertyValue对象**/
public class MutablePropertyValues implements Iterable<PropertyValue>{//定义List集合,存储PropertyValue的容器private final List<PropertyValue> propertyValueList;//空参构造中 初始化一个listpublic MutablePropertyValues() {this.propertyValueList = new ArrayList<PropertyValue>();}//有参构造 接收一个外部传入的list,赋值propertyValueList属性public MutablePropertyValues(List<PropertyValue> propertyValueList) {if(propertyValueList == null){this.propertyValueList = new ArrayList<PropertyValue>();}else{this.propertyValueList = propertyValueList;}}//获取当前容器对应的迭代器对象@Overridepublic Iterator<PropertyValue> iterator() {//直接获取List集合中的迭代器return propertyValueList.iterator();}//获取所有的PropertyValuepublic PropertyValue[] getPropertyValues(){//将集合转换为数组并返回return propertyValueList.toArray(new PropertyValue[0]); //new PropertyValue[0]声明返回的数组类型}//根据name属性值获取PropertyValuepublic PropertyValue getPropertyValue(String propertyName){//遍历集合对象for (PropertyValue propertyValue : propertyValueList) {if(propertyValue.getName().equals(propertyName)){return propertyValue;}}return null;}//判断集合是否为空,是否存储PropertyValuepublic boolean isEmpty(){return propertyValueList.isEmpty();}//向集合中添加public MutablePropertyValues addPropertyValue(PropertyValue value){//判断集合中存储的propertyvalue对象.是否重复,重复就进行覆盖for (int i = 0; i < propertyValueList.size(); i++) {//获取集合中每一个 PropertyValuePropertyValue currentPv = propertyValueList.get(i);//判断当前的pv的name属性 是否与传入的相同,如果相同就覆盖if(currentPv.getName().equals(value.getName())){propertyValueList.set(i,value);return this;}}//没有重复this.propertyValueList.add(value);return this;  //目的是实现链式编程}//判断是否有指定name属性值的对象public boolean contains(String propertyName){return getPropertyValue(propertyName) != null;}
}
  • BeanDefinition类: 用来封装 bean 信息的,主要包含id(即 bean 对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。
/*** 封装Bean标签数据的类,包括id与class以及子标签的数据**/
public class BeanDefinition {private String id;private String className;private MutablePropertyValues propertyValues;public BeanDefinition() {propertyValues = new MutablePropertyValues();}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}public MutablePropertyValues getPropertyValues() {return propertyValues;}public void setPropertyValues(MutablePropertyValues propertyValues) {this.propertyValues = propertyValues;}
}

2) 创建注册表相关的类

BeanDefinition 对象存取的操作, 其实是在BeanDefinitionRegistry接口中定义的,它被称为是BeanDefinition的注册中心。

//源码
public interface BeanDefinitionRegistry extends AliasRegistry {void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException;void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;boolean containsBeanDefinition(String beanName);String[] getBeanDefinitionNames();int getBeanDefinitionCount();boolean isBeanNameInUse(String beanName);
}

BeanDefinitionRegistry继承结构图如下:

在这里插入图片描述

BeanDefinitionRegistry接口的子实现类主要有以下两个:

  • DefaultListableBeanFactory:在该类中定义了如下代码,就是用来注册bean

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    
  • SimpleBeanDefinitionRegistry:在该类中定义了如下代码,就是用来注册bean

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
    
  • 自定义BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:
public interface BeanDefinitionRegistry {//注册BeanDefinition对象到注册表中void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);//从注册表中删除指定名称的BeanDefinition对象void removeBeanDefinition(String beanName) throws Exception;//根据名称从注册表中获取BeanDefinition对象BeanDefinition getBeanDefinition(String beanName) throws Exception;//判断注册表中是否包含指定名称的BeanDefinition对象boolean containsBeanDefinition(String beanName);//获取注册表中BeanDefinition对象的个数int getBeanDefinitionCount();//获取注册表中所有的BeanDefinition的名称String[] getBeanDefinitionNames();
}
  • SimpleBeanDefinitionRegistry类, 该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器。
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {beanDefinitionMap.put(beanName,beanDefinition);}@Overridepublic void removeBeanDefinition(String beanName) throws Exception {beanDefinitionMap.remove(beanName);}@Overridepublic BeanDefinition getBeanDefinition(String beanName) throws Exception {return beanDefinitionMap.get(beanName);}@Overridepublic boolean containsBeanDefinition(String beanName) {return beanDefinitionMap.containsKey(beanName);}@Overridepublic int getBeanDefinitionCount() {return beanDefinitionMap.size();}@Overridepublic String[] getBeanDefinitionNames() {return beanDefinitionMap.keySet().toArray(new String[1]);}
}

3) 创建解析器相关的类

BeanDefinitionReader 接口

  • BeanDefinitionReader 用来解析配置文件并在注册表中注册 bean 的信息。定义了两个规范:
    • 获取注册表的功能,让外界可以通过该对象获取注册表对象;
    • 加载配置文件,并注册bean数据
/*** 该类定义解析配置文件规则的接口**/
public interface BeanDefinitionReader {//获取注册表对象BeanDefinitionRegistry getRegistry();//加载配置文件并在注册表中进行注册void loadBeanDefinitions(String configLocation) throws Exception;
}

XmlBeanDefinitionReader类

  • XmlBeanDefinitionReader 是专门用来解析 xml 配置文件的。该类实现 BeanDefinitionReader 接口并实现接口中的两个功能。
/*** 该类是对XML文件进行解析的类**/
public class XmlBeanDefinitionReader implements BeanDefinitionReader {//声明注册表对象(将配置文件与注册表解耦,通过Reader降低耦合性)private BeanDefinitionRegistry registry;public XmlBeanDefinitionReader() {registry = new SimpleBeanDefinitionRegistry();}@Overridepublic BeanDefinitionRegistry getRegistry() {return registry;}//加载配置文件@Overridepublic void loadBeanDefinitions(String configLocation) throws Exception {//使用dom4j解析xmlSAXReader reader = new SAXReader();//获取配置文件,类路径下InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);//获取document文档对象Document document = reader.read(is);Element rootElement = document.getRootElement();//解析bean标签parseBean(rootElement);}private void parseBean(Element rootElement) {//获取所有的bean标签List<Element> elements = rootElement.elements();//遍历获取每个bean标签的属性值和子标签propertyfor (Element element : elements) {String id = element.attributeValue("id");String className = element.attributeValue("class");//封装到beanDefinitionBeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setId(id);beanDefinition.setClassName(className);//获取propertyList<Element> list = element.elements("property");MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();//遍历,封装propertyValue,并保存到mutablePropertyValuesfor (Element element1 : list) {String name = element1.attributeValue("name");String ref = element1.attributeValue("ref");String value = element1.attributeValue("value");PropertyValue propertyValue = new PropertyValue(name,ref,value);mutablePropertyValues.addPropertyValue(propertyValue);}//将mutablePropertyValues封装到beanDefinitionbeanDefinition.setPropertyValues(mutablePropertyValues);System.out.println(beanDefinition);//将beanDefinition注册到注册表registry.registerBeanDefinition(id,beanDefinition);}}
}

4) 创建IOC容器相关的类

1) BeanFactory接口

在该接口中定义 IOC 容器的统一规范和获取bean对象的方法。

/*** IOC容器父接口**/
public interface BeanFactory {Object getBean(String name)throws Exception;//泛型方法,传入当前类或者其子类<T> T getBean(String name ,Class<? extends T> clazz)throws Exception;
}

2) ApplicationContext 接口

该接口的所有的子实现类对 bean 对象的创建都是非延时的,所以在该接口中定义 refresh() 方法,该方法主要完成以下两个功能:

  • 加载配置文件。
  • 根据注册表中的 BeanDefinition 对象封装的数据进行 bean 对象的创建。
/*** 定义非延时加载功能**/
public interface ApplicationContext extends BeanFactory {//进行配置文件加载,并进行对象创建void refresh();
}

3) AbstractApplicationContext类

  • 作为 ApplicationContext 接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。
  • 声明 BeanDefinitionReader 类型的变量,用来进行 xml 配置文件的解析,符合单一职责原则。
  • BeanDefinitionReader 类型的对象创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader 哪儿个子实现类对象。
/*** ApplicationContext接口的子实现类*      创建容器对象时,加载配置文件,对bean进行初始化**/
public abstract class AbstractApplicationContext implements ApplicationContext {//声明解析器变量protected BeanDefinitionReader beanDefinitionReader;//定义存储bean对象的Map集合protected Map<String,Object> singletonObjects = new HashMap<>();//声明配置文件类路径的变量protected String configLocation;@Overridepublic void refresh() {//加载beanDefinition对象try {beanDefinitionReader.loadBeanDefinitions(configLocation);//初始化beanfinishBeanInitialization();} catch (Exception e) {e.printStackTrace();}}//bean初始化protected  void finishBeanInitialization() throws Exception {//获取对应的注册表对象BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();//获取beanDefinition对象String[] beanNames = registry.getBeanDefinitionNames();for (String beanName : beanNames) {//进行bean的初始化getBean(beanName);}};
}

4) ClassPathXmlApplicationContext类

该类主要是加载类路径下的配置文件,并进行 bean 对象的创建,主要完成以下功能:

  • 在构造方法中,创建 BeanDefinitionReader 对象。
  • 在构造方法中,调用 refresh() 方法,用于进行配置文件加载、创建 bean 对象并存储到容器中。
  • 重写父接口中的 getBean() 方法,并实现依赖注入操作。
/*** IOC容器具体的子实现类,加载XML格式配置文件**/
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{public ClassPathXmlApplicationContext(String configLocation) {this.configLocation = configLocation;//构建解析器对象this.beanDefinitionReader = new XmlBeanDefinitionReader();this.refresh();}//跟据bean的对象名称获取bean对象@Overridepublic Object getBean(String name) throws Exception {//判断对象容器中是否包含指定名称的bean对象,如果包含就返回,否则自行创建Object obj = singletonObjects.get(name);if(obj != null){return obj;}//自行创建,获取beanDefinition对象BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();BeanDefinition beanDefinition = registry.getBeanDefinition(name);//通过反射创建对象String className = beanDefinition.getClassName();Class<?> clazz = Class.forName(className);Object beanObj = clazz.newInstance();//CourseService与UserDao存依赖,所以要将UserDao一同初始化,进行依赖注入MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();for (PropertyValue propertyValue : propertyValues) {//获取name属性值String propertyName = propertyValue.getName();//获取Value属性String value = propertyValue.getValue();//获取ref属性String ref = propertyValue.getRef();//ref与value只能存在一个if(ref != null && !"".equals(ref)){//获取依赖的bean对象,拼接set set+CourseObject bean = getBean(ref);String methodName = StringUtils.getSetterMethodFieldName(propertyName);//获取所有方法对象Method[] methods = clazz.getMethods();for (Method method : methods) {if(methodName.equals(method.getName())){//执行该set方法method.invoke(beanObj,bean);}}}if(value != null && !"".equals(value)){String methodName = StringUtils.getSetterMethodFieldName(propertyName);//获取methodMethod method = clazz.getMethod(methodName, String.class);method.invoke(beanObj,value);}}//在返回beanObj之前 ,需要将对象存储到Map容器中this.singletonObjects.put(name,beanObj);return beanObj;}@Overridepublic <T> T getBean(String name, Class<? extends T> clazz) throws Exception {Object bean = getBean(name);if(bean == null){return null;}return clazz.cast(bean);}
}

5) 自定义IOC容器测试

第一步: 将我们写好的自定义IOC容器项目,安装到maven仓库中,使其他项目可以引入其依赖

//依赖信息
<dependencies><dependency><groupId>com.hopeful</groupId><artifactId>user_defined_springioc</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

第二步: 创建一个新的maven项目,引入上面的依赖

第三步: 完成代码编写

  • dao
public interface CourseDao {public void add();
}public class CourseDaoImpl implements CourseDao {//value注入private String courseName;public String getCourseName() {return courseName;}public void setCourseName(String courseName) {this.courseName = courseName;}public CourseDaoImpl() {System.out.println("CourseDaoImpl创建了......");}@Overridepublic void add() {System.out.println("CourseDaoImpl的add方法执行了......" + courseName);}
}
  • service
public interface CourseService {public void add();
}public class CourseServiceImpl implements CourseService {public CourseServiceImpl() {System.out.println("CourseServiceImpl创建了......");}private CourseDao courseDao;public void setCourseDao(CourseDao courseDao) {this.courseDao = courseDao;}@Overridepublic void add() {System.out.println("CourseServiceImpl的add方法执行了......");courseDao.add();}
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="courseService" class="com.mashibing.test_springioc.service.impl.CourseServiceImpl"><property name="courseDao" ref="courseDao"></property></bean><bean id="courseDao" class="com.mashibing.test_springioc.dao.impl.CourseDaoImpl"><property name="courseName" value="java"></property></bean>
</beans>
  • Controller
public class CourseController{public static void main(String[] args) {//1.创建Spring的容器对象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//2.从容器对象中获取CourseService对象CourseService courseService = context.getBean("courseService", CourseService.class);//3.调用UserService的add方法courseService.add();}
}

在此,我们就已经实现了专属自己的IOC容器,是不是突然发现平时感觉很高深的Sping IoC容器也不是那么复杂!离大佬又近了一步,哈哈!

6) 案例中使用到的设计模式

  • 工厂模式:这个使用工厂模式 + 配置文件的方式。
  • 单例模式:Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
  • 模板方法模式:AbstractApplicationContext 类中的 finishBeanInitialization() 方法调用了子类的 getBean() 方法,因为 getBean() 的实现和环境息息相关。
  • 迭代器模式。对于 MutablePropertyValues 类定义使用到了迭代器模式,因为此类存储并管理PropertyValue 对象,也属于一个容器,所以给该容器提供一个遍历方式。

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

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

相关文章

力扣面试150(42/150)

7.28 20. 有效的括号 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一…

基于黑马教程——微服务架构解析(二):雪崩防护+分布式事务

之前的两篇文章我们介绍了微服务的基础概念及其服务间通信机制。本篇将深入探讨微服务的核心保障&#xff1a;服务保护与分布式事务。一、微服务保护问题描述&#xff1a; 在一个购物车的微服务中&#xff0c;倘若某一项服务&#xff08;服务A&#xff09;同一时刻访问的数据十…

LeetCode: 429 N叉树的层序遍历

题目描述给定一个 N 叉树&#xff0c;返回其节点值的层序遍历&#xff08;即从左到右&#xff0c;逐层访问每一层的所有节点&#xff09;。示例输入格式&#xff08;层序序列化&#xff09;&#xff1a;输入示意&#xff1a;1/ | \3 2 4/ \5 6输出&#xff1a;[[1], [3,2,4…

使用phpstudy极简快速安装mysql

使用 phpStudy 极简快速安装 MySQL 的完整指南&#xff1a; 一、phpStudy 简介 phpStudy 是一款 Windows 平台下的 PHP 环境集成包&#xff0c;包含&#xff1a; Apache/Nginx PHP 5.x-7.x MySQL 5.5-8.0 phpMyAdmin 二、安装步骤 1. 下载安装包 访问官网下载&#xf…

git lfs使用

apt install git lfs 或者下载二进制文件加到环境变量 https://github.com/git-lfs/git-lfs/releases git lfs install git lfs clone huggingface文件路径 如果访问不了hugggingface.co用hf-mirror.com替代&#xff0c;国内下载速度还是挺快的 先按照pip install modelscope m…

6、CentOS 9 安装 Docker

&#x1f433; CentOS 9 安装 Docker 最全图文教程&#xff08;含镜像源优化与常见问题解决&#xff09;标签&#xff1a;CentOS 9、Docker、容器技术、开发环境、国内镜像源 适合读者&#xff1a;后端开发、运维工程师、Linux 初学者&#x1f4cc; 前言 在 CentOS 9 上安装 Do…

SystemV消息队列揭秘:原理与实战

目录 一、消息队列的基本原理 1、基本概念 2、基本原理 3、消息类型的关键作用 4、重要特性总结 5、生命周期管理 6、典型应用场景 二、System V 消息队列的内核数据结构 1、消息队列的管理结构 msqid_ds&#xff08;消息队列标识符结构&#xff09; 关键字段解析 2…

5 分钟上手 Firecrawl

文章目录Firecrawl 是什么&#xff1f;本地部署验证mcp安装palyground&#x1f525; 5 分钟上手 FirecrawlFirecrawl 是什么&#xff1f; 一句话&#xff1a; 开源版的 “最强网页爬虫 清洗引擎” • 自动把任意网页 → 结构化 Markdown / JSON • 支持递归整站抓取、JS 渲染…

算法训练营day31 贪心算法⑤56. 合并区间、738.单调递增的数字 、968.监控二叉树

贪心算法的最后一篇博客&#xff01;前面两道题都是比较简单的思路&#xff0c;重点理解一下最后一道题即可。有一说一&#xff0c;进入到贪心算法这一章节之后&#xff0c;我的博客里和代码注释里的内容明显少了很多&#xff0c;因为很多贪心的题目我觉得不需要很复杂的文字说…

Jenkins流水线部署+webhook2.0

文章目录1. 环境2. 用到的插件3. 流水线部署脚本1. 环境 Centos7Jenkins2.5.0JDKopen17阿里云仓库 注意&#xff1a;这个版本兼容需要特别注意&#xff0c;要不然会很麻烦 2. 用到的插件 Generic Webhook Trigger 3. 流水线部署脚本 兼容钩子部署&#xff08;webhook&…

IDM下载失败排查

网络连接问题排查检查网络连接是否稳定&#xff0c;确保能够正常访问互联网 测试其他下载工具或浏览器是否能够正常下载 尝试关闭防火墙或杀毒软件&#xff0c;排除安全软件拦截的可能性代理和VPN设置检查确认IDM的代理设置是否正确&#xff0c;是否与系统代理一致 检查是否使用…

Anaconda安装时的几个操作

一、安装Anaconda 其实Anaconda的安装比较简单&#xff0c;点击next就好了。在安装中需要注意以下两点&#xff1a; 1、选择安装路径 在安装时&#xff0c;路径最好选择非C盘&#xff0c;且路径中不要出现中文&#xff0c;以免后期运行代码时出现不必要的错误。 我安装时&…

网易易盾、腾讯ACE等主流10款游戏反外挂系统对比

本文将深入对比10款游戏反外挂系统&#xff1a;1.网易易盾&#xff1b;2.Ricochet Anti‑Cheat&#xff1b;3.BattlEye&#xff1b;4.几维安全手游智能反外挂系统&#xff1b;5.伏魔AI反外挂&#xff1b;6.Riot Vanguard&#xff1b;7.Xigncode3&#xff1b;8.盛大GPK&#xff…

wpa_supplicant-2.10交叉编译

参考文章:https://blog.csdn.net/weixin_45783574/article/details/145810790 1、Openssl交叉编译 1.1 下载openssl-1.1.1t.tar.gz 下载网址: https://openssl-library.org/source/old/1.1.1/index.html1.2 编译 sudo tar xvf openssl-1.1.1t.tar.gz cd openssl-1.1

源码解读SpringCloudAlibaba Nacos2.x

Nacos 服务注册 Nacos 服务注册时&#xff0c;客户端会将自己的信息注册到Nicosserver上&#xff0c;形成key-value组合&#xff0c;其中key通常是服务名称&#xff0c;value是实例地址信息。在二点X版本中&#xff0c;客户端通过Spring Boot的扩展机制(例如web_initialized事件…

Windows 11 下 Anaconda 命令修复指南及常见问题解决

Windows 11 下 Anaconda 命令修复指南及常见问题解决 在使用 Anaconda 过程中&#xff0c;可能会遇到环境损坏、更新失败、包依赖冲突等问题。本文整理了一套通过命令行修复 Anaconda 的完整方案&#xff0c;适用于 Windows 11 系统&#xff0c;同时补充了权威参考链接供深入学…

安宝特案例丨全球连线!安宝特Vuzix与RodsCones共筑实时手术教育平台

安宝特Vuzix与合作伙伴Rods&Cones协作&#xff0c;为Rocamed在布拉格UROSANIT诊所举办的创新型实时手术直播研讨会提供技术赋能。 本次直播通过合作伙伴Rods&Cones软件平台搭载安宝特Vuzix智能眼镜&#xff0c;成功连接来自9国、3大洲、6个时区的27位医生&#xff0c;…

【Spring Boot 快速开发】一、入门

目录Spring Boot 简介Web 入门Spring Boot 快速入门HTTP 协议概述请求协议响应协议解析协议TomcatSpring Boot 简介 Spring Boot 是由 Pivotal 团队&#xff08;后被 VMware 收购&#xff09;开发的基于 Spring 框架的开源项目&#xff0c;于 2014 年首次发布。其核心目标是简…

laravel chunkById导出数据乱序问题

2025年7月28日17:47:29 这几天在做数据导出优化&#xff0c;使用xlswriter作为导出组件&#xff0c;但是发现在 使用 $base->chunkById(2000, function ($list) use ($writer, $sheet1) { 发现导出的数据是乱的&#xff0c;偶尔有些重复&#xff0c;偶尔有些少了&#xff0c…

Spring IOC与DI

spring的两大思想:IOC与AOP一、ioc的概念什么叫控制翻转?之前:对象的使用方,创建对象,对象的控制权,在对象的使用方手中.spring:对象的控制权交给了spring.举个例子:智能驾驶,之前车的使用权在人手中,而现在在ai手中,这就是控制反转.什么叫ioc:之前车企生产车需要做整个车,费事…