第三十八章 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之假如让你来写…...
客户端渲染和服务端渲染
二者本质的区别:是在哪完成了 HTML 的拼接,服务端渲染是在服务端拼接,客户端渲染是在客户端拼接。 服务端渲染的优缺点 优点 SEO 友好,服务端渲染更有利于爬虫爬取信息。 更快的首屏渲染,因为 HTML 已经在服务端生…...
《盘古大模型——鸿蒙NEXT的智慧引擎》
在当今科技飞速发展的时代,华为HarmonyOS NEXT的发布无疑是操作系统领域的一颗重磅炸弹,其将人工智能与操作系统深度融合,开启了智能新时代。而盘古大模型在其中发挥着至关重要的核心作用。 赋予小艺智能助手超强能力 在鸿蒙NEXT中…...
软件架构考试基础知识 004:死锁问题
死锁的定义 死锁(Deadlock)是指在多进程系统中,一组进程相互等待对方持有的资源,导致所有相关进程都无法继续执行的状态。这种状态是僵持的,无法自动解除,必须通过外部干预(如重启系统…...
AI学习路线图-邱锡鹏-神经网络与深度学习
1 需求 神经网络与深度学习 2 接口 3 示例 4 参考资料...
Pytorch通信算子组合测试
Pytorch通信算子组合测试 一.背景二.相关链接三.遇到的问题四.操作步骤1.登录服务器2.查看拓扑3.准备测试用例A.准备目录B.用例代码 4.创建docker容器5.查看当前pytorch版本6.运行测试程序 一.背景 测试pytorch通信算子不同配置下的功能及性能测试不同的group组合测试不同的te…...
Android Dex VMP 动态加载加密指令流
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 上一篇【详解如何自定义 Android Dex VMP 保护壳】实现了 VMP 保护壳。 为了进一步加强对 dex 指令的保护,实现指令流加密和动态加载,…...
深度学习blog-剪枝和知识蒸馏
深度学习网络模型从卷积层到全连接层存在着大量冗余的参数,大量神经元激活值趋近于0,将这些神经元去除后可以表现出同样的模型表达能力,这种情况被称为过参数化。因此需要一些技术手段减少模型的复杂性,去除一些不重要的参数和连接…...
13:00面试,13:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到9月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
机器学习笔记合集
大家好,这里是好评笔记,公主 号:Goodnote。本笔记的任务是解读机器学习实践/面试过程中可能会用到的知识点,内容通俗易懂,入门、实习和校招轻松搞定。 笔记介绍 本笔记的任务是解读机器学习实践/面试过程中可能会用到…...
七 rk3568 android 11 ec20 4G驱动移植
一 内核驱动集成 参考:Quectel_LTE&5G_Linux_USB_Driver_V1.0.zip EC20 内核驱动有两个版本 ,一个是 qmi_wwan, 一个是 GOBNet , 这里用的是 qmi_wwan版本 1.1 添加 USBNET 驱动文件 将驱动包里的 qmi_wwan_q.c 拷到 kernel/driver/net/usb/ 下 修改 kernel/dr…...
【Elasticsearch7.11】postman批量导入少量数据
JSON 文件内的数据格式,json文件数据条数不要过多,会请求参数过大,最好控制再10000以内。 {"index":{"_id":"baec07466732902d22a24ba01ff09751"}} {"uuid":"baec07466732902d22a24ba01ff0975…...
NLP三大特征抽取器:CNN、RNN与Transformer全面解析
引言 自然语言处理(NLP)领域的快速发展离不开深度学习技术的推动。随着应用需求的不断增加,如何高效地从文本中抽取特征成为NLP研究中的核心问题。深度学习中三大主要特征抽取器——卷积神经网络(Convolutional Neural Network, …...
45_Lua模块与包
Lua中的模块系统是该语言的一个重要特性,它允许开发者将代码分割成更小、更易于管理的部分。通过使用模块,你可以创建可重用的代码片段,并且可以降低代码间的耦合度。下面我将详细介绍Lua模块的基本概念、语法以及一些实际案例。 1.Lua模块 1.1 模块的基本概念 从Lua 5.1…...
软定时器的原理与创建
目录 问题概述 设计原理 设计实现 一个任务来管理所有在指定的时间、以特定的周期触发某种操作的定时需求。 问题概述 在实际应用中,常常需要周期性或者在指定时间做一件事情。 周期性:在指定的延时开始做某件事情,然后周期性重复执行 一次性…...
【自动化测试】—— Appium安装配置保姆教程(图文详解)
目录 一. 环境准备 二. JDK安装 1. 下载JDK 2. 安装JDK 3. 配置环境 4. 验证安装 三. Android SDK安装 1. 下载Android SDK 2. 安装Android SDK 3. 安装工具 4. 配置环境 5. 验证安装 四. NodeJS安装 1. 下载NodeJS 2. 安装NodeJS 3. 验证安装 4. 安装淘宝镜像…...
穿越火线怀旧服预约网页vue3版本
源码下载地址: https://github.com/superBiuBiuMan/crossfire-old-vue3版权来自穿越火线,项目仅供参考学习!!! 效果 源码下载地址: https://github.com/superBiuBiuMan/crossfire-old-vue3预览地址: https://crossfire.123916.xyz/官网效果: https://www.cfhuodong.com/2025-…...
《Keras3从头开始的图像分类》
Keras3从头开始的图像分类 作者:fchollet创建日期:2020/04/27最后修改时间:2023/11/09描述:在 Kaggle Cats vs Dogs 数据集上从头开始训练图像分类器。 (i) 此示例使用 Keras 3 在 Colab 中查看 • GitHub…...
Apache Hop从入门到精通 第三课 Apache Hop下载安装
1、下载 官方下载地址:https://hop.apache.org/download/,本教程是基于apache-hop-client-2.11.0.zip进行解压,需要jdk17,小伙伴们可以根据自己的需求下载相应的版本。如下图所示 2、下载jdk17(https://www.microsoft…...
Vue.js组件开发-图片剪裁性能优化最佳方案实例
在Vue.js组件开发中,优化图片剪裁性能的最佳方案通常涉及多个方面的综合考虑。以下是一个结合多个优化策略的图片剪裁组件性能优化实例: 1. 组件设计 首先,设计一个简洁且高效的图片剪裁组件,确保其功能明确且易于使用。组件应包…...
React - router的使用 结合react-redux的路由守卫
web端使用路由安装的是 react-router-dom "react-router-dom": "^5.2.0"在组件中使用路由,我们先设置2个路由,分别是首页、关于 // src/components/RouteSample.jsimport React from react; // 引入路由需要的基础模块 import {Bro…...
day09_kafka高级
文章目录 kafka高级今日课程内容核心概念整理Kafka的数据位移offset**为什么 Kafka 的 offset 就像是“书签”?****实际意义** Kafka的基准/压力测试测试生产的效率测试消费的效率 Kafka的分片与副本机制kafka如何保证数据不丢失生产者端Broker端消费者端相关参数 K…...
【MT32F006】MT32F006之通信协议
本文最后修改时间:2025年01月09日 一、本节简介 本文介绍如何使用MT32F006写一个通信协议。 二、实验平台 库版本:V1.0.0 编译软件:MDK5.37 硬件平台:MT32F006开发板(主芯片MT32F006) 仿真器ÿ…...
CMake学习笔记(2)
1. 嵌套的CMake 如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件ÿ…...
访客机的四个功能
访客机,也被称为访客自动登记安全管理系统或访客一体机,是现代安全管理中不可或缺的一部分。它通过整合计算机技术、射频识别技术、指纹生物识别、触摸屏手写技术、文字识别(OCR)技术、热敏打印技术、条码技术、数码摄像技术、自动…...
【Linux系统】—— vim 的使用
【Linux系统】—— vim 的使用 1 vim 的基本概念2 vim 的多模式3 命令模式下的命令集3.1 进入/退出其他模式3.2 光标移动命令集3.3 复制/剪切/粘贴/删除命令集3.4 撤销命令集3.5 查找命令集3.6 替换命令集3.7 进入与退出替换模式 4 批量化编译5 底行模式6 vim 小技巧7 vim简单配…...
华为C语言编程规范总结
1.头文件更改会导致所有直接或间接包含该头文件的的C文件重新编译,会增加大量编译工作量,延长编译时间,因此: 1.1 头文件里尽量少包含头文件 1.2 头文件应向稳定的方向包含 2.每一个.c文件应有一个同名.h文件,…...
深入学习 Python 量化编程
深入学习 Python 量化编程 第一章:Python 基础与量化编程环境搭建 1.1 安装必要的库 首先,你需要安装一些在量化编程中常用的 Python 库。可以通过以下命令安装这些库: pip install numpy pandas matplotlib yfinance backtrader scikit-…...
初识Java3
目录 一.面向对象与面向过程编程区别 二.类 1.类的定义 2.类一般格式 3.类的实例化具体对象 4.this的使用(习惯经常用) 5.this引用 三.对象 1.初始化对象方法 2.构造方法 四.封装 1.封装: 2.拓展“包” (1).包概念 (…...
uniapp 微信小程序内嵌h5实时通信
描述: 小程序webview内嵌的h5需要向小程序实时发送消息,有人说postMessage可以实现,所以试验一下,结果是实现不了实时,只能在特定时机后退、组件销毁、分享时小程序才能接收到信息(小程序为了安全等考虑做了…...
Blazor开发复杂信息管理系统的优势
随着现代企业信息管理需求的不断提升,开发高效、易维护、可扩展的系统变得尤为重要。在这个过程中,Blazor作为一种新兴的Web开发框架,因其独特的优势,逐渐成为开发复杂信息管理系统的首选技术之一。本文将结合Blazor在开发复杂信息…...
【微服务】面试题 5、分布式系统理论:CAP 与 BASE 详解
分布式系统理论:CAP 与 BASE 详解 一、CAP 定理 背景与定义:1998 年由加州大学科学家埃里克布鲁尔提出,分布式系统存在一致性(Consistency)、可用性(Availability)、分区容错性(Part…...
<论文>时序大模型如何应用于金融领域?
一、摘要 本文介绍2024年的论文《Financial Fine-tuning a Large Time Series Model》,论文探索了主流的时间序列大模型在金融领域的微调应用实践,为时序大模型的领域微调提供了参考。 译文: 大型模型在自然语言处理、图像生成以及近期的时间…...
Oracle 表分区简介
目录 一. 前置知识1.1 什么是表分区1.2 表分区的优势1.3 表分区的使用条件 二. 表分区的方法2.1 范围分区(Range Partitioning)2.2 列表分区(List Partitioning)2.3 哈希分区(Hash Partitioning)2.4 复合分…...
安卓硬件加速hwui
安卓硬件加速 本文基于安卓11。 从 Android 3.0 (API 级别 11) 开始,Android 2D 渲染管道支持硬件加速,这意味着在 View 的画布上执行的所有绘图操作都使用 GPU。由于启用硬件加速所需的资源增加,你的应用程序将消耗更多内存。 软件绘制&am…...
【Bluedroid】HFP连接流程源码分析(二)
接上一篇【Bluedroid】HFP连接流程源码分析(一)-CSDN博客分析。本篇主要围绕RFCOMM Connect 与 RFCOMM UA Frame 的处理流程来展开分析。 RFCOMM Connect RFCOMM(Radio Frequency Communication)作为蓝牙协议栈的关键部分&#…...
基于文件系统分布式锁原理
分布式锁:在一个公共的存储服务上打上一个标记,如Redis的setnx命令,是先到先得方式获得锁,ZooKeeper有点像下面的demo,比较大小的方式判决谁获得锁。 package com.ldj.mybatisflex.demo;import java.util.*; import java.util.co…...
java语法知识(二)
1. class文件可以直接拖动到idea中,显示源码。 2.idea快捷键: sout : System.out.println 输出内容.sout :---》 System.out.println(输出内容); psvm: public static void main() 格式化:ctrl altL 复制粘贴:ctrld 3.注释…...
基于Piquasso的光量子计算机的模拟与编程
一、引言 在科技飞速发展的当下,量子计算作为前沿领域,正以前所未有的态势蓬勃崛起。它凭借独特的量子力学原理,为解决诸多经典计算难以攻克的复杂问题提供了全新路径。从优化物流配送网络,以实现资源高效调配,到药物分子结构的精准模拟,加速新药研发进程;从金融风险的…...
导出文件,能够导出但是文件打不开
背景: 在项目开发中,对于列表的查询,而后会有导出功能,这里导出的是一个excell表格。实现了两种,1.导出的文件,命名是前端传输过去的;2.导出的文件,命名是根据后端返回的文件名获取的…...
【动手学电机驱动】STM32-FOC(4)STM32之UART 串口通信
STM32-FOC(1)STM32 电机控制的软件开发环境 STM32-FOC(2)STM32 导入和创建项目 STM32-FOC(3)STM32 三路互补 PWM 输出 STM32-FOC(4)STM32之UART 串口通信 STM32-FOC(6&am…...
RabbitMQ 高可用方案:原理、构建与运维全解析
文章目录 前言:1 集群方案的原理2 RabbitMQ高可用集群相关概念2.1 设计集群的目的2.2 集群配置方式2.3 节点类型 3 集群架构3.1 为什么使用集群3.2 集群的特点3.3 集群异常处理3.4 普通集群模式3.5 镜像集群模式 前言: 在实际生产中,RabbitM…...
Center Loss 和 ArcFace Loss 笔记
一、Center Loss 1. 定义 Center Loss 旨在最小化类内特征的离散程度,通过约束样本特征与其类别中心之间的距离,提高类内特征的聚合性。 2. 公式 对于样本 xi 和其类别yi,Center Loss 的公式为: xi: 当前样本的特征向量&…...
深度解读微软Speech服务:让语音识别走进现实
大家好,今天我们来探讨一个激动人心的技术话题:微软的语音识别服务如何为我们提供强大的语音识别解决方案,特别是在电话录音中识别出不同的说话人。 场景描绘 想象一下,你有一段电话录音,并需要将其中的多个说话人区分…...
第21篇 基于ARM A9处理器用汇编语言实现中断<三>
Q:怎样编写ARM A9处理器汇编语言代码配置按键端口产生中断? A:使用Intel Monitor Program创建中断程序时,Linker Section Presets下拉菜单中需选择Exceptions。主程序在.vectors代码段为ARM处理器设置异常向量表,在…...
专题 - STM32
基础 基础知识 STM所有产品线(列举型号): STM产品的3内核架构(列举ARM芯片架构): STM32的3开发方式: STM32的5开发工具和套件: 若要在电脑上直接硬件级调试STM32设备,则…...
极客说|Azure AI Agent Service 结合 AutoGen/Semantic Kernel 构建多智能体解决⽅案
作者:卢建晖 - 微软高级云技术布道师 「极客说」 是一档专注 AI 时代开发者分享的专栏,我们邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。关注「极客说」&am…...
【C++指南】模板 深度解析
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 1. 引言 2. 模板的基本概念 3. 函数模板 3.1 定义和语法 3.2 函数模板实例化 3.3 隐式实例化 …...
【traefik】forwadAuth中间件跨namespace请求的问题
前情提要 - fowardAuth鉴权中间件的使用: 【traefik】使用forwardAuth中间件做网关层的全局鉴权 1. 问题 我的 traefik-ingress-controller 所在 namespace: traefik 业务服务所在 namespace: apps 路由与 forwardAuth 中间件配置如下: # 路由 apiV…...
【25考研】西南交通大学软件工程复试攻略!
一、复试内容 复试对考生的既往学业情况、外语听说交流能力、专业素质和科研创新能力,以及综合素质和一贯表现等进行全面考查,主要考核内容包括思想政治素质和道德品质、外语听说能力、专业素质和能力,综合素质及能力。考核由上机考试和面试两部分组成&a…...