SpringMVC源码解析——HTTP请求处理(持续更新中)

在SpringMVC源码解析——DispatcherServlet的逻辑处理中,最后介绍到了org.springframework.web.servlet.DispatcherServlet的doDispatch方法中关于处理Web HTTP请求的核心代码是调用AbstractHandlerMethodAdapter类的handle方法,源码如下:

	/**
	 * 此实现期望处理器为 {@link HandlerMethod} 类型。
	 */
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

	    return handleInternal(request, response, (HandlerMethod) handler);
	}

上面的处理HTTP请求的逻辑主要是调用handleInternal函数来进行处理,源码如下:

	/**
	 * 处理请求并返回视图模型。
	 *
	 * @param request HTTP请求对象
	 * @param response HTTP响应对象
	 * @param handlerMethod 处理方法的对象
	 * @return 视图模型对象
	 * @throws Exception 异常
	 */
	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	    ModelAndView mav;
	    checkRequest(request);

	    // 如果需要的话,在同步块中执行invokeHandlerMethod。
	    if (this.synchronizeOnSession) {
	        HttpSession session = request.getSession(false);
	        if (session != null) {
	            Object mutex = WebUtils.getSessionMutex(session);
	            synchronized (mutex) {
	                mav = invokeHandlerMethod(request, response, handlerMethod);
	            }
	        }
	        else {
	            // 没有可用的HttpSession -> 无需互斥锁
	            mav = invokeHandlerMethod(request, response, handlerMethod);
	        }
	    }
	    else {
	        // 一点都没有要求会话同步...
	        mav = invokeHandlerMethod(request, response, handlerMethod);
	    }

	    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
	        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
	            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
	        }
	        else {
	            prepareResponse(response);
	        }
	    }

	    return mav;
	}

这个函数是一个Java方法,它处理HTTP请求并返回一个ModelAndView对象。它首先检查请求,然后根据需要在同步块中执行invokeHandlerMethod方法。如果需要在会话级别进行同步,则使用互斥锁来确保线程安全。接下来,根据响应是否包含缓存控制头,它会根据处理程序的方法来处理会话属性或将响应准备发送给客户端。最后,它返回一个ModelAndView对象。

/**
 * 调用RequestMapping处理器方法,如果需要解析视图,则准备ModelAndView。
 * 
 * @since 4.2
 * @see #createInvocableHandlerMethod(HandlerMethod)
 */
@SuppressWarnings("deprecation")
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	
	// 创建ServletRequestWebRequest
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	
	// 获取WebDataBinderFactory
	WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
	
	// 获取ModelFactory
	ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
	
	// 创建ServletInvocableHandlerMethod
	ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
	
	// 设置处理器方法的参数解析器
	if (this.argumentResolvers != null) {
		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	
	// 设置处理器方法的返回值解析器
	if (this.returnValueHandlers != null) {
		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}
	
	// 设置DataBinderFactory
	invocableMethod.setDataBinderFactory(binderFactory);
	
	// 设置参数名称发现器
	invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
	
	// 设置方法验证器
	invocableMethod.setMethodValidator(this.methodValidator);
	
	// 创建ModelAndViewContainer
	ModelAndViewContainer mavContainer = new ModelAndViewContainer();
	
	// 添加所有属性到ModelAndViewContainer
	mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
	
	// 初始化模型
	modelFactory.initModel(webRequest, mavContainer, invocableMethod);
	
	// 设置是否在重定向时忽略默认模型
	mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
	
	// 创建AsyncWebRequest
	AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
	
	// 设置异步请求超时时间
	asyncWebRequest.setTimeout(this.asyncRequestTimeout);
	
	// 获取WebAsyncManager
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	
	// 设置TaskExecutor
	asyncManager.setTaskExecutor(this.taskExecutor);
	
	// 设置AsyncWebRequest
	asyncManager.setAsyncWebRequest(asyncWebRequest);
	
	// 注册Callable拦截器
	asyncManager.registerCallableInterceptors(this.callableInterceptors);
	
	// 注册DeferredResult拦截器
	asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
	
	// 如果异步结果已并行处理
	if (asyncManager.hasConcurrentResult()) {
		// 获取并行处理的结果和ModelAndViewContainer
		Object result = asyncManager.getConcurrentResult();
		mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
		asyncManager.clearConcurrentResult();
		
		// 调试日志
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(result, !traceOn);
			return "Resume with async result [" + formatted + "]";
		});
		
		// 将并行处理的结果包装到ServletInvocableHandlerMethod
		invocableMethod = invocableMethod.wrapConcurrentResult(result);
	}
	
	// 调用处理器方法并处理
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	
	// 如果异步处理已开始
	if (asyncManager.isConcurrentHandlingStarted()) {
		// 返回null
		return null;
	}
	
	// 返回ModelAndView
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

