第三十八章 Spring之假如让你来写MVC——适配器篇
Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
第三部分——事务篇
第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
文章目录
- Spring源码阅读目录
- 第一部分——IOC篇
- 第二部分——AOP篇
- 第三部分——事务篇
- 第四部分——MVC篇
- 前言
- 尝试动手写IOC容器
- 第三十四版 适配器
- `request`参数
- 参数类型转换
- 注解适配器
- 改造`DispatcherServlet`
- 测试
- 总结
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第三十七章 Spring之假如让你来写MVC——控制器篇 中,A君 已经完成了 控制器 部分的功能了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 已经完成了 控制器 部分的功能了 。。。
第三十四版 适配器
“A君 呐,你用if..else
来判断调用哪个处理器就很灵性了。” 老大 感叹道
“额。” A君 不知道怎么结果话茬,只能装傻充愣了
“这种拓展性太低了,后边还有其他处理器的实现方式呢。这样子往后稍微发展下,你的代码就变成了传说中的 屎山代码。还有一点,就是关于请求参数的处理,要知道一个请求可以携带很多参数的,而你现在这个实现,用户只能去request
里获取,这一点用户体验非常的糟糕。去把这两部部分内容处理下吧。” 说完,老大 就没再说什么了
看到 老大 没有继续往下说的意思,看来是要自己发挥。A君 默默的退了出去,准备着手干活了
对于这种类型适配器,A君 其实并不算陌生,早在之前实现 IOC容器 的时候,A君 就已经接触过,只不过当时是根据属性类型找到对应的转换器进行类型转换。而这次,只是改成根据不同的 控制器 找到对应的 适配器 而已,其本质本无实际上的区别。那么 适配器 的接口就好定义了,必然存在着两个方法,一个是能不能转换,另一个是进行转换。基本盘有了,接下来就要考虑特殊性了,由于HTTP
中定义了Last-Modified
。其规范如下:
这个属性跟缓存息息相关,如果没有超过对应时间,则服务器可以直接不处理,返回上次结果。那么整体的接口定义就出来了,A君 新增HandlerAdapter
接口,代码如下:
/*** 控制器的适配器,找到对应的控制器*/
public interface HandlerAdapter {/*** 是否可以转换** @param handler* @return*/boolean supports(Object handler);/*** 处理请求** @param request* @param response* @param handler* @return* @throws Exception*/Object handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;/*** 如果没有默认实现,默认返回-1* 这个方法放在这里感觉并不合适,LastModified和适配器并有什么关系,违反了接口单一原则,并不符合Spring一贯的作风* 新版本已经弃用,这里考虑到老项目** @param request* @param handler* @return*/@Deprecatedlong getLastModified(HttpServletRequest request, Object handler);
}
Last-Modified
的处理方法放在这里感觉并不合适,Last-Modified
和适配器本身并没有什么关系,违反了接口单一原则。按理说,适配器只负责找到处理对应的控制器进行处理,并不会去关心Last-Modified
这些东西,此处的设计不符合Spring一贯的作风,不知道当初是基于什么考虑才如此设计,这里为了贴近Spring,所以也加上getLastModified
方法。在Spring5.3.9后续版本中,该方法已经标记成弃用
好了,接口出来后,先挑个简单的练练手,A君 盯上了Controller
和Servlet
,原因也简单,实现了对应接口的,其实是最好处理的,只需要强转后调用该接口方法就行了。这里 A君 就以Controller
适配器为例,新增SimpleControllerHandlerAdapter
类,代码如下:
/*** Controller相关接口适配*/
public class SimpleControllerHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof Controller);}@Overridepublic Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);}@Override@SuppressWarnings("deprecation")public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);}return -1L;}
}
简单过后,A君 就开始头疼了,简单之所以简单,是因为实现了对应接口,方法参数、返回值都是固定的。而基于注解的 控制器,这两个没有一个是能确定的,不能确定就意味着:需要框架去推算匹配,那么参数处理就会变得异常的麻烦。没办法,只能先易后难。A君 谨遵 老大 的教诲,对于有公共内容的,提取出来一个抽象类。于是,A君 新增AbstractHandlerMethodAdapter
类,代码如下:
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {@Overridepublic final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}@Overridepublic final Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);}protected abstract Object handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;protected abstract boolean supportsInternal(HandlerMethod handlerMethod);@Override@SuppressWarnings("deprecation")public final long getLastModified(HttpServletRequest request, Object handler) {return getLastModifiedInternal(request, (HandlerMethod) handler);}@Deprecatedprotected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);}
简单的完成了,接下来就得考虑困难的事了,当务之急,就是要解决参数匹配的问题,不然 注解适配类 无从实现
request
参数
要想自动适配 控制器 参数,A君 得先知道哪些可以成为入参,也就是一个request
请求中,哪些可以作为 控制器 的参数。A君 在翻阅 Servlet规范 时,看到这些东西,如下:
- 请求参数:
- 属性:
3.头:
那么正常情况下,request
中:属性(Attributes)、请求头(Headers)、请求参数(Params) 都可以作为 控制器 的入参。明白参数从哪里之后,就可以先把这部分东西抽象化了。这里要注意的是,虽然 A君 一直以 Servlet规范 作为例子,实际上在Web环境下,不仅仅 Servlet 一种规范,还有很多不同的规范,像:WebSocket、 WebFlux、Portlet 等,所以这里定义接口,更是为了通用性,适配这些规范。A君 定义 RequestAttributes
接口,用来封装 属性(Attributes) 的相关操作。代码如下:
/*** request请求属性*/
public interface RequestAttributes {int SCOPE_REQUEST = 0;int SCOPE_SESSION = 1;/*** request作用域*/String REFERENCE_REQUEST = "request";/*** session作用域*/String REFERENCE_SESSION = "session";Object getAttribute(String name, int scope);void setAttribute(String name, Object value, int scope);void removeAttribute(String name, int scope);String[] getAttributeNames(int scope);/*** 注册回调** @param name* @param callback* @param scope*/void registerDestructionCallback(String name, Runnable callback, int scope);/*** 处理引用类型** @param key* @return*/Object resolveReference(String key);/*** 获取SessionId** @return*/String getSessionId();/*** 获取Session同步锁** @return*/Object getSessionMutex();}
这个接口只是规定了 属性(Attributes) 的相关操作,显然是远远不够的,还需要 请求头(Headers)、请求参数(Params) 的相关操作,于是,A君 对其进行拓展,定义WebRequest
接口。代码如下:
public interface WebRequest extends RequestAttributes {/*** 获取请求头** @param headerName* @return*/String getHeader(String headerName);String[] getHeaderValues(String headerName);Iterator<String> getHeaderNames();String getParameter(String paramName);String[] getParameterValues(String paramName);Iterator<String> getParameterNames();Map<String, String[]> getParameterMap();Locale getLocale();String getContextPath();String getRemoteUser();Principal getUserPrincipal();boolean isUserInRole(String role);boolean isSecure();/*** 检查资源是否过期** @param lastModifiedTimestamp* @return*/boolean checkNotModified(long lastModifiedTimestamp);boolean checkNotModified(String etag);boolean checkNotModified(String etag, long lastModifiedTimestamp);String getDescription(boolean includeClientInfo);}
前文提到,由于存在着多种规范,那么对于获取其真实的请求或者响应对象就很有必要了,A君 不可以预判到用户要用哪种协议,所以只能再定义个接口,用来获取真实的请求对象、响应对象。新增NativeWebRequest
接口,代码如下:
public interface NativeWebRequest extends WebRequest {/*** 获取真实请求对象** @return*/Object getNativeRequest();/*** 获取真实响应对象** @return*/Object getNativeResponse();<T> T getNativeRequest(Class<T> requiredType);<T> T getNativeResponse(Class<T> requiredType);
}
接口定义完成之后,接下来就是实现了,虽然存在着多种规范,但 A君 并不太关心,起码现在不用关心,这里是 MVC 的主场。A君 只关注 Servlet 的实现,那问题就简单了,这些东西只需要从 Servlet容器 中获取就行了。A君 新增ServletWebRequest
类,代码如下:
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {@Overridepublic String getParameter(String paramName) {return getRequest().getParameter(paramName);}@Overridepublic String getHeader(String headerName) {return getRequest().getHeader(headerName);}//省略其他方法
}
参数类型转换
解决了参数的来源问题,现在就可以开始进行参数的转换了。要知道request
请求过来的参数可只有字符串类型或字节流,字节流现在不在考虑的范围之内,那还需要把字符串转成 控制器 对应的类型才行。所谓自动适配,无非就是把请求参数转成方法参数罢了。转换接口依旧是那两板斧,能处理吗?进行处理!玩不出什么花来。于是,A君 照猫画虎,定义HandlerMethodArgumentResolver
接口,代码如下:
/*** 参数转换接口*/
public interface HandlerMethodArgumentResolver {boolean supportsParameter(MethodParameter parameter);Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception;
}
接着就是实现了,类那么多,A君 还没颠到全部实现的地步,只要实现主流的类就行了,剩余的部分嘛?就看用户自己发挥了。A君 决定先从两个最重要的参数开始,那就是:request
、response
。两个都是类似的,这里就以request
为例,A君 新增ServletRequestMethodArgumentResolver
类,那么问题来了,要支持那些类型呢? 对于Web来说,request
可谓是举足轻重,主要是从request
可以拿到太多的东西了,比如:ServletRequest
、HttpSession
、Principal
、InputStream
等。那么能从request
中获取的东西,就是ServletRequestMethodArgumentResolver
支持的类型。代码如下:
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType);}@Overridepublic Object resolveArgument(MethodParameter parameter,NativeWebRequest webRequest) throws Exception {Class<?> paramType = parameter.getParameterType();if (WebRequest.class.isAssignableFrom(paramType)) {if (!paramType.isInstance(webRequest)) {throw new IllegalStateException("Current request is not of type [" + paramType.getName() + "]: " + webRequest);}return webRequest;}if (ServletRequest.class.isAssignableFrom(paramType)) {return resolveNativeRequest(webRequest, paramType);}return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));}
}
这还是很简单的,一堆if...esle
就行了,A君 感叹道。接着还需要支持下基本类型,首先要考虑的是如何从request
中获取到参数,HTTP
请求只有字符串或字节流,按照类型匹配显然是不合适的。现在 参数转换器 要做的事情,就是根据名称从request
对应的值,转成 控制器 参数的类型:
A君 先定义一个NamedValueInfo
类,用以包装参数名相关信息。代码如下:
protected static class NamedValueInfo {private final String name;private final boolean required;private final String defaultValue;public NamedValueInfo(String name, boolean required, String defaultValue) {this.name = name;this.required = required;this.defaultValue = defaultValue;}}
接着,A君 思考了下:根据方法参数名(方法参数名的获取在AOP 相关章节中有过说明,这里就不在进行赘述了),去request
中获取值,不论是 属性(Attributes)、请求头(Headers)、请求参数(Params) 都是从 request
中获取的,只是地方不一样罢了,那这就可以提取一个抽象类了。于是,A君 定义AbstractNamedValueMethodArgumentResolver
类,代码如下:
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic final Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {/*** 获取目标参数名*/NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();Object resolvedName = namedValueInfo.name;if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}/*** 从request获取值,由子类实现*/Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = namedValueInfo.defaultValue;} else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = namedValueInfo.defaultValue;}handleResolvedValue(arg, namedValueInfo.name, parameter, webRequest);return arg;}//省略其他方法
}
好了,抽象类已经抽取完毕,那么 属性(Attributes)、请求头(Headers)、请求参数(Params) 都大同小异了,只需要去各自的区域取值就行了。A君 这里以 请求参数(Params) 为例,新增RequestParamMethodArgumentResolver
类,代码如下:
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolverimplements UriComponentsContributor {@Overridepublic boolean supportsParameter(MethodParameter parameter) {if (parameter.hasParameterAnnotation(RequestParam.class)) {/*** map类型*/if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));} else {return true;}} else {/*** 是否是Optional*/parameter = parameter.nestedIfOptional();if (this.useDefaultResolution) {return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());} else {return false;}}}@Overrideprotected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);Object arg = null;if (arg == null) {String[] paramValues = servletRequest.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;}//省略其他方法
}
好了,现在都准备好了,剩下的就是把这些参数处理器进行一下整合,方便别人使用。A君 在定义HandlerMethodArgumentResolverComposite
类,这个不负责具体实现,而是调用别的类来实现转换,类似于一个管理者的角色。代码如下:
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =new ConcurrentHashMap<>(256);@Overridepublic boolean supportsParameter(MethodParameter parameter) {return getArgumentResolver(parameter) != null;}private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}@Overridepublic Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" +parameter.getParameterType().getName() + "]. supportsParameter should be called first.");}return resolver.resolveArgument(parameter, webRequest);}
好嘞,现在处理参数部分也完事了,饶了一大圈,总算把参数处理完了,现在可以继续前行了
注解适配器
解决完最麻烦的参数处理之后,剩下的 注解适配器 就没有什么东西了,跟反射调用基本一样了。A君 新增RequestMappingHandlerAdapter
类,代码如下:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {private HandlerMethodArgumentResolverComposite argumentResolvers;protected Object invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);return invocableMethod.invokeAndHandle(webRequest);} finally {webRequest.requestCompleted();}}//省略其他代码
}
改造DispatcherServlet
适配器 弄完之后,A君 想起还需要对DispatcherServlet
进行改造,毕竟当初折腾 适配器 的目的,不就是为了替换DispatcherServlet
中的if...else
吗?改造如下:
- 新增 适配器 类:
- 替换
doDispatch
方法中的if...else
:
测试
现在一切都准备好了,可以进入检验成果的时候了。A君 修改HelloController
,新增一个参数。代码如下:
@Controller
public class HelloController {@RequestMapping("/hello")public String sayHello(HttpServletRequest req, HttpServletResponse resp, String str) {String key = "message";String message = (String) req.getAttribute(key);if (message == null) {message = "";}req.setAttribute(key, message + " V34 HandleMapping! " + str);return "hello";}
}
其他测试代码不需要变动,编写测试代码如下:
@Testpublic void v34() throws Throwable {System.out.println("############# 第三十四版: 适配器篇 #############");Tomcat tomcat = new Tomcat();//设置端口tomcat.setPort(8082);//设置静态资源路径String webApp = new File("src/main/resources/v34").getAbsolutePath();Context context = tomcat.addWebapp("/test/", webApp);tomcat.start();//挂起tomcat.getServer().await();}
测试结果如下:
后台成功接收到参数并显示出来了,说明 A君 的努力并没有白费。OK!这下子总算弄好了,也可以向 老大 交差了
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)
相关文章:

