当前位置: 首页 > news >正文

SpringMVC系列-7 @CrossOrigin注解与跨域问题

背景

前段时间帮同事分析了一个跨域问题,正好系统分析和整理一下。

1.跨域

理解同源策略是理解跨域的前提。同源策略定义如下: 在同一来源的页面和脚本之间进行数据交互时,浏览器会默认允许操作,而不会造成跨站脚本攻击;不同源之间进行限制。
不同源之间形成跨域,包括:协议、域名、端口。http和https,localhost和127.0.0.1也会形成跨域(即使经过域名解析后相同)。
由于浏览器引擎实现了同源策略,即对跨域访问进行了限制,因此存在跨域问题。

注意:注意区分浏览器引擎和V8引擎的区别,浏览器引擎包括解析HTML/JS/CSS和渲染等功能,而V8只是一个JS解析器;因此:浏览器中存在跨域问题,而基于Chrome-V8的Nodejs中不存在跨域问题。

案例说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jqAhIfM-1698924088095)(C:\Users\0216001379\AppData\Roaming\Typora\typora-user-images\1698912345318.png)]

2.浏览器处理跨域步骤

根据请求类型不同,浏览器有不同的处理策略,可以分为简单请求和复杂请求:

2.1 简单请求

满足以下条件的为简单请求:
(1) 方法取值范围:GET,POST,HEAD;
(2) Context-Type取值范围: text/plain, application/x-www-form-urlencoded, multipart/form-data
(3) 不包含自定义头域; 即只能包含HTTP自带的Accept, Accept-Language, Content-Type …
详见: CORS
对于简单请求,浏览器会直接向服务器发送请求。

2.2 复杂请求

简单请求之外的HTTP请求为复杂请求。此时,浏览器会正式请求之前会先发送一个OPTIONS类型的预检请求。
在这里插入图片描述

2.3 跨域请求头域

如果ajax是跨域请求,浏览器收到HTTP请求响应后对响应头进行分析——是否支持跨域:支持-请求正常,否则-抛出异常。
响应头包含以下几个部分:
(1) Access-Control-Allow-Origin
指定哪些域可以访问请求的资源, 多个用逗号分开; 取值为"file://"时,表示只允许来自本地文件系统的跨域请求, 而* 表示允许所有源访问。

(2) Access-Control-Allow-Credentials

取值范围有true和false; 表示是否允许客户端使用认证信息(如cookies、HTTP身份验证等)进行跨域请求。即取值为true时,客户端可以携带认证信息,如cookies,以进行身份验证和个性化等操作。

(3) Access-Control-Allow-Methods

取值范围为HTTP的方法类型,如GET和POST;指定允许的HTTP请求方法,多个使用逗号分隔。

(4) Access-Control-Allow-Headers

这个头域用于指定允许客户端访问的响应头, 多个值用逗号分隔;

例如,Access-Control-Expose-Headers: X-Custom-Header, Content-Type表示允许客户端访问X-Custom-Header和Content-Type响应头。
上述4个属性是浏览器判断是否跨域的依据。

注意:当指定多个Access-Control-Allow-Origin时,浏览器会报错如下:

Access to XMLHttpRequest at 'http://localhost:8181/a/b/c' from origin 'http://localhost:8182' has been blocked by CORS policy: 
The 'Access-Control-Allow-Origin' header contains multiple values '*, *', 
but only one is allowed.

2.4 跨域解决方式

2.4.1 前端解决跨域

前端可通过使用JSONP和代理服务器方式解决。如 vue项目中使用Axios实现跨域的原理是代理服务器,在vue项目中,通常会使用webpack-dev-server作为开发服务器,它内置了HTTP代理功能。当Axios发出跨域请求时,它会将请求发送到webpack-dev-server的代理服务器上,代理服务器将请求转发到目标服务器。在转发过程中,代理服务器会处理跨域请求,从而绕过浏览器的同源策略限制。

2.4.2 后端-服务器解决跨域

服务端处理跨域问题的核心是在HTTP响应中加入指定的响应头,使得浏览器正常校验跨域。
可通过过滤器(Filter)和SpringMVC的拦截器()来实现。

案例1-使用过滤器Filter:

Filter可以自定义,也可使用开源解决方案:

<dependency><groupId>com.thetransactioncompany</groupId><artifactId>cors-filter</artifactId><version>2.9</version>
</dependency>

配置并注册到web容器中:

<filter><filter-name>CORS</filter-name><filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class><init-param><param-name>cors.allowOrigin</param-name><param-value>*</param-value></init-param><init-param><param-name>cors.supportedMethods</param-name><param-value>GET, POST, HEAD, PUT, DELETE</param-value></init-param><init-param><param-name>cors.supportedHeaders</param-name><param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value></init-param><init-param><param-name>cors.exposedHeaders</param-name><param-value>Set-Cookie</param-value></init-param><init-param><param-name>cors.supportsCredentials</param-name><param-value>true</param-value></init-param>
</filter><filter-mapping><filter-name>CORS</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