这个函数用于调用@RequestMapping注解的处理方法,并准备 ModelAndView对象(如果需要解析视图)。它会执行处理方法,并处理异步结果和模型视图。如果异步处理已经开始,则返回null。 

    /**
     * 调用方法并处理返回值
     *
     * @param webRequest    ServletWebRequest 对象,表示当前请求的上下文
     * @param mavContainer  ModelAndViewContainer 对象,用于处理模型和视图
     * @param providedArgs  提供的参数值
     * @throws Exception 抛出异常
     */
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        // 调用方法并获取返回值
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        
        // 设置响应状态
        this.setResponseStatus(webRequest);
        
        if (returnValue == null) {
            // 如果请求未修改、响应状态为 null 或者已处理请求
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            // 如果响应状态原因文本不为空
            mavContainer.setRequestHandled(true);
            return;
        }
        
        mavContainer.setRequestHandled(false);
        // 断言返回值处理器不为空
        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {
            // 处理返回值
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
            // 如果日志级别为 TRACE,则记录处理返回值时的异常信息
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(this.getReturnValueHandlingErrorMessage("Error handling return value", returnValue), var6);
            }
            
            // 抛出异常
            throw var6;
        }
    }

这个函数是一个公共方法,它调用了其他的方法来处理HTTP请求,并根据处理结果进行相应的操作。首先,它调用了invokeForRequest方法来处理请求并获取返回值。然后,它通过调用setResponseStatus方法设置响应状态。接下来,它根据一些条件判断来决定是否设置请求已处理并返回。如果返回值为null,它会判断请求是否未修改、响应状态是否为null或请求是否已处理,如果满足条件,则设置请求已处理并返回。如果返回值不为null且响应状态原因文本不为空,同样会设置请求已处理并返回。如果以上条件都不满足,则将请求标记为未处理,并通过断言来验证返回值处理器不为空。最后,它调用returnValueHandlers的handleReturnValue方法来处理返回值,并将处理结果类型、模型视图容器和HTTP请求作为参数传递给该方法。如果处理过程中出现异常,它会记录日志并抛出异常。

