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配置,这使得开发…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...

Java中HashMap底层原理深度解析:从数据结构到红黑树优化
一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一,是基于哈希表的Map接口非同步实现。它允许使用null键和null值(但只能有一个null键),并且不保证映射顺序的恒久不变。与Hashtable相比,Hash…...