案例2-使用拦截器:

// 定义跨域拦截器
public class CrossInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {response.addHeader("Access-Control-Allow-Origin", "*");response.addHeader("Access-Control-Allow-Methods", "*");response.addHeader("Access-Control-Max-Age", "100");response.addHeader("Access-Control-Allow-Headers", "Content-Type");response.addHeader("Access-Control-Allow-Credentials", "false");return true;}
}// 注册拦截器
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CrossInterceptor());}
}

相对于过滤器和拦截器,SpringMVC提供了颗粒度更小的解决方案,使用@CrossOrigin注解即可解决跨域问题。
如下所示:

@RestController
@RequestMapping("/api/crossDemo")
public class CrossController {@CrossOrigin(origins = "*", maxAge = 3600)@RequestMapping(value = "/put", method = RequestMethod.PUT)public String put() {return "success";}
}

@CrossOrigin可注解在方法上对方法生效,也可作用在类上对类中所有方法生效;当类和方法都存在@CrossOrigin注解时,方法上的注解会覆盖类上的注解。

3.@CrossOrigin原理介绍

@CrossOrigin注解本质上是对SpringMVC的调用链添加一个拦截器,在拦截器中对HTTP的响应头进行跨域设置。

3.1 @CrossOrigin注解

@CrossOrigin注解包含以下属性:
[1] value和origins属性:
String[]类型; 指定允许请求源列表; 一般设置为*,表示对所有的网址开放。与Access-Control-Allow-Origin头域保持一致。

[2] allowedHeaders属性:
String[]类型;请求中允许的请求头列表。如果设置成“*”,则表示允许所有的请求头。

[3] exposedHeaders属性:
String[]类型;@CrossOrigin注解的exposedHeaders属性用于指定允许暴露的响应头列表。这个属性主要用于控制客户端(如浏览器)可以访问哪些响应头。如果设置成“*”,则表示允许暴露所有的响应头。

例如,假设我们有一个API接口,需要暴露响应头"Content-Length"给客户端,可以这样设置:

@CrossOrigin(origins = "*", allowedHeaders = "*", exposedHeaders = "Content-Length")

在这个例子中,我们允许来自"http://example.com"的请求访问我们的API,并允许请求头"Content-Type"。同时,我们指定了响应头"Content-Length"可以被客户端访问。在实际的CORS请求中,响应头"Content-Length"将被存储在Access-Control-Expose-Headers列表中,客户端可以通过这个头获取"Content-Length"信息。

默认情况下,暴露的响应头有:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果需要暴露其他的响应头,需要在@CrossOrigin注解中显式指定。

[4] methods属性:
RequestMethod[]类型;methods属性用于指定允许的HTTP方法。它是一个字符串数组,表示允许跨域请求的HTTP方法列表。这个属性主要用于控制哪些HTTP请求方法可以被客户端(如浏览器)使用。与Access-Control-Allow-Methods头域保持一致。如果API接口只允许GET和POST方法进行跨域请求,可以按如下方式进行设置:

@CrossOrigin(origins = "*", allowedHeaders = "*", methods = "GET,POST")

[5] allowCredentials属性:
String类型;与Access-Control-Allow-Credentials头域保持一致,表示是否允许携带认证信息(如cookies、HTTP身份验证等)进行跨域请求。

[6] maxAge属性:
long类型; 预检请求的有效期(单位: 秒),有效期内不必再次发送预检请求,默认是-1。
当maxAge值为-1时,表示预检请求没有有效期限制。即浏览器接收到预检响应后,无论经过多长时间,只要浏览器与服务器之间的连接保持打开状态,都不需要再次发送预检请求。

3.2 项目初始化

RequestMappingHandlerMapping类实现了InitializingBean接口,在初始化阶段会调用afterPropertiesSet钩子方法:

public void afterPropertiesSet() {initHandlerMethods();
}

initHandlerMethods()方法核心是调用register方法进行Controller接口url的注册,该过程会同时设置跨域信息:

public void register(T mapping, Object handler, Method method) {// register url 和 method关系CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}// ...
}

通过initCorsConfiguration方法获取跨域配置,保存在内存(corsLookup属性)中。
initCorsConfiguration方法逻辑如下:

@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {HandlerMethod handlerMethod = createHandlerMethod(handler, method);Class<?> beanType = handlerMethod.getBeanType();// 从Controller类上获取@CrossOrigin注解CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);// 从接口方法上获取@CrossOrigin注解CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);// 没有注解,表示不进行跨域处理if (typeAnnotation == null && methodAnnotation == null) {return null;}CorsConfiguration config = new CorsConfiguration();// 先根据类的注解信息进行构造,再使用方法注解信息覆盖,因此优先级方法高于类updateCorsConfig(config, typeAnnotation);updateCorsConfig(config, methodAnnotation);if (CollectionUtils.isEmpty(config.getAllowedMethods())) {for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {config.addAllowedMethod(allowedMethod.name());}}// 默认设置return config.applyPermitDefaultValues();
}

Note: 通过注解未设置时,applyPermitDefaultValues方法进行默认设置:
allowedOrigins跨域源设置为*, allowedMethods和resolvedMethods设置为GET、HEAD、POST;allowedHeaders设置为*;maxAge设为为1800L, 即30分钟。

3.3 HTTP接口被调用

当请求进入DispatcherServlet的doDispatch方法中:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//...// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//...// 调用拦截器的preHandle方法if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//...// 调用目标Controller接口mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//...
}

getHandlerAdapter(mappedHandler.getHandler())根据被调用的接口获取HandlerAdapter对象,该对象包含一个调用链,@CrossOrigin注解关联的拦截器添加在该链路中。
构造调用链的逻辑如下所示:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);// 根据被调用的Controller接口构造执行链 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);// 向执行链中添加跨域拦截器if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);// 从内存中获取跨域-拦截器对象(上一节中的保存为了这里的获取)CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);config = (config != null ? config.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;
}

至此,@CrossOrigin注解的实现原理已梳理完成。

注意:Spring在不同版本实现有区别(最近定位问题时发现一个因版本升级导致的问题-促使我发现这个问题):
5.2.8.RELEASE中:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);} else {chain.addInterceptor(0, new CorsInterceptor(config));}return chain;
}

4.3.20.RELEASE版本中:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, CorsConfiguration config) {if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);} else {chain.addInterceptor(new CorsInterceptor(config));}return chain;
}

区别在于5.2.8.RELEASE版本将跨域拦截器CorsInterceptor放在了拦截器首部,而4.3.20.RELEASE将CorsInterceptor加在了拦截器尾部。
执行顺序不同,业务上可能会引入问题。

相关文章:

SpringMVC系列-7 @CrossOrigin注解与跨域问题

背景 前段时间帮同事分析了一个跨域问题&#xff0c;正好系统分析和整理一下。 1.跨域 理解同源策略是理解跨域的前提。同源策略定义如下&#xff1a; 在同一来源的页面和脚本之间进行数据交互时&#xff0c;浏览器会默认允许操作&#xff0c;而不会造成跨站脚本攻击&#x…...

[RK-Linux] misc分区详解

misc 其实是英文 miscellaneous 的前四个字母,杂项、混合体、大杂烩的意思。 misc 分区的概念来源于 Android 系统,Linux 系统中常用来作为系统升级时或者恢复出厂设置时使用。 misc 分区的读写:misc 分区在以下情况下会被读写。 Uboot:设备加电启动时,首先启动 Uboot,…...

用户与组管理:如何在服务器系统中管理用户和权限

你是否想过&#xff0c;当你登录到一个服务器系统时&#xff0c;你是如何被识别和授权的&#xff1f;你是否知道&#xff0c;你可以通过创建和管理用户和组来简化和优化你的系统管理工作&#xff1f;你是否想了解一些常用的用户和组管理命令和技巧&#xff1f;如果你的答案是肯…...

【负载均衡】这些内容你需要知道下

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…...

深入理解 Django 中的事务管理

概要 在数据库操作中&#xff0c;事务是确保数据完整性和一致性的关键机制。Django 作为一个强大的 Python Web 框架&#xff0c;提供了灵活而强大的事务管理功能。理解和正确使用 Django 中的事务对于开发高质量的 Web 应用至关重要。本文将深入探讨 Django 的事务管理机制&a…...

springsecurity6配置一

springsecurity6默认的过滤器是UsernamePasswordAuthenticationToken。具体操作步骤如下: 一、定义一个实体实现springsecurity的UserDetails接口 package com.school.information.core.security.entity;import com.alibaba.fastjson.annotation.JSONField; import com.scho…...

四边形不等式优化DP

目录 四边形不等式内容[HNOI2008]玩具装箱解析代码实现 参考资料 四边形不等式内容 TODO [HNOI2008]玩具装箱 解析 满足四边形不等式&#xff0c;决策具有单调性. 对于两个位置 i , j i, j i,j, 对应的最优决策点一定有 o p t [ i ] < o p t [ j ] opt[i] < opt[j]…...

Gin 学习笔记01-数据返回