/**
 * 在给定请求的上下文中解析方法参数值并调用方法。
 * <p>参数值通常通过 {@link HandlerMethodArgumentResolver} 解析。
 * 但是,{@code providedArgs} 参数可以提供要直接使用的参数值,即不进行参数解析的情况。
 * 例如,提供的参数值包括 {@link WebDataBinder}、{@link SessionStatus} 或抛出的异常实例。
 * 在参数解析器之前检查提供的参数值。
 * @param request 当前请求
 * @param mavContainer 本请求的 ModelAndViewContainer
 * @param providedArgs 与类型匹配的"给定"参数,未解析
 * @return 被调用方法的原始返回值
 * @throws Exception 如果找不到合适的参数解析器,或者方法引发了异常,则抛出异常
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("调用方法 '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
					"' 与参数 " + Arrays.toString(args));
		}
		Object returnValue = doInvoke(args);
		if (logger.isTraceEnabled()) {
			logger.trace("方法 [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
					"] 返回 [" + returnValue + "]");
		}
		return returnValue;
}

这个函数用于在给定请求的上下文中解析方法参数值并调用方法。参数值通常通过HandlerMethodArgumentResolver解析,但是提供的参数值可以直接使用,无需解析。提供的参数值在解析器之前进行检查。如果找不到合适的参数解析器或方法引发了异常,则会抛出异常。函数返回调用方法的原始返回值。

	/**
	 * 迭代遍历注册的 {@link HandlerMethodReturnValueHandler} 接口,并调用支持它的处理器。
	 * @throws IllegalStateException 如果找不到合适的 {@link HandlerMethodReturnValueHandler}。
	 */
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	    if (handler == null) {
	        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	    }
	    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		// 将请求标记为已处理
		mavContainer.setRequestHandled(true);
		
		// 创建一个ServletServerHttpRequest对象,用于处理请求
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		
		// 创建一个ServletServerHttpResponse对象,用于处理响应
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// 即使返回值为null,也要尝试继续执行下面的操作。ResponseBodyAdvice可以进行处理。
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

	/**
	 * 使用给定的输出消息写入给定的返回类型。
	 * @param value 要写入到输出消息的值
	 * @param returnType 返回值的类型
	 * @param inputMessage 输入消息。用于检查请求中的`Accept`标头。
	 * @param outputMessage 要写入的输出消息
	 * @throws IOException 发生I/O错误时抛出
	 * @throws HttpMediaTypeNotAcceptableException 当请求中的`Accept`标头无法由消息转换器满足时抛出
	 * @throws HttpMessageNotWritableException 当消息无法被写入时抛出
	 */
	@SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;

		if (value instanceof CharSequence) {
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;
			valueType = getReturnValueType(outputValue, returnType);
			declaredType = getGenericType(returnType);
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					outputValue = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = outputValue.getClass();
					declaredType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}


		List<MediaType> mediaTypesToUse;

		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			mediaTypesToUse = Collections.singletonList(contentType);
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

			if (outputValue != null && producibleMediaTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : requestedMediaTypes) {
				for (MediaType producibleType : producibleMediaTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (outputValue != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
				}
				return;
			}
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
		}

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypesToUse) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
						}
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
									"\" using [" + converter + "]");
						}
					}
					return;
				}
			}
		}

		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

这个Java函数用于将给定的返回值写入到输出消息中。它根据请求中的"Accept"头检查是否可以满足请求,并使用适当的消息转换器将返回值转换为适当的内容类型。如果找不到适合的内容类型,则会引发异常。最后,它将内容写入到输出响应中。

	@Override
	@Nullable
	public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {

		return processBody(body, returnType, contentType, converterType, request, response);
	}

@SuppressWarnings("unchecked")
	@Nullable
	private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {

		for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
			if (advice.supports(returnType, converterType)) {
				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
						contentType, converterType, request, response);
			}
		}
		return body;
	}
