mvc 异常处理源码解析(3)

目录

  • 准备
  • 源码跟踪
  • ExceptionHandlerExceptionResolver初始化
    • ExceptionHandlerExceptionResolver注入
    • ExceptionHandlerExceptionResolver中exceptionHandlerAdviceCache初始化
    • ExceptionHandlerMethodResolver中mappedMethods初始化
  • 结尾

准备

  1. 准备一个controller类, 里面抛出一个异常
@RestController
@RequestMapping("/hello")
public class HelloController {@GetMapping("/h")public String hello(HttpServletResponse response) throws ArithmeticException {try {int a = 1 / 0;} catch (Exception e) {throw e;}return "hello";}}
  1. 配置一个全局异常处理类
/*** 异常处理器*/
@RestControllerAdvice
public class BDExceptionHandler {/*** 参数异常* @param e 异常* @return BaseResult*/@ExceptionHandler(ArithmeticException.class)public R doBaseExceptionHandler(ArithmeticException e) {return R.error(e.getMessage());}
}
  1. 接下来访问url, 跟踪源码

源码跟踪

进入到mvc的入口类DispatcherServlet的 doDispatch方法, 可以发现整体逻辑被try catch包围, 当第3步抛出异常之后就会被捕获, dispatchException对象则不为空,最后进入processDispatchResult方法进行处理

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//1.获取处理器链路//2.执行拦截器前置方法//3.调用controller方法, 返回结果集//4.执行拦截器后置方法}catch (Exception ex) {//dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

进入processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//判断异常是否为空, 如果不是空则处理if (exception != null) {//处理ModelAndViewDefiningException异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {//处理其他所有的异常Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}//省略下面代码