第三十八章 Spring之假如让你来写MVC——适配器篇
Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…...

服务器引导异常,Grub报错: error: ../../grub-core/fs/fshelp.c:258:file xxxx.img not found.
服务器引导异常,Grub报错: error: ../../grub-core/fs/fshelp.c:258:file xxxx.img not found. 1. 故障现象2. 解决思路3. 故障分析4. 案件回溯5. 解决问题 1. 故障现象 有一台服务器业务报无法连接. 尝试用Ping命令发现无法ping通. 通过控制台查看发现有以下报错: error: ..…...
昵称 校验
1. 基本格式校验 1. 长度限制 • 设置最小和最大字符长度:2-20 个字符(常见范围)。 • 避免昵称过短或过长影响显示和识别。 • 示例: • 2 ≤ 长度 ≤ 20:let minLength 2 let maxLength 20 if nickname.count <…...
MATLAB学习笔记目录
MATLAB学习笔记-生成纯音并保存-CSDN博客 MATLAB学习笔记-各种格式之间的转换 - 知乎 MATLAB学习笔记-胞组(cell array)转换为矩阵,cell2mat_matlab如何把元胞数组改为矩阵-CSDN博客MATLAB学习笔记-判断数组、结构体、数值、字符串是否相同…...

基于单片机的语音控制玩具汽车的设计
语音控制小汽车选用了两个单片机、一个语音识别芯片、两个无线收发模块、一个电机驱动模块、两个电机、一个音频解码模块。语音控制端选用了一个语音识别芯片,实现了将声音信号转换成数字信号,再将数据传输给单片机的功能。小车端选用了单片机来控制电机…...

