[SpringCloud] Feign Client 的创建 (一) (四)

文章目录

      • 1.FeignClientsRegistrar
      • 2.完成配置注册
        • 2.1 registerDefaultConfiguration方法
        • 2.2 迭代稳定性
        • 2.3 registerFeignClients方法

1.FeignClientsRegistrar

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

FeignClientsRegistrar实现ImportBeanDefinitionRegistrar接口。

在这里插入图片描述

2.完成配置注册

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认的Configuration(其实就是在解析@EnableFeignClients注解)this.registerDefaultConfiguration(metadata, registry);// 1)扫描所有的@FeignClient接口, 即扫描到Feign接口// 2)将每个@FeignClient注解的configuration属性注册进一个缓存map// 3)根据@FeignClient注解元数据生成的FeignClientBeanFactory的BeanDefinition, // 并将这个BeanDefinition注册进一个mapthis.registerFeignClients(metadata, registry);}
  1. registerDefaultConfiguration方法
  2. registerFeignClients方法

导入的类元数据就是启动类, 通过这个类的元数据可以获取到它上面所有的注解信息。

在这里插入图片描述

2.1 registerDefaultConfiguration方法

registerDefaultConfiguration():

//FeignClientsRegistrar.java
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//从类元数据中获取@EnableFeignClients注解//getAnnotationAttributes:获取类上指定注解的属性//该方法第二个参数true,表示将注解中class类型的属性转换为字符串类名暴露到返回到map中Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);//处理defaultConfiguration属性if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {String name;//返回当前类是否在封闭类中声明(例如,当前类是一个内部/嵌套类,还是一个方法中的本地类)。//false代表当前类就是顶级类,此时是启动类,肯定是顶级类if (metadata.hasEnclosingClass()) {//如果当前是内部、嵌套、方法中的类,获取我的封闭类的类名name = "default." + metadata.getEnclosingClassName();}else {//返回false代表当前是就顶级类,直接获取类名name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));}
}

在这里插入图片描述

registerClientConfiguration():

