0%

跨域问题详解:原因与解决方案

在 Web 开发中,跨域是前端与后端交互时常见的问题,其根源是浏览器的同源策略。本文将详细解释跨域的定义、产生原因,并提供多种解决方案,包括 Nginx 代理、JSONP 和后端配置等。

什么是跨域?

同源策略

浏览器的同源策略(Same-Origin Policy)是一种安全机制,限制不同源的网页之间的交互。同源指的是 “协议、域名、端口” 三者完全相同:

URL 1 URL 2 是否同源 原因
http://example.com http://example.com 协议、域名、端口均相同
http://example.com https://example.com 协议不同(http vs https)
http://example.com http://api.example.com 域名不同(主域 vs 子域)
http://example.com:8080 http://example.com:8081 端口不同(8080 vs 8081)

跨域的定义

当一个请求的 “协议、域名、端口” 与当前页面的源不一致时,该请求就是跨域请求。同源策略会阻止跨域请求的成功响应(如 AJAX 请求被拦截),但允许某些标签(如 <script><img>)的跨域加载。

跨域解决方案

方案一:Nginx 代理(推荐)

通过 Nginx 作为中间代理,将前端的跨域请求转发到后端,使浏览器认为请求是同源的,从而绕过同源策略。

配置示例:
阅读全文 »

Linux 交换分区(Swap):虚拟内存的核心实现

Swap(交换分区 / 交换空间)是 Linux 系统中用于扩展内存能力的关键机制,它通过将磁盘空间模拟为 “虚拟内存”,解决了物理内存不足时的系统运行问题。理解 Swap 的工作原理和配置方法,对系统性能优化和稳定性保障至关重要。

Swap 的本质与核心价值

什么是 Swap?

Swap 是 Linux 内核使用的磁盘空间(分区或文件),用于临时存储物理内存中暂时不活跃的数据。当物理内存(RAM)不足时,内核会将这些 “不常用” 的数据转移到 Swap 中,释放物理内存给当前活跃的进程使用。

  • 形象比喻:物理内存是 “高速缓存”,Swap 是 “备用仓库”—— 常用物品放在缓存,暂时不用的移到仓库,需要时再取回来。

Swap 的核心作用

  • 避免 OOM 崩溃:当物理内存耗尽时,Swap 可防止进程因内存不足被内核强制杀死(OOM,Out Of Memory)。
  • 支持内存密集型任务:如数据库服务、虚拟机、大数据处理等,这些任务可能短时间占用远超物理内存的空间。
  • 实现系统休眠:休眠(Hibernate)时,内核会将内存中所有数据写入 Swap,下次开机时从 Swap 恢复状态。

Swap 的工作机制

内存页的 “换入” 与 “换出”

Linux 内存管理以 “页(Page,通常 4KB)” 为单位,Swap 的核心操作是内存页的 “换出”(从物理内存到 Swap)和 “换入”(从 Swap 到物理内存):

阅读全文 »

架构演进:从集中到分布式的迭代之路

架构的演进始终围绕着业务增长带来的复杂度挑战,从最初的简单集中式到复杂的分布式,每一步都是为了更好地适配业务规模、团队协作和技术能力的提升。

一、单一应用架构:简单起步的集中式方案

核心特征

所有功能模块(如用户管理、订单处理、商品展示等)集中在一个应用中,打包成单一部署单元(如 WAR 包),运行在一台服务器上,共享同一个数据库。

适用场景

  • 业务初期:用户量少(如日均几千访问)、功能简单(如小型企业官网、内部管理系统)。
  • 团队规模小:1-3 人开发,无需复杂协作流程。

优势

  • 开发部署高效:无需考虑跨服务通信,代码修改后直接打包部署,初期迭代速度快。
  • 资源成本低:单服务器 + 单数据库即可支撑,硬件和运维成本极低。

局限

  • 扩展性瓶颈:随着功能增加(如从 3 个模块增至 10 个模块),代码量激增(可能超 10 万行),模块间耦合严重(如一个模块的 bug 可能影响整个系统)。
  • 性能风险:所有请求集中在一台服务器,用户量增长后(如日均访问超 10 万),容易出现 CPU 占用过高、数据库连接耗尽等单点故障。
  • 协作困难:多人开发同一应用时,代码冲突频繁,需频繁合并分支,影响迭代效率。

二、垂直应用架构:按业务拆分的分散式尝试

核心特征

阅读全文 »

Spring MVC 参数解析机制详解:从接口到实战全流程

Spring MVC 的参数解析是连接 “HTTP 请求数据” 与 “Controller 方法参数” 的核心桥梁,负责将请求中的 URL 参数、请求头、请求体等数据,自动转换为 Controller 方法所需的 Java 类型参数(如 StringUser 对象、MultipartFile 等)。其核心是 HandlerMethodArgumentResolver 接口,通过多种实现类覆盖不同场景的参数解析需求,并结合 HttpMessageConverter(请求体转换)、Converter(类型转换)、Validator(数据验证)等组件,实现灵活且低侵入的参数绑定。从 “核心接口→解析器分类→解析流程→消息转换→参数转换→数据验证” 六个维度,彻底讲透 Spring MVC 参数解析的底层逻辑与实战应用。

核心接口:HandlerMethodArgumentResolver