进入mv = processHandlerException(request, response, handler, exception);

	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {//this.handlerExceptionResolvers里面默认有两个处理异常的类for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 遍历,判断使用那个类来处理exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {//省略代码return exMv;}throw ex;}

看一下上面for循环里面的this.handlerExceptionResolvers对象的结构 :
DefaultErrorAttributes : 没做任何处理, 只是将异常设置到request对象的属性当中;

HandlerExceptionResolverComposite : 里面有三个异常处理类, 分别的作用是 :

ExceptionHandlerExceptionResolver:基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过 @ControllerAdvice 注解自定义异常处理器,加上@ExceptionHandler注解指定方法所需要处理的异常类型

ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息

DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码和错误信息

原文链接:https://blog.csdn.net/Running666/article/details/130786561

在这里插入图片描述
进入HandlerExceptionResolverComposite类的resolveException(); 遍历上面的三种异常处理类, 判断应该使用哪个处理

	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (this.resolvers != null) {//获取, 遍历for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);if (mav != null) {return mav;}}}return null;}

由于只设置了第一种异常处理方式, 所以异常会由ExceptionHandlerExceptionResolver来处理, 进入handlerExceptionResolver.resolveException()

	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);//主要的异常处理逻辑ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// Print debug message when warn logger is not enabled.if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}logException(ex, request);}return result;}else {return null;}}

进入doResolveException(request, response, handler, ex)方法;

	@Override@Nullableprotected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {//根据controller类和异常来判断由哪个类来处理这个异常ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}//填充参数处理器if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}//填充返回结果处理器if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();ArrayList<Throwable> exceptions = new ArrayList<>();try {//省略代码Object[] arguments = new Object[exceptions.size() + 1];exceptions.toArray(arguments);  // efficient arraycopy call in ArrayListarguments[arguments.length - 1] = handlerMethod;//反射调用异常处理类exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);}catch (Throwable invocationEx) {// Any other than the original exception (or a cause) is unintended here,// probably an accident (e.g. failed assertion or the like).if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}//省略}

主要逻辑就是看如何找到处理对应异常的方法, 进入getExceptionHandlerMethod(handlerMethod, exception);

进入到了ExceptionHandlerExceptionResolver类中, 在这个类里面有两个关键的属性,exceptionHandlerCache和exceptionHandlerAdviceCache, 两个都是map, exceptionHandlerAdviceCache的初始化下面再说,exceptionHandlerCache的初始化想知道的自己去查一下吧

	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =new ConcurrentHashMap<>(64);private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();

exceptionHandlerCache里面存的就是在当前controller里面包含@ExceptionHandler注解的方法, key: controller类, value : 方法包装类

exceptionHandlerAdviceCache里面存的就是标注了@ControllerAdvice注解的类,里面带有@ExceptionHandler注解的方法, key:
@RestControllerAdvice注解标注的类, value: 方法包装类

	@Nullableprotected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;//从exceptionHandlerCache获取异常对应的处理方法, 由于没有配置, 肯定获取不到if (handlerMethod != null) {handlerType = handlerMethod.getBeanType();ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}//遍历全局的异常处理类, 看是否可以获取到对应的处理方法for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method);}}}return null;}

进入Method method = resolver.resolveMethod(exception);, 最终进入到resolveMethodByExceptionType()方法, 在ExceptionHandlerMethodResolver类中

ExceptionHandlerMethodResolver中有两个关键属性mappedMethods,和exceptionLookupCache, 两个也都是map, 两个都是根据异常获取对应的异常处理方法, mappedMethods在程序启动时候初始化, exceptionLookupCache相当于一个缓存,为了提高查询速度

	@Nullablepublic Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {//从缓存中获取处理方法, 如果获取不到,返回null(第一次执行此方法肯定获取不到)Method method = this.exceptionLookupCache.get(exceptionType);if (method == null) {//如果获取不到, 则去mappedMethods获取method = getMappedMethod(exceptionType);//将获取的结果缓存到exceptionLookupCache中this.exceptionLookupCache.put(exceptionType, method);}return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);}

进入getMappedMethod(exceptionType);

	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<>();//遍历所有可以处理的异常, 判断与抛出的异常是否匹配for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {//如果有多个方法可以匹配来处理异常,则进行排序if (matches.size() > 1) {matches.sort(new ExceptionDepthComparator(exceptionType));}//获取第一个方法来处理异常,返回方法return this.mappedMethods.get(matches.get(0));}else {return NO_MATCHING_EXCEPTION_HANDLER_METHOD;}}

到此为止, 已经获取到对应的处理异常的方法了, 在上面的doResolveHandlerMethodException方法中, 最后通过反射调用该方法, 处理异常

接下来讨论一下ExceptionHandlerExceptionResolver类里面的exceptionHandlerCache和exceptionHandlerAdviceCache初始化和
ExceptionHandlerMethodResolver类中的mappedMethods初始化

ExceptionHandlerExceptionResolver初始化

ExceptionHandlerExceptionResolver实现了InitializingBean接口, 猜测初始化肯定在afterPropertiesSet()方法中, 我们至于要查找ExceptionHandlerExceptionResolver是如何注入到springboot容器中的, 及afterPropertiesSet()方法逻辑即可
在这里插入图片描述

ExceptionHandlerExceptionResolver注入

实际上,ExceptionHandlerExceptionResolver并不会成为bean交给Spring容器管理。但是在WebMvcConfigurationSupport初始化过程中,会手动调用afterPropertiesSet()进行默认初始化

还记得上面HandlerExceptionResolverComposite类里面有三个异常处理类吗? 看一下他们是如何注入的

在WebMvcConfigurationSupport类里面的handlerExceptionResolver()方法:

	@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();configureHandlerExceptionResolvers(exceptionResolvers);//如果是空, 在下面的if里面将三个异常处理类添加到集合中if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);//赋值属性, 最后返回composite.setExceptionResolvers(exceptionResolvers);return composite;}

进入到addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);

	protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,ContentNegotiationManager mvcContentNegotiationManager) {//创建ExceptionHandlerExceptionResolver 对象ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}//手动调用afterPropertiesSet()给属性初始化exceptionHandlerResolver.afterPropertiesSet();//添加第一个exceptionResolvers.add(exceptionHandlerResolver);ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);//添加第二个exceptionResolvers.add(responseStatusResolver);//添加第三个exceptionResolvers.add(new DefaultHandlerExceptionResolver());}

所以我们只需要看WebMvcConfigurationSupport是如何注入的就可以了, 实际上他是通过WebMvcAutoConfiguration类来间接注入的
在这里插入图片描述
而WebMvcAutoConfiguration类的注入是通过自动装配来注册的
在这里插入图片描述

ExceptionHandlerExceptionResolver中exceptionHandlerAdviceCache初始化

	@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}

进入initExceptionHandlerAdviceCache()方法

	private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}//获取带有ControllerAdvice注解的类List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}//构建value对象, 存入到exceptionHandlerAdviceCache中//ExceptionHandlerMethodResolver中mappedMethods初始化逻辑也在其中ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}}

ExceptionHandlerMethodResolver中mappedMethods初始化

mappedMethods的初始化逻辑很简单, 在上面的initExceptionHandlerAdviceCache()方法中我们已经找到了带有ControllerAdvice的所有类, 将类的类型作为参数创建了ExceptionHandlerMethodResolver对象, mappedMethods的初始化就在构造方法中