/**
 * 该实现通过调用 {@link #addDefaultHeaders} 设置默认头部,然后调用 {@link #writeInternal}。
 */
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
                        HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    final HttpHeaders headers = outputMessage.getHeaders();
    addDefaultHeaders(headers, t, contentType);

    if (outputMessage instanceof StreamingHttpOutputMessage) {
        StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
        streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
            @Override
            public OutputStream getBody() {
                return outputStream;
            }
            
            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }
        }));
    } else {
        writeInternal(t, type, outputMessage);
        outputMessage.getBody().flush();
    }
}
	/**
	 * 返回可以生成的媒体类型。结果的媒体类型如下:
	 * <ul>
	 * <li>请求映射中指定的可生成媒体类型,或者
	 * <li>能够写入特定返回值的配置转换器的媒体类型,或者
	 * <li> `MediaType#ALL`
	 * </ul>
	 * @since 4.2
	 */
	@SuppressWarnings("unchecked")
	protected List<MediaType> getProducibleMediaTypes(
	        HttpServletRequest request, Class<?> valueClass, @Nullable Type declaredType) {

	    Set<MediaType> mediaTypes =
	            (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	    if (!CollectionUtils.isEmpty(mediaTypes)) {
	        return new ArrayList<>(mediaTypes);
	    }
	    else if (!this.allSupportedMediaTypes.isEmpty()) {
	        List<MediaType> result = new ArrayList<>();
	        for (HttpMessageConverter<?> converter : this.messageConverters) {
	            if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
	                if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
	                    result.addAll(converter.getSupportedMediaTypes());
	                }
	            }
	            else if (converter.canWrite(valueClass, null)) {
	                result.addAll(converter.getSupportedMediaTypes());
	            }
	        }
	        return result;
	    }
	    else {
	        return Collections.singletonList(MediaType.ALL);
	    }
	}

 这个函数用于获取可以生成的媒体类型。媒体类型包括在请求映射中指定的可生成媒体类型、能够写入特定返回值的配置转换器的媒体类型,或者所有媒体类型。函数首先从请求中获取可生成媒体类型集合,如果不为空,则直接返回该集合。否则,函数遍历所有消息转换器,判断是否能够写入特定返回值,如果可以,则将支持的媒体类型添加到结果集中。如果无法写入特定返回值,但消息转换器支持写入特定类型的媒体类型,则将支持的媒体类型添加到结果集中。如果没有可生成的媒体类型,则返回包含"(MediaType.ALL)"的单元素集合。

	/**
	 * 使用给定的参数值调用处理方法。
	 */
	protected Object doInvoke(Object... args) throws Exception {
	    ReflectionUtils.makeAccessible(getBridgedMethod());
	    try {
	        return getBridgedMethod().invoke(getBean(), args);
	    }
	    catch (IllegalArgumentException ex) {
	        assertTargetBean(getBridgedMethod(), getBean(), args);
	        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
	        throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
	    }
	    catch (InvocationTargetException ex) {
	        // 解封装...
	        Throwable targetException = ex.getTargetException();
	        if (targetException instanceof RuntimeException) {
	            throw (RuntimeException) targetException;
	        }
	        else if (targetException instanceof Error) {
	            throw (Error) targetException;
	        }
	        else if (targetException instanceof Exception) {
	            throw (Exception) targetException;
	        }
	        else {
	            String text = getInvocationErrorMessage("Failed to invoke handler method", args);
	            throw new IllegalStateException(text, targetException);
	        }
	    }
	}

其中getBridgedMethod()获取的是Controller中相应的函数实例,getBean函数获取对应Controller的实例,运行时的堆栈信息如下:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

04-微服务-Nacos

Nacos注册中心 国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba也推出了一个名为Nacos的注册中心。 1.1.认识和安装Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eureka功能更加丰富&#xff0c;在…

[redis] redis的安装,配置与简单操作

一、缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且…

Matlab二维绘图

低级绘图命令line 有什么点就点哪里&#xff0c;然后连起来&#xff0c;没什么细节&#xff0c;不光滑&#xff0c;所以基本不会用到。 x0:0.2*pi:2*pi; ysin(x); line(x,y);%画一条sin函数线 line([-5,5],[2,2]);%画一条水平线 line([5,5],[0,2]);%画一条竖线 高级绘图命令…

1867_noweb简介

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_org: my learning trip for org-mode 1867_noweb简介 noweb是一个简单可扩展的文学式编程工具&#xff0c;操作简单且不限制编程语言。 主题由来介绍 本质上来说&#xff0c;我对noweb没有太多的了解欲望。但是我…

5.云原生安全之kubesphere应用网关配置域名TLS证书

文章目录 cloudflare配置使用cloudflare托管域名获取cloudflare API Token在cloudflare中配置SSL/TLS kubesphere使用cert-manager申请cloudflare证书安装证书管理器创建Secret资源创建cluster-issuer.yaml创建cert.yaml申请证书已经查看申请状态 部署harbor并配置ingress使用证…

机器学习笔记 - 基于OpenCV+稀疏光流的无监督运动检测

一、简述 在各种高级开源库的帮助下&#xff0c;检测固定摄像机拍摄的运动行为是轻而易举可以实现的&#xff0c;但检测移动的摄像机拍摄的移动物体的运动检测依然是一个复杂的问题。在这里&#xff0c;我们将继续基于稀疏光流&#xff0c;并检测移动的无人机相机的运动。 这里…

2024年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人…

STL标准库与泛型编程(侯捷)笔记2

STL标准库与泛型编程&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCo…

企业网盘全方位解读:热门云存储工具的优势与适用场景

企业网盘无疑是当下最热门的企业协同工具。什么是企业网盘&#xff1f;企业网盘与个人网盘又有什么不同呢&#xff1f;一文全方位解读企业网盘这一热门云存储工具。 什么是企业网盘 企业网盘为企业级文件存储、管理与共享平台&#xff0c;企业团队可以在企业网盘中存储企业文…