Qt WORD/PDF(五)使用Json一键填充Word表格
关于QT Widget 其它文章请点击这里: QT Widget 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 姊妹篇: 《Qt WORD/PDF(一)使用 QtPdfium库实现 PDF 操作》 《Qt WORD/PDF&#…...
vue3+ts的几个bug调试
由于编译问题,把几个type检查给关闭了,否则错误太多。 1)第一个检查出的问题,拼写错误数组的length,写成了lengh。 2)数组的对象引用。 torStatus Array(8).fill({ ...defaultStatus }) as TorStatus[]…...

DVWA靶场CSRF漏洞通关教程及源码审计
目录标题 CSRFlow源码审计 medium源码审计 high源码审计 impossible源码审计 CSRF low 先修改密码 看到地址栏 复制在另一个网页打开 成功登录 源码审计 没有任何过滤措施,很危险,并且采用了不安全的md5加密 <?phpif( isset( $_GET[ Change ] )…...
前端开发:HTML常见标签
1.注释标签 注释不会显示在界面上 . 目的是提高代码的可读性 . ctrl / 快捷键可以快速进行注释 / 取消注释 . <!-- 我是注释 --> 2.标题标签 有六个 , 从 h1 - h6. 数字越大 , 则字体越小 <h1> hello </h1> //我们所写的csdn的格式中的标题一…...
【机器学习】主动学习-增加标签的操作方法-样本池采样(Pool-Based Sampling)
Pool-Based Sampling Pool-based sampling 是一种主动学习(Active Learning)方法,与流式选择性采样不同,它假设有一个预先定义的未标注样本池,算法从中选择最有价值的样本进行标注,以提升模型的性能。这种…...

【Rust自学】11.9. 单元测试
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 11.9.1. 测试的分类 Rust把测试分为两类,一个是单元测试,一个是集成测试。 单元测试比较小也比较专注ÿ…...
深入理解Web存储机制:Cookie、SessionStorage与LocalStorage的区别
文章目录 前言一、Cookie简介二、SessionStorage简介三、LocalStorage简介四、三者之间的比较五、最佳实践建议结语 前言 随着Web应用程序变得越来越复杂,开发者需要更有效的办法来管理客户端数据。Cookie、SessionStorage和LocalStorage是三种常用的Web存储机制&a…...
SpringBoot之BeanDefinitionLoader类源码学习
该类的作用 Spring 框架中用于加载和解析 Bean 定义的工具类。它主要用于从不同的资源(如 XML 文件、注解、Java 配置类等)中读取 Bean 定义,并将这些定义注册到 Spring 的 BeanFactory 或 ApplicationContext 中 基本属性 //指定的资源pri…...

【芯片封测学习专栏 -- 2D | 2.5D | 3D 封装的区别和联系】
请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 Overview线键合(wire-bonding)封装FOWLP2D封装2.5D 封装硅通孔(TSV)硅中介层无TSV的2.5D 3D封装 Overview 我们先要了解一下&…...

从硬件设备看Linux
一、介绍 DM3730通过各种连接方式连接了各种设备,输入输出设备根据不同的类型大体可 以分为电源管理、用户输人、显示输出、图像采集、存储以及无线设备等。我们可以将DM 3730与这些设备的数据接口分为总线和单一的数据接口总线。总线的显著特点是单个总线上可以连…...

open3d+opencv实现矩形框裁剪点云操作(C++)
👑主页:吾名招财 👓简介:工科学硕,研究方向机器视觉,爱好较广泛… 💫签名:面朝大海,春暖花开! open3dopencv实现矩形框裁剪点云操作(Cÿ…...
git 本地操作
一、git.vscode 撤回本地提交 要在Git中撤销本地的最后一次提交,可以使用以下命令: git reset --soft HEAD^ 这将会撤销最后一次的提交,但是保留工作区以及暂存区的内容,方便重新提交。 如果你想完全撤销最后一次提交…...
PL/SQL语言的文件操作
PL/SQL语言的文件操作 PL/SQL(Procedural Language/SQL)是Oracle公司开发的一种过程化扩展SQL的语言,广泛应用于Oracle数据库的开发和管理。PL/SQL不仅支持SQL指令,还支持过程化编程,例如条件控制、循环控制、异常处理…...
linux lsof 和 fuser命令介绍
lsof 和 fuser 是两个在 Linux 系统中用于查看文件占用情况的重要工具。它们都可以用于查看哪些进程正在使用某些文件、设备或端口。下面是这两个命令的介绍、举例和背景。 lsof (List Open Files) 命令介绍: lsof 命令用于列出当前系统中所有打开的文件以及与之相关的进程。它…...

[Python学习日记-76] 网络编程中的 socket 开发 —— 介绍、工作流程、socket 模块用法和函数介绍
[Python学习日记-76] 网络编程中的 socket 开发 —— 介绍、工作流程、socket 模块用法和函数介绍 简介 socket 介绍 socket 的工作流程及用法 简介 前面在[Python学习日记-75] 计算机基础与网络当中介绍了一大堆基础知识之后我们终于开始进入到网络编程的开发阶段了&#x…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
从实验室到产业:IndexTTS 在六大核心场景的落地实践
一、内容创作:重构数字内容生产范式 在短视频创作领域,IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色,生成的 “各位吴彦祖们大家好” 语音相似度达 97%,单条视频播放量突破百万…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)
注:文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件:STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...
「Java基本语法」变量的使用
变量定义 变量是程序中存储数据的容器,用于保存可变的数据值。在Java中,变量必须先声明后使用,声明时需指定变量的数据类型和变量名。 语法 数据类型 变量名 [ 初始值]; 示例:声明与初始化 public class VariableDemo {publi…...