//FeignClientsRegistrar.java
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {//获取一个BeanDefinition的构建者 专门构建FeignClientSpecification的BeanDefinitionBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);//为FeignClientSpecification的构造器设置参数builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);//builder.getBeanDefinition()会构建对应的BeanDefinition实例//然后将其注册到Spring的注册表中registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}//BeanDefinitionBuilder.java
/*** Add an indexed constructor arg value. The current index is tracked internally* and all additions are at the present point.* 添加一个索引构造函数arg值。 内部跟踪当前索引,所有添加都在当前位置。*/
public BeanDefinitionBuilder addConstructorArgValue(@Nullable Object value) {this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(this.constructorArgIndex++, value);return this;
}

在这里插入图片描述

将BeanDefinition注册到Spring注册表:

在这里插入图片描述

DefaultListableBeanFactory.registerBeanDefinition():

//DefaultListableBeanFactory.java
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {//此时beanName为  default.启动类类名.FeignClientSpecificationAssert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {//验证这个bean定义。((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}//先尝试从注册表中获取BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {//如果已经存在,判断是否允许覆盖,不允许就抛异常if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}//覆盖,放入beanDefinitionMapthis.beanDefinitionMap.put(beanName, beanDefinition);}else {//第一次注册//检查该工厂bean创建阶段是否已经开始,通过在此期间是否有任何bean被标记为已创建来判断if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)// 不能修改启动中的集合元素(用于稳定的迭代)synchronized (this.beanDefinitionMap) {//先放入注册表this.beanDefinitionMap.put(beanName, beanDefinition);//beanDefinitionNames是一个可供遍历的beanName集合,bean创建阶段就是//先遍历该集合通过beanName再从beanDefinitionMap中获取BeanDefinition的//所以在创建阶段为了保证集合迭代稳定性,需要创建新的集合在新的集合上进行修改//创建一个新list,将beanDefinitionNames内容添加进去List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);//把新的beanName放到新的list中updatedDefinitions.add(beanName);//将新的集合替代旧的集合this.beanDefinitionNames = updatedDefinitions;//从工厂内部的手动单例名称集中删除指定名称,避免重复注册removeManualSingletonName(beanName);}}else {// Still in startup registration phase//仍处于启动注册阶段,不用考虑集合迭代稳定性问题//直接放入注册表、添加到可遍历集合中this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);//从工厂内部的手动单例名称集中删除指定名称,避免重复注册removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}
}
2.2 迭代稳定性

在这里插入图片描述

多线程的情况, 需要复制一个新的集合, 在新的集合中添加元素后, 替换原来旧集合。

  • 保证多线程情况下共享变量的可见性, 添加了volatile修饰。
    在这里插入图片描述

  • 保证有序性、原子性, 添加了synchronized关键字。

  • 迭代稳定性:
    一个线程正在修改集合中的数据,另一个线程正在迭代读取集合中的数据,由于加了volatile,导致读线程迭代的过程中,写线程对集合中的修改读线程是立即可见的,读线程读取的数据正好是写线程修改的数据,或者读线程一开始获取的个数是10个,遍历过程中,数量变多了变少了,发生这些变化都代表不稳定,并有可能引发错误。
    解决方案:修改的线程在原来集合基础上复制一个新的集合进行修改,等所有修改完成后,将整个新的集合替换掉原来旧的集合,而在修改过程中,其他线程访问的集合的地址还是指向旧的(类似写时复制的感觉)
    如果使用JUC并发包的集合,严重影响性能。

手动单例名称集中删除指定名称, 避免重复注册。

//DefaultListableBeanFactory.java
private void removeManualSingletonName(String beanName) {//Consumer:对给定的参数执行此操作。//set -> set.remove(beanName):对给定的set集合删除key为beanName的元素//Predicate:对给定参数计算此谓词。//set -> set.contains(beanName):对给定的set集合进行判断,包含key为beanName的元素就返回trueupdateManualSingletonNames(set -> set.remove(beanName), set -> set.contains(beanName));
}//DefaultListableBeanFactory.java
//更新工厂内部的手动单例名称集。
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {//检查这个工厂的bean创建阶段是否已经开始if (hasBeanCreationStarted()) {//一样存在迭代稳定性问题// Cannot modify startup-time collection elements anymore (for stable iteration)// 不能修改启动中的集合元素(用于稳定的迭代)synchronized (this.beanDefinitionMap) {if (condition.test(this.manualSingletonNames)) {//复制了一个集合,对复制的集合进行操作Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);action.accept(updatedSingletons);this.manualSingletonNames = updatedSingletons;}}}else {// Still in startup registration phase// 判断this.manualSingletonNames这个集合是否包含key为beanName的元素if (condition.test(this.manualSingletonNames)) {//包含了就从manualSingletonNames集合删除这个key为beanName的元素action.accept(this.manualSingletonNames);}}
}

updateManualSingletonNames方法:

此方法不仅有迭代稳定性, 而且有双锁DCL

在这里插入图片描述

2.3 registerFeignClients方法
//FeignClientsRegistrar.java
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//获取扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();//设置资源加载器scanner.setResourceLoader(this.resourceLoader);Set<String> basePackages;//获取@EnableFeignClients注解的属性Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());//定义扫描过滤器,专门指定扫描被@FeignClient注解的类AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);//获取@EnableFeignClients注解的clients属性//该属性直接指定要加载哪些@FeignClient类,配置了这个属性就只会加载指定的final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {//clients属性为空,则为扫描器指定条件,只扫描被@FeignClient注解的类scanner.addIncludeFilter(annotationTypeFilter);//获取扫描路径basePackages = getBasePackages(metadata);}else {//clients不空的情况final Set<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {//直接遍历指定的@FeignClient类//获取类所在的包路径basePackages.add(ClassUtils.getPackageName(clazz));//获取类的规范类名clientClasses.add(clazz.getCanonicalName());}//定义扫描过滤器,只扫描clientClasses中包含的类AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected boolean match(ClassMetadata metadata) {//对扫描到的类进行匹配://获取当前扫描到的类的类名,转换成规范类名(处理内部类的情况)String cleaned = metadata.getClassName().replaceAll("\\$", ".");//判断clientClasses中是否包含这个类return clientClasses.contains(cleaned);}};//即要同时满足被@FeignClient注解,同时该类在@FeignClientd的clients属性中被指定scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}for (String basePackage : basePackages) {//扫描包,获取候选组件的BeanDefinitionSet<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {//判断是否是具有注解元数据的BeanDefinitionif (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;//获取注解的元数据AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");//获取@FeignClient注解的属性Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());//获取FeignClient的名称(即@FeignClient注解的四个属性)//优先级contextId > value > name > serviceId//就是服务id、服务名称String name = getClientName(attributes);//注册ClientConfiguration,之前跟过//就是注册FeignClientSpecification,FeignClient规范registerClientConfiguration(registry, name,attributes.get("configuration"));//注册FeignClient的FactoryBean的BeanDefinitionregisterFeignClient(registry, annotationMetadata, attributes);}}}
}
  • getBasePackages: clients属性为空, 扫描其路径。
  • getCanonicalName: 获取规范类名。
  • getClientName: 获取FeignClient的服务名称。
  • registerFeignClient: 注册FeignClient的FactoryBean。
  1. getBasePackages
