SpringMVC源码分析(二)启动过程之RequestMappingHandlerMapping分析
a、http请求中的url是如何与对应Handler的即Controller对应method映射的?
在上篇中提到在SpringMVC中,初始化9大内置组件的时候其中有一个组件就是HandlerMapping,在初始化HandlerMapping的时候会加载代码中所有标注了@Controller和@RequestMapping的类到spring容器中,作为一个个bean对象。
关键类RequestMappingHandlerMapping
类图上看出RequestMappingInfoHandlerMapping继承了AbstractHandlerMethodMapping。实现了InitializingBean接口并且实现了afterPropertiesSet方法。所以在spring初始化这个RequestMappingHandlerMapping对象的时候会进入到afterPropertiesSet()中,这个里面会调用父类AbstractHandlerMethodMapping的afterPropertiesSet(),然后调用initHandlerMethods()。在其中会初始化所有的HandlerMethods。
1、initHandlerMethods()
在当前方法中,主要做了几件事:
- 扫描所有的Handler类,获取所有带有@Controller或@RequestMapping注解的类。
- 遍历每个Handler类,获取类中的所有方法。
- 对于每个方法,判断是否存在@RequestMapping注解。
- 如果存在@RequestMapping注解,则解析该注解,获取其中的属性值,如请求路径、请求方法、请求参数等。
- 根据解析到的属性值,生成一个RequestMappingInfo对象,该对象代表了一个请求路径和请求方法的映射关系。
- 将生成的RequestMappingInfo对象与对应的HandlerMethod对象进行关联,形成一个映射关系
- 将该映射关系保存到RequestMappingInfoHandlerMapping中的pathLookup和registry两个Map中。
- pathLookup是一个Map,以请求路径作为键,将对应的RequestMappingInfo对象作为值存储起来,用于后续处理请求时的查找。
- registry是一个Map,以RequestMappingInfo对象作为键,将对应的HandlerMethod对象作为值存储起来,用于后续执行相应的方法。
- 遍历完所有的Handler类和方法后,初始化完成,此时已经将请求路径、请求方法和对应的HandlerMethod对象都保存起来了。
当有实际的请求进来时,RequestMappingHandlerMapping会根据请求的路径和方法,从pathLookup中查找对应的RequestMappingInfo对象。
然后,通过RequestMappingInfo对象从registry中获取对应的HandlerMethod对象,从而执行相应的方法。HandlerMethod对象是在处理请求时动态生成的,它包含了方法的相关信息,如所属的类、方法名、参数列表等。
protected void initHandlerMethods() {if(this.logger.isDebugEnabled()) {this.logger.debug("Looking for request mappings in application context: " + this.getApplicationContext());}//这里是获取应用中所有Object的bean的名字String[] beanNames = this.detectHandlerMethodsInAncestorContexts?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.obtainApplicationContext(), Object.class):this.obtainApplicationContext().getBeanNamesForType(Object.class);String[] var2 = beanNames;int var3 = beanNames.length;//遍历这个含有应用中所有beanName的字符串数组,并得到这个beanName对应的bean的类型for(int var4 = 0; var4 < var3; ++var4) {String beanName = var2[var4];if(!beanName.startsWith("scopedTarget.")) {Class beanType = null;try {//根据这个beanName对应的beanType的类型beanType = this.obtainApplicationContext().getType(beanName);} catch (Throwable var8) {if(this.logger.isDebugEnabled()) {this.logger.debug("Could not resolve target class for bean with name \'" + beanName + "\'", var8);}}//判断这个根据这个bean的类型判断是不是一个handlerif(beanType != null && this.isHandler(beanType)) {this.detectHandlerMethods(beanName);}}}this.handlerMethodsInitialized(this.getHandlerMethods());}
1.1 isHandler()
这个Bean是否含有@Controller注解或@RequestMapping注解,如果是就表示是一个handler
* {@inheritDoc}* Expects a handler to have a type-level @{@link Controller} annotation.*/@Overrideprotected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}
1.2 detectHandlerMethods()
获取这个handler中所有requestMappinng的方法,然后循环去注册该方法与对应requestMapping信息到一个名为registry的一个HashMap中去
protected void detectHandlerMethods(Object handler) {Class handlerType = handler instanceof String?this.obtainApplicationContext().getType((String)handler):handler.getClass();if(handlerType != null) {Class userType = ClassUtils.getUserClass(handlerType);//获取这个handler中有requestMapping的方法//这个methods的Map结构为key是一个Method对象,value是一个RequestMappingInfo对象Map methods = MethodIntrospector.selectMethods(userType, (method) -> {try {return this.getMappingForMethod(method, userType);} catch (Throwable var4) {throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);}});if(this.logger.isDebugEnabled()) {this.logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);}//循环去注册Method与RequestMappingInfo的关系methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);this.registerHandlerMethod(handler, invocableMethod, mapping);});}}
1.2.1 第一步selectMethods()
1、若这个targetType不是一个代理类,就获得它本身的类以及它的接口放入handlerTypes这么一个Set中去。
2、遍历这个handlerTypes,找到用户自己定义的方法并过滤出有requestMapping的方法,并将之塞入一个methodMap中
public static <T> Map<Method, T> selectMethods(Class<?> targetType, MethodIntrospector.MetadataLookup<T> metadataLookup) {LinkedHashMap methodMap = new LinkedHashMap();LinkedHashSet handlerTypes = new LinkedHashSet();Class specificHandlerType = null;//若这个targetType不是一个代理类,就获得它本身的类以及它的接口if(!Proxy.isProxyClass(targetType)) {specificHandlerType = ClassUtils.getUserClass(targetType);handlerTypes.add(specificHandlerType);}handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));Iterator var5 = handlerTypes.iterator();//遍历while(var5.hasNext()) {Class currentHandlerType = (Class)var5.next();Class targetClass = specificHandlerType != null?specificHandlerType:currentHandlerType;//找到用户自己定义的方法并过滤出有requestMapping的方法,并将之塞入一个methodMap中ReflectionUtils.doWithMethods(currentHandlerType, (method) -> {Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);Object result = metadataLookup.inspect(specificMethod);if(result != null) {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);if(bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {methodMap.put(specificMethod, result);}}}, ReflectionUtils.USER_DECLARED_METHODS);}return methodMap;}
1 ReflectUtilsl.doWithMethods(Class<?> clazz, ReflectionUtils.MethodCallback, ReflectionUtils.MethodFilter)
入参1: Class<?> targetType
入参2: MethodCallback 一个方法回调
入参3: MethodFilter方法过滤器
在当前方法中主要做了3件事:
1、首先获取这个Class中所有定义的方法并且将之存入一个methods的Method数组中
2、遍历这个methods数组中的method如果这个mf方法拦截器为空或者这个method与方法拦截器mf的匹配规则对应,就回调mc.doWith方法。这个mc.doWith()就会调用回到去执行doWithMethods()的第二个lamda表达式。在这个表达式中又会继续回掉执行另一个方法。
3、后面我们还发现对这个类的父类和接口都有一个递归调用
其中这个mf方法拦截器就是这个RelectionUtils.USER_DECLARED_METHODS;顾名思义就是用户自己定义的方法,而非继承与Object类的方法什么的。
/*** 执行给定回调操作在给定类和父类(或者给定的接口或父接口)的所有匹配方法*/public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {// Keep backing up the inheritance hierarchy.// 从缓存中获取clazz的所有声明的方法,包括它的所有接口中所有默认方法;没有时就从{@code clazz}中获取,再添加到缓存中,Method[] methods = getDeclaredMethods(clazz, false);// 遍历所有方法for (Method method : methods) {// 如果mf不为null 且 method不满足mf的匹配要求if (mf != null && !mf.matches(method)) {// 跳过该methodcontinue;}try {// 对method执行回调操作mc.doWith(method);}catch (IllegalAccessException ex) {throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);}}// 如果clazz的父类不为null且(mf不是与未在{@code java.lang.Object}上声明的所有非桥接非合成方法匹配的预购建方法过滤器或者clazz的父类不为Objectif (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {// 递归方法// 执行给定回调操作在clazz的父类的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,// 子类和父类发生的相同命名方法将出现两次,除非被mf排查doWithMethods(clazz.getSuperclass(), mc, mf);}// 如果clazz是接口else if (clazz.isInterface()) {// 遍历clazz的所有接口for (Class<?> superIfc : clazz.getInterfaces()) {// 递归方法// 执行给定回调操作在superIfc的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,// 子类和父类发生的相同命名方法将出现两次,除非被mf排查doWithMethods(superIfc, mc, mf);}}}
2 mc.doWith(method)–> 回调3
当执行到这个方法时会回掉执行doWithMethods()中的第二个入参即lamda表达式
method -> {Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);T result = metadataLookup.inspect(specificMethod);if (result != null) {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {methodMap.put(specificMethod, result);}}}
3 metadataLookup.inspect(specificMethod)–> 回调1.2.1
执行到inspect()方法的时候又会继续调用MethodIntrospector.selectMethods()方法中的第二个入参数去执行第二个lamda表达式。
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});
4 getMappingForMethod()
最终执行到getMappingForMethod(),找到这个方法上的RequestMapping,如果这个方法上的requestMapping信息不为空的话就去照这个handler类上面的requestMapping信息然后将之合并.
最后返回一个RequestMappingInfo ;
@Override@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}String prefix = getPathPrefix(handlerType);if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);}}return info;}
createRequestMappingInfo()
RequestMappingInfo 是请求映射信息的封装对象,用来确定请求的URL、请求方法、请求参数等信息
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);RequestCondition<?> condition = (element instanceof Class ?getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);}
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name());if (customCondition != null) {builder.customCondition(customCondition);}return builder.options(this.config).build();}
1.2.2 第二步 registerHandlerMethod()
遍历methods注册handlerMethod
。。。。。省略selectMethods()中的代码
//循环去注册Method与RequestMappingInfo的关系methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);this.registerHandlerMethod(handler, invocableMethod, mapping);});}
1 registerHandlerMethod()
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);}
2 register()
通过handler与method创建HandlerMethod对象;确保requestMapping唯一映射一个method, 最后注册requestMappingInfo与对应handlerMethod的关系。
public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {//创建HandlerMethod对象,这个对象包含了handler与method的信息HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);//确保同一个requestMapping唯一映射一个method// 例如:url路径 /aaa/bbb 只能对应methodA 不能对应对应methodBthis.assertUniqueMethodMapping(handlerMethod, mapping);if(AbstractHandlerMethodMapping.this.logger.isInfoEnabled()) {//SpringBoot项目或者SpringMVC项目启动的时候控制台上输出的就是这个AbstractHandlerMethodMapping.this.logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);}//注册requestMapping与HandlerMethodInfo的关系this.mappingLookup.put(mapping, handlerMethod);List directUrls = this.getDirectUrls(mapping);Iterator name = directUrls.iterator();while(name.hasNext()) {String corsConfig = (String)name.next();this.urlLookup.add(corsConfig, mapping);}String name1 = null;if(AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {name1 = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);this.addMappingName(name1, handlerMethod);}CorsConfiguration corsConfig1 = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);if(corsConfig1 != null) {this.corsLookup.put(handlerMethod, corsConfig1);}this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directUrls, name1));} finally {this.readWriteLock.writeLock().unlock();}}
相关文章:

SpringMVC源码分析(二)启动过程之RequestMappingHandlerMapping分析
a、http请求中的url是如何与对应Handler的即Controller对应method映射的? 在上篇中提到在SpringMVC中,初始化9大内置组件的时候其中有一个组件就是HandlerMapping,在初始化HandlerMapping的时候会加载代码中所有标注了Controller和RequestMap…...

KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(7)
接前一篇文章:KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(6) 上一回讲到了drm_internal_framebuffer_create函数中的framebuffer_check函数。讲解了该函数的参数检查部分中的第二部分,本回对于该函数余下部分进行解析。 为了便于理解,再次贴出framebuffer_ch…...

2023 年 Arm A-Profile 架构发展
随着人工智能 (AI) 的兴起和安全威胁的加剧,计算需求不断发展,作为世界设备核心的基础计算架构也必须不断发展。这就是为什么我们的工程团队向普遍存在的 Arm 架构添加新功能和技术,然后软件团队确保软件尽可能无缝地适应这些未来的功能和技术。 Arm架构是如何开发的 Arm …...

2023年09月 C/C++(五级)真题解析#中国电子学会#全国青少年软件编程等级考试
C/C++编程(1~8级)全部真题・点这里 Python编程(1~6级)全部真题・点这里 第1题:红与黑 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。 时间限…...

CentOS系统/root根目录扩容(扩展逻辑卷)
具体操作步骤 1、查看本机磁盘环境挂载情况 2、添加磁盘分区 3、开始扩容 4、同步到文件系统 1、查看本机磁盘环境挂载情况 [rooticon ~]# df -lh 可以看到/dev/mapper/centos-root 路径下容量为50G,我们要给这个路径下的容量扩容:[rooticon ~]# lsblk…...

苍穹外卖(三) 员工分页及技术实现细节
2. 员工分页查询 2.1 需求分析和设计 2.1.1 产品原型 2.1.2 接口设计 2.2 代码开发 2.2.1 设计DTO类 根据请求参数进行封装 2.2.2 封装PageResult 后面所有的分页查询,统一都封装为PageResult对象。 员工信息分页查询后端返回的对象类型为: Result 2.…...

二进制部署MySQL8.0
1、下载MySQL官方包 ## 下载MySQL [rootlocalhost ~]# wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz2、解压并移动安装包 # 解压安装包 [rootlocalhost ~]# tar xf mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz# 移动 mv…...

全力以赴,火山引擎边缘云代表团出战亚运会
END 未来,火山引擎边缘云赛事阵容将继续全力以赴,通过领先、可信赖的云和智能技术,助力游戏行业呈现更加精彩的竞技赛事。...

WPF页面向后端传参
WPF页面(前端)向后端传参 1、编写一个Button,绑定后端命令,并传递参数: <ButtonWidth"100"Command"{Binding SendCommand}"CommandParameter"{Binding ElementNameSendMessage, PathTex…...

PyTorch 入门
一、说明 深度学习是机器学习的一个分支,其中编写的算法模仿人脑的功能。深度学习中最常用的库是 Tensorflow 和 PyTorch。由于有各种可用的深度学习框架,人们可能想知道何时使用 PyTorch。以下是人们更喜欢使用 Pytorch 来完成特定任务的原因。 Pytorch…...

微信自动批量添加好友的方法
在现在的营销中微信已成为一种重要的沟通方式。微信目前是没有自动批量添加好友的功能,需要运营者一个一个手动去添加,这样太过于浪费时间,并且加频繁了还容易被封号,今天给大家介绍几种手动批量加好友的方式以及怎么借助第三方软…...

[网鼎杯 2018]Comment git泄露 / 恢复 二次注入 .DS_Store bash_history文件查看
首先我们看到账号密码有提示了 我们bp爆破一下 我首先对数字爆破 因为全字符的话太多了 爆出来了哦 所以账号密码也出来了 zhangwei zhangwei666 没有什么用啊 扫一下吧 有git git泄露 那泄露看看 真有 <?php include "mysql.php"; session_start(); if(…...

生态兼容性进一步提升!白鲸开源 WhaleStudio 与火山引擎ByteHouse完成产品互认
数据作为新型生产要素,已快速融入生产、分配、流通、消费和社会服务管理等各环节,深刻改变着生产方式、生活方式和治理方式。越来越多企业也在尝试充分利用数据要素,开辟全新发展路径,进一步实现业务价值提升。 在数字化转型的大…...

iOS 内存管理和优化
对内存管理和拓展有独特的描述 iOS学习-内存管理 比较详细说明内存的关系 iOS 内存管理机制与原理 iOS 内存泄漏排查方法及原因分析 对weak的实现原理描写详细 【iOS】—— weak的基本原理 iOS copy & mutableCopy iOS 深拷贝与浅拷贝 对iOS的浅复制和深复制的深入解释…...

常见工具指令【Vim | GIT | ZIP | UNZIP | IDEA】
VIM 快捷键说明Ctrl U (up)向上翻动半页Ctrl B (back)向上翻动一页Ctrl D (down)向下翻页半页Ctrl F (forward)向下翻动一页 GIT 指令解释git init 使用指定目录作为Git仓库git add filename向资源库添加文件filenamegit rm file从资源库中删除文件git branch 分支名称创…...

中国人民大学与加拿大女王大学金融硕士——顺势而为,掌握人生的方向盘
在这个快速发展的时代,每个人在不断面临全新挑战的同时, 也在时刻接收着各种机会。有人抓住时代红利,实现更新迭代;有人却只能作为旁观者,眼看时代发展,而自己被逐渐淘汰。时代从不会抛下任何一个人&#x…...

Apache Ranger:(二)对Hive集成简单使用
1.Ranger Hive-plugin安装 进入 Ranger 编译生成的目录下 找到 ranger-2.0.0-hive-plugin.tar.gz 进行解压 tar -zxvf ranger-2.0.0-hive-plugin.tar.gz -C /opt/module/ 2.修改配置文件 vim install.properties #策略管理器的url地址 POLICY_MGR_URLhttp://[ip]:6080#组件…...

【angular】实现简单的angular国际化(i18n)
文章目录 目标过程运行在TS中国际化参考 目标 实现简单的angular国际化。本博客实现中文版和法语版。 将Hello i18n!变为中文版:你好 i18n!或法语版:Bonjour l’i18n !。 过程 创建一个项目: ng new i18nDemo在集成终端中打开。 添加本地化包&#…...

Redis之主从复制,哨兵模式,集群
Redis之主从复制,哨兵模式,集群 1、主从复制1.1主从复制概述1.2Redis主从复制作用1.3Redis主从复制流程1.4部署Redis 主从复制 2、哨兵模式2.1哨兵模式原理2.2哨兵模式的作用2.3哨兵模式的结构2.4故障转移机制2.5搭建Redis 哨兵模式 3、Redis集群模式3.1…...

掌动智能浅析Web自动化测试的重要性
在现代Web开发中,确保Web应用程序的质量和稳定性至关重要。Web自动化测试工具成为了开发团队的关键资源,帮助他们自动化测试流程、减少手动劳动,提高测试覆盖率和效率。本文将介绍Web自动化测试的重要性是什么! Web自动化测试的重要性&#x…...

JTS: 12 Descriptions 图形覆盖
这里写目录标题 版本代码Intersection 交集Union 并集Difference 差集SymDifference 补集 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 /*** 图形覆盖操作* author LiHan* 2023年10月12日 19:34:09*/ public class GeometryDescriptions {private final Ge…...

业务安全五重价值:防攻击、保稳定、助增收、促合规、提升满意度
目录 防范各类威胁攻击 保障业务的连续性和稳定性 保障业务的合规性 提升企业营收和发展 提升企业满意度和品牌知名度 2023年暑假被“票贩子”和“黄牛”攻陷。他们利用各种手段抢先预约名额,然后加价出售给游客,导致了门票供不应求的局面ÿ…...

shiro反序列化和log4j
文章目录 安装环境shiro漏洞验证log4j 安装环境 进入vulhb目录下的weblogic,复现CVE-2018-2894漏洞: cd /vulhub/shiro/CVE-2010-3863查看docker-compose的配置文件: cat docker-compose.yml如图,里面有一个镜像文件的信息和服…...

『Linux项目自动化构建工具』make/Makefile
前言 如题可知,make/Makefile为在Linux下的项目自动化构建工具; 在上一篇文章『Linux - gcc / g』c程序翻译过程 中讲解了C/C程序的翻译过程; 而make/Makefile即可以看成,是Makefile在使用gcc/g使在Linux环境下能够更好的高效率的进行项目构建; 在此之前首先要对make/Makefile…...

github提示Permission denied (publickey),如何才能解决
当GitHub提示“Permission denied (publickey)”错误,这通常意味着您的SSH密钥没有被正确地配置。以下是一些常见的解决步骤,帮助您诊断和解决该问题: 检查是否已设置SSH密钥: 运行以下命令检查是否存在SSH密钥: bash…...

金x软件有限公司安全测试岗位面试
目录 一、自我介绍 二、你是网络空间安全专业的,那你介绍下网络空间安全这块主要学习的东西? 三、本科专业是网络工程,在嘉兴海视嘉安智城科技有限公司实习过,你能说下干的工作吗?(没想到问的是本科实习…...

c语言之strlen函数使用和实现
文章目录 前言一、strlen函数使用二、实现方法 前言 c语言之strlen函数使用和实现 一、strlen函数使用 strlen函数返回的是在字符串中的个数,但不包含字符串结束符’\0’ #include<stdio.h> #include<string.h> int main() {char str1[] "abcd…...

网络初识(JAVA EE)
文章目录 一、网络发展史二、网络通信基础三、协议分层四、封装和分用 一、网络发展史 独立模式:计算机之间相互独立,每个终端都各自持有客户数据,且当处理一个业务时,按照业务流程进行 网络互连:将多台计算机连接在一…...

kantts docker化
kan-tts docker本地化 环境安装 下载docker镜像(python3.8的) registry.cn-hangzhou.aliyuncs.com/modelscope-repo/modelscope:ubuntu20.04-cuda11.8.0-py38-torch2.0.1-tf2.13.0-1.9.2 安装基础模型 pip install modelscope 安装语音模型 pip install "modelscope…...

Axure RP医疗在线挂号问诊原型图医院APP原形模板
医疗在线挂号问诊Axure RP原型图医院APP原形模板,是一款原创的医疗类APP,设计尺寸采用iPhone13(375*812px),原型图上加入了仿真手机壳,使得预览效果更加逼真。 本套原型图主要功能有医疗常识科普、医院挂号…...