进入ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType) 方法

	public ExceptionHandlerMethodResolver(Class<?> handlerType) {//获取到handlerType类中所有的带有ExceptionHandler注解的方法,for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {//解析ExceptionHandler注解中的属性, 因为可以配置多个异常类型, 所有返回的是集合, 进行遍历for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {//将异常类型作为key, 方法作为value, 存入到mappedMethods中addExceptionMapping(exceptionType, method);}}}private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {List<Class<? extends Throwable>> result = new ArrayList<>();//获取method上的ExceptionHandler注解的所有的异常类型,存入到result中, 返回detectAnnotationExceptionMappings(method, result);if (result.isEmpty()) {for (Class<?> paramType : method.getParameterTypes()) {if (Throwable.class.isAssignableFrom(paramType)) {result.add((Class<? extends Throwable>) paramType);}}}if (result.isEmpty()) {throw new IllegalStateException("No exception types mapped to " + method);}return result;}private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {Method oldMethod = this.mappedMethods.put(exceptionType, method);if (oldMethod != null && !oldMethod.equals(method)) {throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +exceptionType + "]: {" + oldMethod + ", " + method + "}");}}

结尾

上面已经说了到逻辑代码发生异常之后, 我们自定义的异常处理类如何加载处理这些异常(同时包括系统启动时的一些系统异常), 但是如果抛出一种没有处理类的异常会怎么样呢? 下篇文章在讨论一下

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

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

相关文章

9. 微积分 - 导数

文章目录 导数求导实例代码演示:迭代法求解二次函数最小值阶Hi, 大家好。我是茶桁。 我们终于结束了极限和连续的折磨,开启了新的篇章。 不过不要以为我们后面的就会很容易,只是相对来说, 没有那么绕而已。 那么,我们今天开始学习「导数」。 导数 在之前的导论,也就是…

嵌入式岗位笔试面试专栏 - 岗位介绍

文章目录 一、嵌入式岗位的分类二、热门领域及公司三、发展前景四、技能要求沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解嵌入岗位的工作职责 。 一、嵌入式岗位的分类 嵌入式软件工程师大致可以分为两种类型: 应用开发工程师驱动开发工程师应用工程…

ShardingSphere——弹性伸缩原理

摘要 支持自定义分片算法&#xff0c;减少数据伸缩及迁移时的业务影响&#xff0c;提供一站式的通用弹性伸缩解决方案&#xff0c;是 Apache ShardingSphere 弹性伸缩的主要设计目标。对于使用单数据库运行的系统来说&#xff0c;如何安全简单地将数据迁移至水平分片的数据库上…

【重要】NAND Flash基础知识简介

NAND Flash是一种非易失存储介质&#xff08;掉电后数据不会丢失&#xff09;&#xff0c;常见的U盘、TF卡/SD卡&#xff0c;以及大部分SSD&#xff08;固态硬盘&#xff09;都是由它组成的。 本文主要介绍其组成及工作原理。 为了表述方便&#xff0c;后面所说的Flash仅指NAN…

【原创】H3C路由器OSPF测试

网络拓扑图 路由器配置&#xff1a; 路由器1上接了4跟线&#xff0c;分别为这四个接口配置IP地址。 # interface GigabitEthernet0/0/0port link-mode routecombo enable copperip address 2.1.1.2 255.255.255.0 # interface GigabitEthernet0/0/1port link-mode routecombo…

【UI 设计】触摸界面设计

触摸界面设计是一种以触摸操作为主的用户界面设计。以下是一些触摸界面设计的要点&#xff1a; 界面布局&#xff1a;设计简洁、直观的界面布局&#xff0c;使用户可以快速找到所需的功能和信息。避免过于拥挤的布局&#xff0c;保持按钮和菜单的大小适中&#xff0c;以便用户能…

Android DataBinding 基础入门(学习记录)

目录 一、DataBinding简介二、findViewById 和 DataBinding 原理及优缺点1. findViewById的优缺点2. DataBinding的优缺点 三、Android mvvm 之 databinding 原理1. 简介和三个主要的实体DataViewViewDataBinding 2.三个功能2.1. rebind 行为2.2 observe data 行为2.3 observe …

MacOS 为指定应用添加指定权限(浏览器无法使用摄像头、麦克风终极解决方案)

起因&#xff1a;需要浏览器在线做一些测评&#xff0c;但我的 Chrome 没有摄像头/麦克风权限&#xff0c;并且在设置中是没有手动添加按钮的。 我尝试了重装软件&#xff0c;更新系统&#xff08;上面的 13.5 就是这么来的&#xff0c;我本来都半年懒得更新系统了&#xff09…

开始MySQL之路——MySQL存储引擎概念

一、存储引擎概念 MySQL数据库和大多数的数据库不同, MySQL数据库中有一个存储引擎的概念, 针对不同的存储需求可以选择最优的存储引擎。 ​ 存储引擎就是存储数据&#xff0c;建立索引&#xff0c;更新查询数据等等技术的实现方式 。存储引擎是基于表的&#xff0c;而不是基…

error: ‘std::_hypot‘ has not been declared using std::hypot;

Cmake 使用qt的编译器 编译opencv时 执行mingw32-make时出现了错误 本质原因就是 _hypot 没有声明。所以找到对应的文件声明一下 就行了。 E:\*****\Qt5.14.1\Tools\mingw730_64\lib\gcc\x86_64-w64-mingw32\7.3.0\include\c 下面的math.h 文件。 可以看到这个文件有一个…

二、C#—第一个c#程序(2)

&#x1f33b;&#x1f33b; 目录 一、编写第一个C#程序1.1 使用Visual Studio创建c#程序的步骤1.2 编写第一个程序“Hello Word”1.3 c#程序的基本结构1.3.1 c#中的命名空间1.3.2 c#中的类1.3.3 c#中的程序启动器——Main方法1.3.4 c#中的标识符1.3.5 c#中的关键字1.3.6 c#中的…

Yjs + Quill 实现文档多人协同编辑器开发(基础+实战)

前言 多人协同开发确实是比较难的知识点&#xff0c;在技术实现上有一定挑战&#xff0c;但随着各种技术库的发展&#xff0c;目前已经有了比较成熟的解决方案。今介绍 Yjs 基于CRDT算法&#xff0c;用于构建自动同步的协作应用程序&#xff0c;与Quill富文本编辑器&#xff0c…

海康机器人工业相机 Win10+Qt+Cmake 开发环境搭建

文章目录 一. Qt搭建海康机器人工业相机开发环境 一. Qt搭建海康机器人工业相机开发环境 参考这个链接安装好MVS客户端 Qt新建一个c项目 cmakeList中添加海康机器人的库&#xff0c;如下&#xff1a; cmake_minimum_required(VERSION 3.5)project(HIKRobotCameraTest LANG…

OPENCV实现暴力特征匹配

# -*- coding:utf-8 -*- """ 作者:794919561 日期:2023/9/1 """ import cv2 import numpy as np# 读

iPhone 14四款机型电池容量详细参数揭秘

苹果推出的iPhone 14系列与2021系列的设计和外形尺寸相同&#xff08;仅缩小了几分之一毫米&#xff09;&#xff0c;所以这并不奇怪&#xff0c;但电池容量也大致相同。 虽然可能不足以对电池寿命产生可衡量的影响&#xff0c;但也存在微小的差异。不同的是&#xff0c;现在有…

GreenPlum的gpfdist使用与原理流程分析

一、简介 GreenPlum 的数据导入功能作为对数据源的一种扩充&#xff0c;数据导入的方式有&#xff1a; 1、insert 该方式通过 sql 语句&#xff0c;把数据一条一条插入至表中。这种方式&#xff0c;不仅读取数据慢&#xff08;一条一条读取&#xff09;&#xff0c;且数据需要…

【局部活动轮廓】使用水平集方法实现局部活动轮廓方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

uni-app:允许字符间能自动换行(英文字符、数字等)

<template><view class"container"><!-- 这里是你的文本内容 -->{{ multilineText }}</view> </template><style> .container {word-break: break-all; } </style>例如&#xff1a; <template><view class"…

三、定长内存池

三、定长内存池 我们知道申请内存使用的是malloc&#xff0c;malloc其实就是一个通用的大众货&#xff0c;什么场景下都可以使用&#xff0c;而什么场景下都可以用就意味着什么场景下都不会有很高的性能&#xff0c;下面我们就先来设计一个定长内存池作为一个开胃菜&#xff0…

华为云服务

【计算】 【存储】 对象存储服务 OBS 对象存储服务&#xff08;Object Storage Service&#xff0c;OBS&#xff09;是一个基于对象的海量存储服务&#xff0c;为客户提供海量、安全、高可靠、低成本的数据存储能力。 OBS系统和单个桶都没有总数据容量和对象/文件数量的限制…