//FeignClientsRegistrar.java
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {//获取@EnableFeignClients注解的属性Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());Set<String> basePackages = new HashSet<>();for (String pkg : (String[]) attributes.get("value")) {//获取value属性if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : (String[]) attributes.get("basePackages")) {//获取basePackages属性if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {//获取basePackageClasses属性//获取类所在的包路径basePackages.add(ClassUtils.getPackageName(clazz));}if (basePackages.isEmpty()) {//如果还为空,获取之前@Import注解所标记的那个类所在的包路径basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));}return basePackages;
}

这些属性指定的扫描包路径, 是一个并集的关系。

  1. getCanonicalName

在这里插入图片描述

//Class.java
/*** Returns the canonical name of the underlying class as* defined by the Java Language Specification.  Returns null if* the underlying class does not have a canonical name (i.e., if* it is a local or anonymous class or an array whose component* type does not have a canonical name).* 返回Java语言规范定义的基础类的规范名称。如果基础类没有规范名称则返回null。* (例如,如果它是一个本地或匿名类,或者是一个元素类型没有规范名称的数组)* * @since 1.5*/
public String getCanonicalName() {if (isArray()) {//数组情况://获取数组元素的规范名称String canonicalName = getComponentType().getCanonicalName();if (canonicalName != null)return canonicalName + "[]";elsereturn null;}if (isLocalOrAnonymousClass())//本地或匿名类return null;Class<?> enclosingClass = getEnclosingClass();if (enclosingClass == null) { // top level class//当前类就是最顶层类return getName();} else {//内部类情况:String enclosingName = enclosingClass.getCanonicalName();if (enclosingName == null)return null;return enclosingName + "." + getSimpleName();}
}
  1. getClientName
//FeignClientsRegistrar.java
private String getClientName(Map<String, Object> client) {//注意,client是@FeignClient注解的属性if (client == null) {return null;}String value = (String) client.get("contextId");if (!StringUtils.hasText(value)) {value = (String) client.get("value");}if (!StringUtils.hasText(value)) {value = (String) client.get("name");}if (!StringUtils.hasText(value)) {value = (String) client.get("serviceId");}if (StringUtils.hasText(value)) {return value;}//看到优先级contextId > value > name > serviceIdthrow new IllegalStateException("Either 'name' or 'value' must be provided in @"+ FeignClient.class.getSimpleName());
}
  1. registerFeignClient
//FeignClientsRegistrar.java
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {//registry:Spring注册表//annotationMetadata:扫描到的被@FeignClient注解的类的注解数据//attributes:当前@FeignClient注解上相关的属性String className = annotationMetadata.getClassName();//FeignClientFactoryBean.class:FeignClient的工厂Bean,用来创建FeignClient实例的。//获取构建者构建FeignClientFactoryBean的BeanDefinitionBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);//@FeignClient注解的属性校验validate(attributes);//用@FeignClient注解的属性值为将来创建的FeignClientFactoryBean实例赋值definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);//别名String alias = contextId + "FeignClient";//构建出beanDefinition实例AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();//继续初始化,处理primary和qualifier属性配置boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}//包装成BeanDefinitionHolderBeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });//注册BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}//BeanDefinitionReaderUtils.java
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.// 获取beanNameString beanName = definitionHolder.getBeanName();// 注册到注册表registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.// 为beanName注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}
}

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

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

相关文章

浏览器工作原理与实践--作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的

在上一篇文章中我们讲到了什么是作用域&#xff0c;以及ES6是如何通过变量环境和词法环境来同时支持变量提升和块级作用域&#xff0c;在最后我们也提到了如何通过词法环境和变量环境来查找变量&#xff0c;这其中就涉及到作用域链的概念。 理解作用域链是理解闭包的基础&#…

Verilog语法之assign语句学习

assign语法主要是对组合逻辑的变量进行赋值的&#xff0c;就是把一个变量赋值给另一个变量&#xff0c;被复制的变量必须是wire类型的参数。 从仿真结果可以看出&#xff0c;data_in变量的值赋值给了data_out,assign语法就是赋值没有任何延迟&#xff0c;data_in是什么值&#…

Java数据结构与集合原码

数据结构与集合原码 文章目录 数据结构与集合原码1. 数据结构基本概念1.1 概念1.2 数据结构的研究对象 2. 常见存储结构2.1 数组2.2 链表2.2.1 单向链表2.2.2 双向链表 2.3 二叉树2.4 栈(stack)2.5 队列 3. 二叉树3.1 二叉树的遍历3.2 经典二叉树 4. List实现类源码分析4.1 Arr…

redis和redisson实现分布式锁

redis和redisson实现分布式锁 基于setnx命令的分布式锁基于set命令的分布式锁redission看门狗分布式锁 基于setnx命令的分布式锁 1. 加锁 使用 Redis 实现分布式锁&#xff0c;最直接的想法是利用 setnx 和 expire 命令实现加锁。 在 Redis 中&#xff0c;setnx 是「set if …

1.排列数组奇数在前偶数在后

文章目录 大家好&#xff0c;我是晓星航。今天为大家带来的是 排列数组奇数在前偶数在后 相关的讲解&#xff01;&#x1f600; public static void swap(int[] array) {int left 0;int right array.length - 1;while (left < right) {while (left < right &&…

IDEA2023版本整合SpringBoot热部署

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 开发环境篇 ✨特色专栏&#xff1a; M…

手撕算法-最小覆盖子串

描述 分析 滑动窗口。 参考力扣官方的题解思路 本问题要求我们返回字符串 s 中包含字符串 t 的全部字符的最小窗口。我们称包含 t 的全部字母的窗口为「可行」窗口。 我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针&#xff0c;一个用于「延伸…

javascript基础练习题之渔夫捕鱼

一、题目要求&#xff1a;根据用户输入的年、月、日判断是打鱼还是晒网。代码中使用了isLeapYear函数来判断输入的年份是否为闰年&#xff0c;getDays函数来计算输入日期是一年中的第几天&#xff0c;然后根据计算结果来确定是打鱼还是晒网。最后代码通过弹窗提示用户是打鱼还是…

吴渔夫:AI技术引领游戏产业革命,小团队有大作为

AI技术的突飞猛进&#xff0c;游戏产业正在经历一场前所未有的变革。中国网游先锋&#xff0c;火石控股创始人吴渔夫&#xff0c;近日在接受第一财经日报的采访&#xff0c;对AI在游戏制作中的应用和未来趋势有着深刻的见解。 吴渔夫指出&#xff0c;AI技术的引入极大地降低了游…

游戏推广的新篇章:Xinstall助力实现全渠道效果统计与提升

随着游戏市场的日益繁荣&#xff0c;游戏推广已成为各大游戏公司争夺市场份额的关键环节。然而&#xff0c;面对众多推广渠道和复杂的用户行为&#xff0c;如何精准地评估推广效果、优化投放策略&#xff0c;成为了游戏推广人员亟待解决的问题。此时&#xff0c;Xinstall作为一…

绿岛机械加入2024第13届生物发酵展

参展企业介绍 南京绿岛机械设备有限公司是一家专注于碟式分离机领域的生产服务型企业。公司以多年从事离心分离设备的设计和制造经验为基础&#xff0c;通过产品改良和技术革新&#xff0c;从根本上解决了传统碟式分离设备的固有技术缺陷&#xff0c;增强了控制系统的安全性和…

2024第六届环境科学与可再生能源国际会议能源 (ESRE 2024) 即将召开!

2024第六届环境科学与可再生能源国际会议 能源 &#xff08;ESRE 2024&#xff09; 即将举行 2024 年 6 月 28 日至 30 日在德国法兰克福举行。ESRE 2024 年 旨在为研究人员、从业人员和专业人士提供一个论坛 从工业界、学术界和政府到研究和 发展&#xff0c;环境科学领域的专…

【C++初阶】之类和对象(中)

【C初阶】之类和对象&#xff08;中&#xff09; ✍ 类的六个默认成员函数✍ 构造函数&#x1f3c4; 为什么需要构造函数&#x1f3c4; 默认构造函数&#x1f3c4; 为什么编译器能自动调用默认构造函数&#x1f3c4; 自己写的构造函数&#x1f3c4; 构造函数的特性 ✍ 拷贝构造…

在Windows系统上安装多个 Nodejs

前言 在Windows系统安装Nodejs 在Windows系统上安装多个 Nodejs v14.16.1安装位置 D:\sde\nodejs\node-v14.16.1-win-x64 v16.20.2安装位置 D:\sde\nodejs\node-v16.20.2-win-x64 v18.20.0安装位置 D:\sde\nodejs\node-v18.20.0-win-x64 v20.12.0安装位置 D:\sde\nod…

Java毕业设计-基于springboot开发的游戏分享网站平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、后台登录2.1管理员功能模块2.2用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的…

ChatGLM2本地部署方法

chatglm2部署在本地时&#xff0c;需要从huggingface上下载模型的权重文件&#xff08;需要科学上网&#xff09;。下载后权重文件会自动保存在本地用户的文件夹上。但这样不利于分享&#xff0c;下面介绍如何将chatglm2模型打包部署。 一、克隆chatglm2部署 这个项目是chatgl…

“李子园”上榜中国民营企业社会责任优秀案例

日前&#xff0c;由浙江省工商联、浙江工商大学主办&#xff0c;杭州市工商联协办的2024浙江民营企业社会责任暨浙商ESG研讨会在杭州召开&#xff0c;探索民营企业履行社会责任的方法路径和趋势。会上公布了2023年中国民营企业社会责任优秀案例&#xff08;浙江入选企业&#x…

【小尘送书-第十五期】Excel函数与公式应用大全for Excel 365 Excel

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

【Linux】对进程地址空间的理解

一、关于进程地址空间的简单理解 进程地址空间其实是分了很多个区域的&#xff0c;区域划分的本质就是区域内的各个地址都是可以使用的。如同下面这个图所示&#xff1a; 无论是环境变量的地址还是环境变量表的地址&#xff0c;所存放的地址都在栈的上部。这里的已初始化数据和…

浅谈性能测试

本文主要针对WEB系统的性能测试。不涉及具体的执行操作&#xff0c;只是本人对性能测试的一点理解和认识。 性能测试的目的&#xff0c;简单说其实就是为了获取待测系统的响应时间、吞吐量、稳定性、容量等信息。而发现一些具体的性能相关的缺陷&#xff08;如内存溢出、并发处…