2024.1.6 关于 Redis 数据类型 Zset 常用命令

目录 Zset 基本概念 Zset 命令操作 ZADD ZRANGE ZREVRANGE ZCARD ZCOUNT ZRANGEBYSCORE ZPOPMAX BZPOPMAX ZPOPMIN BZPOPMIN ZRANK ZREVRANK ZSCORE ZREM ZREMRANGEBYRANK ZREMRANGEBYSCORE ZINCRBY Zset 基本概念 Set&#xff08;集合&#xff09; 元素具…

ARM笔记-----输入捕获

输入捕获可以对输入的信号的上升沿、下降沿或者双边沿进行捕获&#xff0c;常用的有测量输入信号的脉 宽&#xff0c;和测量 PWM 输入信号的频率和占空比这两种。 输入捕获的大概的原理 当捕获到信号的跳变沿的时候&#xff0c;把计数器 CNT 的值锁存到捕获寄 存器 CCR 中…

各种基础环境搭建

1、Python解释器安装 验证环境变量是否添加成功 winr --> cmd 输入python 会出现的问题 在命令行输入python弹出应用商城 是由于商城的环境变量在python的环境变量前面,移动到最下面就好 C:\Users…\AppData\Local\Microsoft*WindowsApps*

3d全景怎么拍摄?应用领域有哪些?

3d全景技术是综合了VR技术和全景拍摄的一种新型应用技术&#xff0c;通过3D全景技术可以为用户带来720度无死角的观看方式和真实的观看体验&#xff0c;那么3d全景是怎么拍摄制作的呢&#xff1f;应用领域又有哪些呢&#xff1f; 3d全景拍摄制作流程其实不难&#xff0c;常见的…

Linux-文件系统管理实验2

1、将bin目录下的所有文件列表放到bin.txt文档中&#xff0c;并将一共有多少个命令的结果信息保存到该文件的最后一行。统计出文件中以b开头的所有命令有多少个&#xff0c;并将这些命令保存到b.txt文档中。将文档中以p结尾的所有命令保存到p.txt文件中&#xff0c;并统计有多少…

多租户看这一篇就够了

什么是多租户&#xff1f;举个例子&#xff1a;马云、马化腾和刘强东三个人去租房子&#xff0c;他们因为家里经济困难所以勤工俭学&#xff0c;三个人决定合租一套三室一厅的房子&#xff0c;虽然每个人有自己的房间&#xff0c;但是家里的水电、厨房、卫生间和热水器都是大家…

攀登者1 - 华为OD统一考试

OD统一考试 分值: 100分 题解: Java / Python / C++ 题目描述 攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如:[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下…

2、Excel:基础概念、表格结构与常见函数

数据来源&#xff1a;八月成交数据 数据初探 业务背景 数据来源行业&#xff1a;金融行业&#xff08;根据应收利息和逾期金额字段来判断&#xff09; 可以猜测&#xff1a; 业务主体&#xff1a;某互联网金融公司&#xff08;类似支付宝&#xff09;也业务模式&#xff1a;给…

汽车电子行业的 C 语言编程标准

前言 之前分享了一些编程规范相关的文章&#xff0c;有位读者提到了汽车电子行业的MISRA C标准&#xff0c;说这个很不错。 本次给大家找来了一篇汽车电子行业的MISRA C标准的文章一同学习下。 什么是MISRA&#xff1f; MISRA (The Motor Industry Software Reliability Ass…

K8S-应用部署

1 应用管理解读 2 应用部署实践 资源对象管理关系 资源对象管理实践 手工方式&#xff1a; kubectl run pod名称 --imageimage地址资源清单方式: apiVersion: v1 kind: Pod metadata:labels:run: my-podname: my-pod spec:containers:- image: kubernetes-register.sswang.co…

报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法

我起初想要在macOS系统安装pip包&#xff0c;首先在终端安装homebrew&#xff0c;敲了命令&#xff1a;/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错&#xff0c;报错内容&#xff1a;curl: (6) Could not resolve host: raw.…