HandlerMethodArgumentResolver 是所有参数解析器的顶层接口,定义了参数解析的通用规范,仅包含两个核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface HandlerMethodArgumentResolver {
/**
* 1. 判断当前解析器是否支持解析指定类型的参数
* @param parameter Controller 方法的参数描述(包含参数类型、注解等信息)
* @return true:支持解析;false:不支持,交给下一个解析器
*/
boolean supportsParameter(MethodParameter parameter);

/**
* 2. 执行参数解析,将请求数据转换为 Controller 方法所需的参数值
* @param parameter 参数描述
* @param mavContainer 模型容器(存储模型数据,如 Model)
* @param webRequest 封装 HTTP 请求(可获取请求参数、请求头、请求体等)
* @param binderFactory 数据绑定工厂(用于参数绑定与验证)
* @return 解析后的参数值(如 String、User 对象)
* @throws Exception 解析过程中抛出的异常(如参数格式错误)
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

核心设计思想:职责链模式

Spring MVC 维护一个 HandlerMethodArgumentResolver 列表(由 HandlerMethodArgumentResolverComposite 组合管理),当解析参数时:

  1. 遍历所有解析器,通过 supportsParameter 判断是否支持当前参数;
  2. 找到第一个支持的解析器,调用 resolveArgument 完成解析;
  3. 若所有解析器均不支持,抛出 IllegalStateException(如 “Could not resolve parameter [xxx]”)。

参数解析器分类与核心实现类

Spring MVC 提供了数十个 HandlerMethodArgumentResolver 实现类,覆盖从 “简单参数” 到 “复杂对象” 的所有场景。按功能可分为 6 大类,以下是关键实现类的解析:

分类 核心实现类 适用场景 关键特性
路径参数解析 PathVariableMethodArgumentResolver 处理 @PathVariable 注解的参数(非 Map 类型) 从 URL 路径中提取参数(如 /user/{id}id 值)
PathVariableMapMethodArgumentResolver 处理 @PathVariable 注解的 Map 类型参数 将所有路径参数封装为 Map(如 @PathVariable Map<String, String> params
请求参数解析 RequestParamMethodArgumentResolver 处理 @RequestParam 注解、基本类型(String/Integer)、MultipartFile 从 URL 查询参数或表单中提取参数;支持参数必填校验
RequestParamMapMethodArgumentResolver 处理 @RequestParam 注解的 Map 类型参数 将同名参数封装为 Map(如 @RequestParam Map<String, String> params
请求体解析 RequestResponseBodyMethodProcessor 处理 @RequestBody 注解的参数 依赖 HttpMessageConverter 将请求体(如 JSON)转换为 Java 对象
HttpEntityMethodProcessor 处理 HttpEntity/RequestEntity 类型参数 封装整个请求(包含请求头 + 请求体)为 HttpEntity 对象
请求头 / Cookie 解析 RequestHeaderMethodArgumentResolver 处理 @RequestHeader 注解的参数(非 Map 类型) 从请求头中提取参数(如 @RequestHeader("User-Agent") String userAgent
ServletCookieValueMethodArgumentResolver 处理 @CookieValue 注解的参数 从 Cookie 中提取参数(如 @CookieValue("JSESSIONID") String sessionId
Servlet 原生对象解析 ServletRequestMethodArgumentResolver 处理 HttpServletRequest/HttpServletResponse/HttpSession 等类型参数 直接注入 Servlet 原生对象,无需额外转换
模型 / 会话参数解析 ModelAttributeMethodProcessor 处理 @ModelAttribute 注解的参数 将请求参数绑定为 Java Bean(如表单提交的 User 对象)
SessionAttributeMethodArgumentResolver 处理 @SessionAttribute 注解的参数 从 Session 中提取已存储的属性(如 @SessionAttribute("loginUser") User user

关键实现类详解

1. RequestParamMethodArgumentResolver(最常用)

负责解析 URL 查询参数、表单参数、基本类型参数,是日常开发中最频繁使用的解析器:

阅读全文 »

Tomcat 项目部署全攻略:三种方式详解与实战

Tomcat 作为 Java Web 应用的主流容器,提供了多种灵活的部署方式,适应不同的开发、测试和生产环境。本文详细讲解 Tomcat 部署 Web 项目的三种核心方式,分析其优缺点及适用场景,帮助开发者高效管理应用部署。

方式一:直接部署到webapps目录(最简单)

这是 Tomcat 最默认、最便捷的部署方式,无需额外配置,适合快速测试或小型应用。

部署步骤

  1. 准备应用包
    • 可以是解压后的 Web 应用文件夹(目录结构需包含WEB-INFMETA-INF等标准目录);
    • 也可以是WAR 包(Web Application Archive,将应用文件压缩为.war格式)。
  2. 放置到webapps目录
    将应用文件夹或 WAR 包复制到 Tomcat 安装目录的webapps文件夹下(如/usr/local/tomcat/webapps)。
  3. 自动部署
    • 若为 WAR 包,Tomcat 会自动解压(依赖server.xmlunpackWARs="true"配置),生成同名文件夹;
    • 启动 Tomcat 后,应用即可访问。

访问路径规则

  • 若应用文件夹(或解压后的 WAR 包文件夹)名为myapp,访问路径为:http://localhost:8080/myapp
  • 若文件夹名为ROOT(大写),则为默认应用,访问路径为:http://localhost:8080(无需加应用名)。

优缺点

  • 优点:零配置,操作简单,适合新手和快速部署;
  • 缺点:应用必须放在webapps目录,灵活性低;大规模部署时难以管理。

方式二:在server.xml中配置<Context>节点(灵活但不推荐)

通过修改 Tomcat 核心配置文件server.xml,手动指定应用路径,适合需要自定义部署路径的场景。

部署步骤

  1. 编辑server.xml
    打开conf/server.xml,在<Host>标签内添加<Context>节点:

阅读全文 »