Gin 数据返回 1、返回 string 和 json2、返回 xml 和 ymal3、返回html4、重定向 1、返回 string 和 json c.String()c.JSON() package mainimport ("github.com/gin-gonic/gin""net/http" )func getJSON(c *gin.Context) {//c.String(http.StatusOK, &qu…...

【AI考证笔记】NO.1人工智能的基础概念

目录 一、什么是智能 1.什么是智能 2.智能的种类 二、什么是人工智能 1.人工智能之父——图灵 2.人工智能的定义 三、人工智能的发展阶段 四、哪些工作要被淘汰掉&#xff1f; 以下部分内容来自于百度智能云人才认证培训讲义&#xff0c;腾讯等也有人工智能类似的讲义&…...

【Exception】npm ERR! code UNABLE_TO_GET_ISSUER_CERT_LOCALLY

Talk is cheap, show me the code. 环境 | Environment kversionOSwindows 11nodev18.14.2npm9.5.0 报错日志 | Error log >npm create vitelatest Need to install the following packages:create-vite5.0.0 Ok to proceed? (y) y npm ERR! code UNABLE_TO_GET_ISSUER_…...

持续集成交付CICD:GitLabCI 通过trigger触发流水线

目录 一、理论 1.GitLabCI 二、实验 1.搭建共享库项目 2.GitLabCI 通过trigger触发流水线 三、问题 1.项目app02未触发项目app01 2.GitLab 报502网关错误 一、理论 1.GitLabCI (1) 概念 GitLab CI&#xff08;Continuous Integration&#xff09;是一种持续集成工具…...

Java 多线程中的sleep()和wait()方法的区别

Java 多线程中的sleep()和wait()方法的区别 1、相同点 sleep()和wait()都可以暂停线程的执行。 2、不同点 所在类不同 sleep()是Thread类的静态方法。 wait()是Object类的方法。 锁释放不同 sleep()是不释放锁的。 wait()是释放锁的。 用途不同 sleep()常用于一定时间内暂停…...

车载以太网-数据链路层-VLAN

文章目录 车载以太网VLAN(Vehicle Ethernet VLAN)车载以太网VLAN帧格式VLAN帧报文VLAN帧报文示例车载以太网VLAN(Vehicle Ethernet VLAN) 车载以太网VLAN(Vehicle Ethernet VLAN)是一种在车辆网络中使用的虚拟局域网技术。它允许在车载以太网网络中创建多个逻辑网络,从…...

【Vue】filter的用法

上一篇&#xff1a; vue的指令 https://blog.csdn.net/m0_67930426/article/details/134599378?spm1001.2014.3001.5502 本篇所使用指令 v-for v-on v-html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"&…...

python web项目导包规范

python web项目导包规范 python 内置的模块通过第三方库安装的模块框架自身提供的模块用户自己定义的模块 如&#xff1a; from __future__ import absolute_import, unicode_literalsfrom debug_toolbar.panels import Panelfrom django.utils.translation import ugettext_…...

AtCoder Beginner Contest 330 题解

目录 A - Counting PassesB - Minimize Abs 1C - Minimize Abs 2D - Counting LsE - Mex and Update A - Counting Passes 原题链接 题目描述 给定N个数和一个整数L&#xff0c;输出大于等于L的数的个数。 public static void solve() throws IOException{int n readInt(), m…...

论文速读《DeepFusion: Lidar-Camera Deep Fusion for Multi-Modal 3D Object Detection》

概括主要内容 文章《DeepFusion: Lidar-Camera Deep Fusion for Multi-Modal 3D Object Detection》提出了两种创新技术&#xff0c;以改善多模态3D检测模型的性能&#xff0c;通过更有效地融合相机和激光雷达传感器数据来提高对象检测的准确性&#xff0c;尤其是在行人检测方面…...

关于前端处理后端轮询的操作 (总结)

使用场景&#xff1a;前端首次发起请求获取数据&#xff0c;若失败则每隔1s发起一次知道成功获取数据为止解决方案&#xff1a; 使用轮询操作&#xff0c;涉及定时器的使用和关闭 &#xff08;使用vue2代码为例) data() {return {pollingResult_en: null, // 处理轮询结果bizI…...

【SpringCloud】设计原则之单一职责与服务拆分

一、设计原则之单一职责 设计原则很重要的一点就是简单&#xff0c;单一职责也就是所谓的专人干专事 一个单元&#xff08;一个类、函数或微服务&#xff09;应该有且只有一个职责 无论如何&#xff0c;一个微服务不应该包含多于一个的职责 职责单一的后果之一就是职责单…...

UDP分片和丢包与TCP效果对比

UDP 分片 与 丢包&#xff0c;UDP 真的比 TCP 高效吗&#xff1f; UDP&#xff08;用户数据报协议&#xff09;和TCP&#xff08;传输控制协议&#xff09;在很多方面都有显著的区别。总体来说&#xff0c;TCP更适合需要可靠传输的应用&#xff0c;例如网页浏览、电子邮件等&a…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...