springboot系列--web相关知识探索六
一、前言
web相关知识探索五中研究了请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。web相关知识探索五中主要研究自定义对象参数数据绑定底层原理。本次主要是研究数据响应与内容协商底层原理。
二、数据响应与内容协商
一、数据响应
接口的数据响应分两种:
一、响应页面:发送一个请求,跳转到指定页面。一般常见于开发单体项目
二、响应数据:发送一个请求,相应相关格式数据。常见于前后端分离项目。
1、响应JSON格式数据
2、响应xml格式数据
3、响应xls数据
4、图片、音视频
5、自定义协议数据
二、响应JSON数据
在前后端分离项目的日常开发中,一般后端返回的都是JSON类型的数据。想要返回JSON类型的数据,就需要在项目中引入Jackson.jar 和在接口上加上@ResponseBody 注解,项目中只需引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>即可。web场景自动引入了json场景
一、JSON数据响应原理
一、设置返回值处理器
在前几篇研究中,处理器适配器类(RequestMappingHandlerAdapter)里面有一个方法invokeHandlerMethod,这个方法提前加载了参数解析器以及返回值处理器。其中返回值处理器有15个,具体如图片所示
二、执行接口方法,获取返回值
三、返回值处理
// ServletInvocableHandlerMethod类里面的方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 请求参数解析,绑定接口参数,执行接口方法。获取到返回值Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);// 设置各种返回的状态。this.setResponseStatus(webRequest);if (returnValue == null) {if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {this.disableContentCachingIfNecessary(webRequest);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 {// 这里就会处理返回值,当前返回值是一个对象,这里就会把对象变为JSON;this.getReturnValueType(returnValue):获取到返回值的类型this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception var6) {if (logger.isTraceEnabled()) {logger.trace(this.formatErrorForReturnValue(returnValue), var6);}throw var6;}}// 请求会返回各种状态,这里会将返回的状态设置到HttpServletResponse当中private void setResponseStatus(ServletWebRequest webRequest) throws IOException {HttpStatus status = this.getResponseStatus();if (status != null) {HttpServletResponse response = webRequest.getResponse();if (response != null) {String reason = this.getResponseStatusReason();if (StringUtils.hasText(reason)) {response.sendError(status.value(), reason);} else {response.setStatus(status.value());}}webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);}}
从 this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);方法进入,里面就是将返回值转换为JSON对象的逻辑,
// HandlerMethodReturnValueHandlerComposite类里面的方法public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 获取能够处理返回值的处理器,returnValue:返回值对象,returnType:返回值类型HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());} else {// 使用获取到的返回值处理器处理返回值handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}}// 获取能够处理返回值的处理器private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {// 判断是否是异步返回值boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);// 遍历返回值处理器,一共15个Iterator var4 = this.returnValueHandlers.iterator();HandlerMethodReturnValueHandler handler;do {do {if (!var4.hasNext()) {return null;}handler = (HandlerMethodReturnValueHandler)var4.next();// 如果是异步返回值,同时这个处理器有时异步处理器就跳出循环} while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));// 判断当前处理器是否支持处理当前返回值} while(!handler.supportsReturnType(returnType));return handler;}
handler.supportsReturnType(returnType)其实返回值处理器就是第一个接口,
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter var1);void handleReturnValue(@Nullable Object var1, MethodParameter var2, ModelAndViewContainer var3, NativeWebRequest var4) throws Exception;
}
然后有多个实现类,每个实现类的都是实现上面两个方法,逻辑都不同,源码里面就是遍历多个实现类,看看那个实现类支持处理当前返回值,就用那个返回值处理器。
原理就是
public boolean supportsReturnType(MethodParameter returnType) {return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class); }AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)这段话,也就是方法标了ResponseBody注解,所以支持处理。
通过循环遍历所有的返回值处理器的supportsReturnType方法就可以知道springmvc到底支持处理那些返回值。sprringmvc能够处理的返回值类型。
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
然后开始调用RequestResponseBodyMethodProcessor这个处理器的handleReturnValue方法,对返回值进行处理。可以处理返回值标了@ResponseBody 注解的
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);// 将原生请求和响应包装一下ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);// 使用消息转换器进行写出操作。这个是最终处理返回值的方法,也就是Person对象转JSON的核心方法this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}
四、内容协商
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)方法
这个方法就是最终处理返回值的方法。主要就是利用MessageConverters(消息转换器) 进行处理 将数据写为json。而这里面就涉及到内容协商原理。
一、什么是内容协商(这里大致说一下,之后再细究)
浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据。
/**T value:返回值数据内容:这里的数据已经绑定到了接口方法的返回值类型当中MethodParameter returnType:接口需要返回的数据类型,也即返回值类型例如这个接口@PostMapping("/test")public Map<String,Object> testEntity(Person person){value:就是Map里面的数据returnType:就是Map类型**/// 使用消息转换器进行写出操作protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {// 返回值数据Object body;// 返回值数据的class对象,Class valueType;// 返回值数据类型:一般和valueType是相同的Object targetType;// 返回值等于字符串进入if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;} else {body = value;valueType = this.getReturnValueType(value, returnType);targetType = GenericTypeResolver.resolveType(this.getGenericType(returnType), returnType.getContainingClass());}if (this.isResourceType(value, returnType)) {outputMessage.getHeaders().set("Accept-Ranges", "bytes");if (value != null && inputMessage.getHeaders().getFirst("Range") != null && outputMessage.getServletResponse().getStatus() == 200) {Resource resource = (Resource)value;try {List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());body = HttpRange.toResourceRegions(httpRanges, resource);valueType = body.getClass();targetType = RESOURCE_REGION_LIST_TYPE;} catch (IllegalArgumentException var19) {outputMessage.getHeaders().set("Content-Range", "bytes */" + resource.contentLength());outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());}}}MediaType selectedMediaType = null;// 从响应请求头中获取媒体类型,如果前面使用了aop等切面技术处理了请求媒体类型,这里就是拿到处理后的媒体类型MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (this.logger.isDebugEnabled()) {this.logger.debug("Found 'Content-Type:" + contentType + "' in response");}// 如果前面有处理过媒体类型,这里就直接拿到媒体类型赋值过去即可selectedMediaType = contentType;} else {// 一般正常请求都是进来这部分逻辑HttpServletRequest request = inputMessage.getServletRequest();List acceptableTypes;try {// 这里就是获取到浏览器发送请求时,请求头携带的Accept字段,里面接受的类型acceptableTypes = this.getAcceptableMediaTypes(request);} catch (HttpMediaTypeNotAcceptableException var20) {int series = outputMessage.getServletResponse().getStatus() / 100;if (body != null && series != 4 && series != 5) {throw var20;}if (this.logger.isDebugEnabled()) {this.logger.debug("Ignoring error response content (if any). " + var20);}return;}// valueType:当前返回值数据是什么类型也就是接口方法的返回值类型,targetType:接口方法返回值类型。一般来说valueType和targetType是相同的。// 整个方法原理就是,当前服务器能够响应什么样的类型,List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList();Iterator var15 = acceptableTypes.iterator();// 其实就是循环遍历浏览器能够支持的媒体类型MediaType mediaType;while(var15.hasNext()) {mediaType = (MediaType)var15.next();Iterator var17 = producibleTypes.iterator();// 然后再里面循环遍历服务器能够支持的媒体类型,拿浏览器能够支持的媒体类型一个个和服务器能够支持的媒体类型进行比较,然后得到两方都能接受的内容类型。可能会有多个while(var17.hasNext()) {MediaType producibleType = (MediaType)var17.next();if (mediaType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (this.logger.isDebugEnabled()) {this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);var15 = mediaTypesToUse.iterator();// 这里其实就是找到浏览器能够接受的类型且服务器能够转换的类型中最合适的一个类型while(var15.hasNext()) {mediaType = (MediaType)var15.next();if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (this.logger.isDebugEnabled()) {this.logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);}}HttpMessageConverter converter;GenericHttpMessageConverter genericConverter;label183: {if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();Iterator var23 = this.messageConverters.iterator();// 这里就开始遍历消息转换器,找到能够转换最佳类型的那个消息转换器。一旦确定了最佳类型,SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理while(var23.hasNext()) {converter = (HttpMessageConverter)var23.next();genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;if (genericConverter != null) {if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {break label183;}} else if (converter.canWrite(valueType, selectedMediaType)) {break label183;}}}if (body != null) {Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMediaTypeNotAcceptableException(this.getSupportedMediaTypes(body.getClass()));}throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}return;}// 拿到需要处理的返回数据,就是接口返回数据body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);if (body != null) {LogFormatUtils.traceDebug(this.logger, (traceOn) -> {return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";});// 响应请求的头部添加信息this.addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {// 拿到执行的消息转换器调用写入操作genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);} else {converter.write(body, selectedMediaType, outputMessage);}} else if (this.logger.isDebugEnabled()) {this.logger.debug("Nothing to write: null body");}}
二、HTTPMessageConverter原理
HTTPMessageConverter消息转换器,这是一个接口,定义了消息转换器的规范,也就是各个方法。
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:canWrite:Person对象转为JSON。canRead:JSON转为Person
十种消息转换器能够处理的数据类型,这里只是每个转换器调用support方法,简单判断了一下接口返回值是否是指定类型的。如果想要进一步判断是否将接口返回值转为期望的媒体类型,还是需要调用canWriter方法
0 - 只支持Byte类型的,也就是只支持接口返回值是byte类型的
1 - String,只支持接口返回值是String类型的,以下同理
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true,jackson2类继承了AbstractGenericHttpMessageConverter类,这个类里面默认写死了为true
8 - true
9 - 支持注解方式xml处理的。
//AbstractJackson2HttpMessageConverter类判断能够转换目标类型的方法public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {// 判断是否支持目标媒体类型if (!this.canWrite(mediaType)) {return false;} else {if (mediaType != null && mediaType.getCharset() != null) {Charset charset = mediaType.getCharset();if (!ENCODINGS.containsKey(charset.name())) {return false;}}ObjectMapper objectMapper = this.selectObjectMapper(clazz, mediaType);if (objectMapper == null) {return false;} else {AtomicReference<Throwable> causeRef = new AtomicReference();// Jackson底层组件判断如果能够处理,就返回trueif (objectMapper.canSerialize(clazz, causeRef)) {return true;} else {this.logWarningIfNecessary(clazz, (Throwable)causeRef.get());return false;}}}}// AbstractHttpMessageConverter类里面方法protected boolean canWrite(@Nullable MediaType mediaType) {// 如果目标类型不为空,循环遍历Jackson能够支持转换的类型,一般有两个application/json和application/*+jsonif (mediaType != null && !MediaType.ALL.equalsTypeAndSubtype(mediaType)) {Iterator var2 = this.getSupportedMediaTypes().iterator();MediaType supportedMediaType;do {if (!var2.hasNext()) {return false;}supportedMediaType = (MediaType)var2.next();// 这里主要就是对比浏览器接受类型和var2那个类型更加匹配} while(!supportedMediaType.isCompatibleWith(mediaType));return true;} else {// 如果目标类型为空,也是支持的return true;}}
// AbstractGenericHttpMessageConverter类里面的方法public final void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {HttpHeaders headers = outputMessage.getHeaders();// 设置默认响应头为目标响应类型this.addDefaultHeaders(headers, t, contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage)outputMessage;streamingOutputMessage.setBody((outputStream) -> {this.writeInternal(t, type, new HttpOutputMessage() {public OutputStream getBody() {return outputStream;}public HttpHeaders getHeaders() {return headers;}});});} else {// 这里就会跳转到jackson里面的writeInternal方法进行json转换,AbstractJackson2HttpMessageConverter,至于具体是如何进行转化的这个就是Jackson的底层原理了this.writeInternal(t, type, outputMessage);// 到这里就完成了转化并且写入到了响应流当中outputMessage.getBody().flush();}}
MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
总结:
一、返回值处理器判断是否支持这种类型返回值 supportsReturnType
二、返回值处理器调用 handleReturnValue 进行处理
三、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
1. 利用 MessageConverters 进行处理 将数据写为json
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
- 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
- 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
相关文章:

springboot系列--web相关知识探索六
一、前言 web相关知识探索五中研究了请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。web相关知识探索五中主要研究自定义对象参数数据绑定底层原理。本次…...

FreeSWITCH 简单图形化界面30 - 使用MYODBC时可能遇到的错误
FreeSWITCH 简单图形化界面30 - 使用MYODBC时可能遇到的错误 测试环境1、 MYODBC 3.51.18 or higher2、分析和解决2.1 解决1,降级MySQL ODBC2.2 解决2,修改FreeSWITCH代码 测试环境 http://myfs.f3322.net:8020/ 用户名:admin,密…...

阿里云物联网的通信方式
阿里云物联网通信的两种方式,一个是物模型(分为服务,事件,属性),一个是自定义topic(要另外设置数据流转) 1.使用产品内的功能定义,(其实也就是Topic中定义好的…...

自由职业者的一天:作为小游戏开发者的真实工作日记
大家好,我是小蜗牛。 在这个快节奏的数字时代,自由职业者的生活往往充满了挑战与机遇。作为一名微信小游戏开发者,我的日常工作并不像人们想象中的那样充满光鲜亮丽的画面,而是由无数的编码、调试和创意碰撞组成的。今天…...
【RL Latest Tech】分层强化学习:Option-Critic架构算法
📢本篇文章是博主强化学习RL领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在…...
分布式数据库
前言 分布式数据库系统(DDBS)包含分布式数据库管理系统(DDBMS)和分布式数据库(DDB)。在分布式数据库系统中,一个应用程序可以对数据库进行透明操作,数据库中的数据分别在不同的…...

MySQL(2)【库的操作】
阅读导航 引言一、创建数据库1. 基本语法2. 创建数据库案例📌创建名为db1的数据库📌创建一个使用utf8字符集的db2数据库📌创建一个使用utf8字符集,并带校对规则的db3数据库 二、字符集和校验规则1. 查看系统默认字符集以及校验规则…...

python pip更换(切换)国内镜像源
国内镜像源列表(个人推荐清华大学的源) 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple阿里云: http://mirrors.aliyun.com/pypi/simple豆瓣: http://pypi.douban.com/simple中国科技大学: https://pypi.mirrors.ustc.e…...

阿里云镜像源无法访问?使用 DaoCloud 镜像源加速 Docker 下载(Linux 和 Windows 配置指南)
🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot 🍃 vue-uniapp-template 🌺 仓库主页: GitCode💫 Gitee …...
使用 BERT 和逻辑回归进行文本分类及示例验证
使用 BERT 和逻辑回归进行文本分类及示例验证 一、引言 在自然语言处理领域中,文本分类是一项至关重要的任务。本文将详细介绍如何结合 BERT 模型与逻辑回归算法来实现文本分类,并通过实际示例进行验证。 二、环境准备 为了运行本文中的代码…...

【skywalking 】监控 Spring Cloud Gateway 数据
使用Spring Cloud 开发,用Skywalking 监控服务,但是Skywalking 默认是不支持 Spring Cloud Gateway 网关服务的,需要手动将 Gateway 的插件添加到 Skywalking 启动依赖 jar 中。 skywalking相关版本信息 jdk:17skywalking&#x…...

SpringWeb
SpringWeb SpringWeb 概述 SpringWeb 是 spring 框架中的一个模块,基于 Servlet API 构建的 web 框架. springWeb 是 Spring 为 web 层开发提供的一整套完备的解决方案。 在 web 层框架历经 Strust1,WebWork,Strust2 等诸多产品的历代更…...
嵌入式刷题(day21)
MySQL和sqlite的区别 MySQL和SQLite是两种常见的关系型数据库管理系统(RDBMS),但它们在特性、使用场景和架构方面有显著的区别: 1. 架构 MySQL:是一个基于服务器的数据库系统,遵循客户端-服务器架构。MySQL服务器运行在主机上,客户端通过网络连接并发送查询。它可以并…...

OpenAI 下一代旗舰模型现身?奥尔特曼亲自辟谣“猎户座“传闻
在人工智能领域最受瞩目的ChatGPT即将迎来两周岁之际,一场关于OpenAI新旗舰模型的传闻再次引发业界热议。然而,这场喧嚣很快就被OpenAI掌门人奥尔特曼亲自澄清。 事件源于科技媒体The Verge的一则报道。据多位知情人士透露,OpenAI可能会在11…...

【C++】STL初识
【C】STL初识 文章目录 【C】STL初识前言一、STL基本概念二、STL六大组件简介三、STL三大组件四、初识STL总结 前言 本篇文章将讲到STL基本概念,STL六大组件简介,STL三大组件,初识STL。 一、STL基本概念 STL(Standard Template Library,标准…...

框架篇补充(东西多 需要重新看网课)
什么是AOP 面向切面编程 降低耦合 提高代码的复用 Spring的bean的生命周期 实例化bean 赋值 初始化bean 使用bean 销毁bean SpringMVC的执行流程 Springboot自动装配原理 实际上就是为了从spring.factories文件中 获取到对应的需要 进行自动装配的类 并生成相应的Bean…...

合约门合同全生命周期管理系统:企业合同管理的数字化转型之道
合约门合同全生命周期管理系统:企业合同管理的数字化转型之道 1. 引言 在现代企业中,合同管理已经不再是简单的文件存储和审批流程,而是企业合规性、风险管理和业务流程的关键环节之一。随着企业规模的扩大和合同数量的增加,传统…...
等保测评与风险管理:识别、评估和缓解潜在的安全威胁
在信息化时代,数据已成为企业最宝贵的资产之一,而信息安全则成为守护这份资产免受侵害的重中之重。等保测评(信息安全等级保护测评)作为保障信息系统安全的重要手段,其核心在于通过科学、规范、专业的评估手段…...

Golang Agent 可观测性的全面升级与新特性介绍
作者:张海彬(古琦) 背景 自 2024 年 6 月 26 日,ARMS 发布了针对 Golang 应用的可观测性监控功能以来,阿里云 ARMS 团队与程序语言与编译器团队一直致力于不断优化和提升该系统的各项功能,旨在为开发者提…...

SpringBoot的开篇 特点 初始化 ioc 配置文件
文章目录 前言SpringBoot发展历程SpringBoot前置准备SpringBoot特点 SpringBoot项目初始化项目启动Springboot的核心概念IOC概念介绍Bean对象通过注解扫描包 例子配置文件 前言 SpringBoot发展历程 最初,Spring框架的使用需要大量的XML配置,这使得开发…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...