-
前言首先,我们必须明确一个核心观点:在分布式环境下,要实现强一致性(在任何时刻读取的数据都是最新的)是极其困难且代价高昂的,通常会严重牺牲性能。因此,在实践中,我们通常追求最终一致性,即允许在短暂的时间内数据不一致,但通过一些手段保证数据最终会保持一致。下面我将从基础概念、各种策略、最佳实践到最新方案,为你详细讲解。一、基础概念:为什么会有不一致?在一个包含 MySQL(作为可靠数据源)和 Redis(作为缓存)的系统中,所有的写操作(增、删、改)都必须同时处理这两个地方。 这个过程中,任何一步失败或延迟都会导致不一致:写 MySQL 成功,写 Redis 失败:导致 Redis 中是旧数据。写 Redis 成功,写 MySQL 失败:导致 Redis 中是“脏数据”,数据库中不存在。并发读写:一个线程在更新数据库,但还没更新缓存时,另一个线程读取了旧的缓存数据。二、核心策略与模式解决双写一致性有多种策略,我们需要根据业务场景(对一致性的要求、读写的比例等)进行选择。策略一:Cache-Aside Pattern(旁路缓存模式)这是最常用、最经典的缓存模式。核心原则是:应用程序直接与数据库和缓存交互,缓存不作为写入的必经之路。读流程:收到读请求。首先查询 Redis,如果数据存在(缓存命中),直接返回。如果 Redis 中没有数据(缓存未命中),则从 MySQL 中查询。将从 MySQL 查询到的数据写入 Redis(以便后续读取),然后返回数据。写流程:收到写请求。更新 MySQL 中的数据。删除 Redis 中对应的缓存。为什么是删除(Invalidate)缓存,而不是更新缓存?这是一个关键设计点!性能:如果更新缓存,每次数据库写操作都要伴随一次缓存写操作,如果该数据并不经常被读取,那么这次缓存写入就是浪费资源的。并发安全:在并发写场景下,更新缓存的顺序可能与更新数据库的顺序不一致,导致缓存中是旧数据。而删除操作是幂等的,更为安全。Cache-Aside 如何保证一致性?它通过“先更新数据库,再删除缓存”来尽力保证。但它依然存在不一致的窗口期:线程 A 更新数据库。线程 B 读取数据,发现缓存不存在,从数据库读取旧数据(因为 A 还没提交或刚提交)。线程 B 将旧数据写入缓存。线程 A 删除缓存。这种情况发生的概率较低,因为通常数据库写操作(步骤1)会比读操作(步骤2)耗时更长(因为涉及锁、日志等),所以步骤2在步骤1之前完成的概率很小。但这是一种理论上的可能。策略二:Write-Through / Read-Through Pattern(穿透读写模式)在这种模式下,缓存层(或一个独立的服务)自己负责与数据库交互。对应用来说,它只与缓存交互。写流程:应用写入缓存,缓存组件同步地写入数据库。只有两个都成功后才会返回成功。读流程:应用读取缓存,如果未命中,缓存组件自己从数据库加载并填充缓存,然后返回。优点:逻辑对应用透明,一致性比 Cache-Aside 更好。缺点:性能较差,因为每次写操作都必然涉及一次数据库写入。通常需要成熟的缓存中间件支持。策略三:Write-Behind Pattern(异步写回模式)Write-Through 的异步版本。应用写入缓存后立即返回,缓存组件在之后某个时间点(例如攒够一批数据或定时)批量异步地更新到数据库。优点:写性能极高。缺点:有数据丢失风险(缓存宕机),一致性最弱。适用于允许少量数据丢失的场景,如计数、点赞等。三、保证最终一致性的进阶方案为了弥补 Cache-Aside 模式中的缺陷,我们可以引入一些额外的机制。方案一:延迟双删针对 Cache-Aside 中提到的“先更新数据库,再删除缓存”可能带来的并发问题,可以引入一个延迟删除。线程 A 更新数据库。线程 A 删除缓存。线程 A 休眠一个特定的时间(如 500ms - 1s)。线程 A 再次删除缓存。第二次删除是为了清理掉在第1次删除后、其他线程可能写入的旧数据。这个休眠时间需要根据业务读写耗时来估算。优点:简单有效,能很大程度上解决并发读写导致的不一致。缺点:降低了写入吞吐量,休眠时间难以精确设定。方案二:通过消息队列异步删除为了解耦和重试,可以将删除缓存的操作作为消息发送到消息队列(如 RocketMQ, Kafka)。更新数据库。向消息队列发送一条删除缓存的消息。消费者消费该消息,执行删除 Redis 的操作。如果删除失败,消息会重试。这保证了删除缓存的操作至少会被执行一次,大大提高了可靠性。方案三:通过数据库 Binlog 同步(最优解)这是目前最成熟、对业务侵入性最小、一致性最好的方案。其核心是利用 MySQL 的二进制日志(Binlog)进行增量数据同步。工作原理:业务系统正常写入 MySQL。由一个中间件(如 Canal, Debezium)伪装成 MySQL 的从库,订阅 Binlog。中间件解析 Binlog,获取数据的变更详情(增、删、改)。中间件根据变更,调用 Redis 的 API 来更新或删除对应的缓存。优点:业务无侵入:业务代码只关心写数据库,完全不知道缓存的存在。高性能:数据库和缓存的同步是异步的,不影响主业务链路的性能。强保证:由于基于 Binlog,它能保证只要数据库变了,缓存最终一定会被同步。顺序也与数据库一致。缺点:架构复杂,需要维护额外的同步组件。同步有毫秒级到秒级的延迟。四、总结与最佳实践选择策略一致性保证性能复杂度适用场景Cache-Aside + 删除最终一致性(有微弱不一致风险)高低绝大多数场景的首选,读多写少Cache-Aside + 延迟双删更好的最终一致性中低对一致性要求稍高,且能接受一定延迟的写操作Write-Through强一致性中中写多读少,且对一致性要求非常高的场景Binlog 同步最终一致性(推荐)高高大型、高要求项目的最佳实践,对业务无侵入通用建议:首选方案:对于大多数应用,从 Cache-Aside(先更新数据库,再删除缓存) 开始。它简单、有效,在大多数情况下已经足够。进阶保障:如果 Cache-Aside 的不一致窗口无法接受,可以引入延迟双删或消息队列异步删除来增强。终极方案:当业务发展到一定规模,对一致性和系统解耦有更高要求时,投入资源搭建基于 Binlog 的异步同步方案。这是业界证明最可靠的方案。设置合理的过期时间:无论如何,都给 Redis 中的缓存设置一个过期时间(TTL)。这是一个安全网,即使同步逻辑出现问题,旧数据也会自动失效,最终从数据库加载新数据,保证最终一致性。业务容忍度:最重要的是,与产品经理确认业务对一致性的容忍度。很多时候,1-2秒内的数据不一致用户是感知不到的,不需要为此付出巨大的架构和性能代价。————————————————原文链接:https://blog.csdn.net/2402_87298751/article/details/153478251
-
如何使用update-alternatives管理多版本Java JDK?(Windows、Mac、Ubuntu)摘要在实际开发中,往往会遇到既要维护老项目又要跟进新特性的场景,这就需要在一台机器上同时安装并切换多个Java JDK版本。本文将针对三大主流平台——Windows、macOS 和 Ubuntu,详细介绍如何安装多个 JDK,并使用各自平台上的“替代方案”工具来管理与切换。Windows:通过系统环境变量与批处理脚本实现版本切换macOS:利用 /usr/libexec/java_home 与 jEnv 工具Ubuntu:深入剖析 update-alternatives 原理与实战无论您是新手还是有一定经验的开发者,都能从中获得清晰的思路与操作指南。文章目录如何使用update-alternatives管理多版本Java JDK?(Windows、Mac、Ubuntu) 摘要引言作者名片 加入我们AI共创团队 加入猫头虎的共创圈,一起探索编程世界的无限可能! 正文1. Windows 平台1.1 环境变量基础1.2 安装多个 JDK1.3 手动切换1.4 使用批处理脚本自动切换2. macOS 平台2.1 `/usr/libexec/java_home` 命令2.2 使用 jEnv 统一管理(推荐)3. Ubuntu 平台(Debian系)3.1 `update-alternatives` 原理3.2 安装与注册 JDK3.2.1 使用 APT 安装(OpenJDK)3.2.2 手动下载并注册 Oracle JDK3.3 切换与查看查看当前注册项交互式切换4. 验证与示例5. 常见问题与解决6. 常见 QA总结粉丝福利联系我与版权声明 引言多版本 JDK 切换为何如此重要?兼容性测试:老项目可能依赖 Java 8,而新项目需要 Java 17。生态差异:Spring Boot 2.x 与 3.x 对 Java 版本的要求不同。CI/CD 集成:自动化构建需要在不同 JDK 下验证构建过程。三大平台各有生态与管理方式,因此本文将分别展开,帮助您在不同系统上搭建灵活的多版本 Java 环境。作者名片 博主:猫头虎全网搜索关键词:猫头虎作者微信号:Libin9iOak作者公众号:猫头虎技术团队更新日期:2025年07月21日欢迎来到猫头虎的博客 — 探索技术的无限可能!加入我们AI共创团队 猫头虎AI共创社群矩阵列表:点我进入共创社群矩阵入口点我进入新矩阵备用链接入口加入猫头虎的共创圈,一起探索编程世界的无限可能! 正文1. 🪟 Windows 平台1.1 环境变量基础Windows 管理可执行程序的核心是 系统路径(PATH) 与 环境变量(Environment Variables)。切换 JDK 版本,本质上就是让系统在 PATH 中优先找到对应版本的 java.exe 与 javac.exe。1.2 安装多个 JDK从 Oracle 官网或 AdoptOpenJDK 下载所需版本的 Windows 安装包(.exe)。依次安装到不同目录,如:C:\Program Files\Java\jdk1.8.0_381C:\Program Files\Java\jdk-17.0.71.3 手动切换打开系统环境变量:右键「此电脑」→「属性」→「高级系统设置」→「环境变量」。找到 系统变量 中的 JAVA_HOME、Path:修改 JAVA_HOME 为目标 JDK 目录。在 Path 里,将 %JAVA_HOME%\bin 放到最前面。点击「确定」,重新打开命令行窗口,即可 java -version 验证。1.4 使用批处理脚本自动切换为了避免每次手动修改环境变量,可编写简单的 .bat 脚本:@echo offREM 切换到 Java 8setx JAVA_HOME "C:\Program Files\Java\jdk1.8.0_381" /Msetx PATH "%%JAVA_HOME%%\bin;%%PATH%%" /Mecho 已切换到 Java 8一键获取完整项目代码bat保存为 switch-to-java8.bat,右键以管理员身份运行。同理可写 switch-to-java17.bat。运行后重启命令行窗口即可生效。2. macOS 平台2.1 /usr/libexec/java_home 命令macOS 自带命令 /usr/libexec/java_home,可列出并切换已安装的 JDK 版本。# 列出所有已安装JDK/usr/libexec/java_home -V# 切换到 Java 11export JAVA_HOME=$(/usr/libexec/java_home -v 11)export PATH=$JAVA_HOME/bin:$PATH一键获取完整项目代码bash-V:显示版本列表及安装路径。-v <version>:选择指定版本。将上述两行写入 ~/.zshrc 或 ~/.bash_profile,并配合 alias:alias j8='export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)'alias j11='export JAVA_HOME=$(/usr/libexec/java_home -v 11)'alias j17='export JAVA_HOME=$(/usr/libexec/java_home -v 17)'一键获取完整项目代码bash打开新终端后,输入 j11 即可切换。2.2 使用 jEnv 统一管理(推荐)jEnv 是跨平台的 Java 版本管理工具,支持 macOS、Linux。安装 jEnv(需先安装 Homebrew):brew install jenv一键获取完整项目代码bash将 jEnv 集成到 shell 配置:echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrcecho 'eval "$(jenv init -)"' >> ~/.zshrcsource ~/.zshrc一键获取完整项目代码bash添加已安装的 JDK:jenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_381.jdk/Contents/Homejenv add /Library/Java/JavaVirtualMachines/jdk-17.0.7.jdk/Contents/Home一键获取完整项目代码bash列出与切换:jenv versionsjenv global 11 # 全局切换到 Java 11jenv local 1.8 . # 针对当前目录切换到 Java 1.8jenv shell 17 # 仅对当前 shell 有效一键获取完整项目代码bashjEnv 会自动管理 JAVA_HOME 与 PATH,并支持插件扩展(Maven、Gradle 插件等)。3. Ubuntu 平台(Debian系)3.1 update-alternatives 原理Debian/Ubuntu 引入 alternatives 系统,允许对系统命令(如 java、javac)创建“组”,并在组内注册多个“备选项”。每个备选项由 可执行文件路径 和 优先级 组成。运行 update-alternatives --config <name> 即可交互式切换。3.2 安装与注册 JDK3.2.1 使用 APT 安装(OpenJDK)sudo apt updatesudo apt install -y openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk一键获取完整项目代码bashAPT 安装后通常会自动注册到 alternatives,您可以直接执行下一步。3.2.2 手动下载并注册 Oracle JDK下载并解压到 /usr/lib/jvm:sudo mkdir -p /usr/lib/jvmsudo tar -xzf ~/Downloads/jdk-17.0.7_linux-x64_bin.tar.gz -C /usr/lib/jvm一键获取完整项目代码bash注册到 alternatives(以 Java 17 为例,优先级设为 2):sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-17.0.7/bin/java 2sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk-17.0.7/bin/javac 2sudo update-alternatives --install /usr/bin/jar jar /usr/lib/jvm/jdk-17.0.7/bin/jar 2一键获取完整项目代码bash第三个参数为命令组名(可省略后缀)。最后一个数字为优先级,数值越大越优先。3.3 切换与查看查看当前注册项update-alternatives --query java一键获取完整项目代码bash输出包含所有 java 备选路径及当前选择。交互式切换sudo update-alternatives --config java一键获取完整项目代码bash会列出所有已注册的 Java 可执行文件,按提示输入对应序号即可切换。同理切换 javac、jar 等。4. 验证与示例无论在哪个平台,切换后都应首先验证:java -versionjavac -version一键获取完整项目代码bash并可编写最简单的 HelloWorld 程序进行编译与运行测试。5. 常见问题与解决场景 原因与排查 解决思路切换后 java -version 仍指向旧版本 PATH 未更新或 shell 缓存未刷新 重新打开终端;Windows 重启 CMD;Linux hash -rWindows 脚本执行报 “权限不足” 未以管理员身份运行 .bat 右键 → “以管理员身份运行”macOS /usr/libexec/java_home 列不全 JDK 未正确安装到 /Library/Java/... 检查 JDK 文件夹;重启 shellUbuntu 手动注册后未见新选项 alternatives 配置不一致 再次执行 --install;检查路径拼写6. 常见 QAQ:为什么 Linux 上要用 update-alternatives?A:它能同时管理多个版本的同名命令,避免手动修改 PATH,且支持优先级与脚本化。Q:Windows 有没有类似 update-alternatives 的工具?A:官方没有,但可借助 jabba 或自定义批处理脚本。Q:macOS 上除了 jEnv 还有其他方案吗?A:也可使用 SDKMAN! 管理,但 SDKMAN! 对 Windows 支持有限。总结本文深入对比了 Windows、macOS 和 Ubuntu 三大平台上多版本 Java JDK 管理的思路与实践:Windows:环境变量 + 批处理脚本macOS:/usr/libexec/java_home + jEnvUbuntu:update-alternatives 原理详解掌握上述方法后,无论在本地开发还是在 CI/CD 环境,都能灵活切换 JDK 版本,确保兼容性与高效协同开发。祝您 Java 开发之路顺畅————————————————原文链接:https://blog.csdn.net/qq_44866828/article/details/149533211
-
本文简介目的:Spring生态为Java后端开发提供了强大支持,但将分散的技术点整合成完整解决方案往往令人困惑。本文将以登录接口为切入点,系统演示如何将IOC/DI、MyBatis数据持久化、MD5加密、Session/Cookie管理、JWT令牌和拦截器机制融合运用,打造企业级认证方案技术栈:前端:HTML + CSS + JavaScript + Jquery后端:SpringBoot + Mybatis + JWT搭建环境:数据库:MySQL8.4.0项目结构:maven前端框架:Jquery后端框架:SpringBootJDK:17编译器:IDEA目录结构:项目搭建及配置1.创建SpringBoot3.0.0+项目并添加依赖:Spring Web、MyBatis Framework、MySQL Driver、Lombok2.初始化数据库:create database spring_blog_login charset utf8mb4; use spring_blog_login;create table user_info (id int primary key auto_increment,user_name varchar(128) unique , password varchar(128) not null,delete_flag int default 0, create_time datetime default now(),update_time datetime default now());insert into user_info (user_name,password) values ('张三','123456'), ('李四','123456'), ('王五','123456');运行本项目sql3.将application.properties修改为application.yml并添加如下配置:spring: datasource: url: jdbc:mysql://127.0.0.1:3306/spring_blog_login?characterEncoding=utf8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Drivermybatis: configuration: map-underscore-to-camel-case: true #自动驼峰转换server: port: 8080 #不显式设置默认为8080运行本项目yml按住Ctrl + F5,如果程序能运行成功则说明搭建及配置都没问题(MySQL服务器必须要处于运行状态)1.登录认证全栈实现 ->基础版1.1 后端实现1.1.1 架构设计本次登录功能采用Controller、Service、Mapper三层架构:Controller层依赖于Service层来执行业务逻辑并获取处理结果,而Service层又依赖于Mapper层来进行数据持久化操作1.1.2 实体类实体类用于封装业务数据,需要与数据库表结构一一对应import lombok.Data;import java.util.Date;@Datapublic class UserInfo { private Integer id; private String userName; private String password; private Integer deleteFlag; private Date createTime; private Date updateTime;}运行本项目java运行1.1.3 Controller处理HTTP请求、参数校验、返回响应import org.example.springlogin.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")public class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @RequestMapping("/login") public String login(String userName,String password) { if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) { return "用户或密码为空"; } return userService.getUserInfoByUserName(userName,password); }}运行本项目java运行1.1.4 Service业务逻辑处理import org.example.springlogin.mapper.UserMapper;import org.example.springlogin.model.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class UserService { private final UserMapper userMapper; @Autowired public UserService(UserMapper userMapper) { this.userMapper = userMapper; } public String getUserInfoByUserName(String userName,String password) { UserInfo userInfo = userMapper.getUserInfoByUserName(userName); if (userInfo == null) { return "用户不存在"; } if (!password.equals(userInfo.getPassword())) { return "密码错误"; } return "登录成功"; }}运行本项目java运行1.1.5 Mapper数据持久化操作import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;import org.example.springlogin.model.UserInfo;@Mapperpublic interface UserMapper { @Select("select * from user_info where user_name = #{userName}") UserInfo getUserInfoByUserName(String userName);}运行本项目java运行1.2 前端实现Gitee:项目前端代码,Gitee上的前端代码是最新提交的,如下效果图仅作参考效果演示:1.用户或密码为空2.用户不存在3.密码错误4.登录成功2.Cookie/SessionHTTP(超文本传输协议)设计为无状态协议,指服务器默认不保留客户端请求之间的任何状态信息。每个请求独立处理,服务器不会记忆之前的交互内容(如下图)优点:请求独立性:每次请求被视为新请求,服务器不依赖历史请求数据简单高效:无状态设计降低服务器资源消耗,简化实现逻辑缺点:身份识别困难:需通过额外机制(如Cookies、Session)跟踪用户状态重复传输数据:每次请求需携带完整信息,可能增加冗余(如认证信息)cookie:是存储在客户端(浏览器)的小型文本数据,由服务器通过HTTP响应头Set-Cookie发送给客户端,并在后续请求中自动携带session:是存储在服务器端的用户状态信息,通常通过一个唯一的Session ID标识,该ID可能通过Cookie或URL传递如上图片引用自我的博客:Java EE(13)——网络原理——应用层HTTP协议,服务器内部实际上专门开辟了一个session空间用于存储用户信息,每当新用户发送第一次请求时服务器会将用户信息存储在session中并生成一个session id通过Set-Cookie方法返回给客户端,即cookiesession结构如下:修改Controller类代码:import jakarta.servlet.http.HttpSession;import lombok.extern.slf4j.Slf4j;import org.example.springlogin.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;@RestController@RequestMapping("/user")@Slf4jpublic class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @RequestMapping("/login") public String login(String userName, String password, HttpSession session) { log.info("接收到参数,userName:{},password:{}",userName,password); if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) { return "用户或密码为空"; } String result = userService.getUserInfoByUserName(userName, password); if (result.equals("登录成功")){ HashMap<String,String> map = new HashMap<>(); map.put("userName",userName); map.put("password",password); //将map作为用户信息存储到session/会话中 session.setAttribute("cookie", map); log.info("登录成功"); } return result; }运行本项目java运行修改前端代码: function login() { $.ajax({ url: '/user/login', type: "post", data:{ userName:$('#username').val(), password:$('#password').val(), }, success: function(result) { alert(result); }, }) }运行本项目javascript运行Fiddler抓包结果:前端/浏览器按住Ctrl + Shift + i打开控制台点击应用程序/application,打开Cookie:3.统一返回结果封装统一返回结果封装是后端开发中的重要设计模式,能够保持API响应格式的一致性,便于前端处理1.创建枚举类:统一管理接口或方法的返回状态码和描述信息,标准化业务逻辑中的成功或失败状态import lombok.Getter;@Getterpublic enum ResultStatus { SUCCESS(200,"成功"), FAIL(-1,"失败"), ; private final Integer code; private final String message; ResultStatus(Integer code, String message) { this.code = code; this.message = message; }}运行本项目java运行2.创建Result< T >类:主要用于规范服务端返回给客户端的响应数据格式。通过固定结构(状态码、错误信息、数据)确保前后端交互的一致性import lombok.Data;@Data//通过泛型<T>设计,可以灵活封装任意类型的数据对象到data字段public class Result<T> { //业务码 private ResultStatus code; //错误信息 private String errorMessage; //数据 private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(ResultStatus.SUCCESS); result.setErrorMessage(null); result.setData(data); return result; } public static <T> Result<T> fail(String errorMessage) { Result<T> result = new Result<>(); result.setCode(ResultStatus.FAIL); result.setErrorMessage(errorMessage); result.setData(null); return result; }}运行本项目java运行3.修改Controller代码:@RestController@RequestMapping("/user")@Slf4jpublic class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @RequestMapping("/login") public Result<String> login(String userName, String password, HttpSession session) { log.info("接收到参数,userName:{},password:{}",userName,password); if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) { return Result.fail("用户或密码为空"); } String result = userService.getUserInfoByUserName(userName, password); if (!result.equals("登录成功")){ return Result.fail(result); } HashMap<String,String> map = new HashMap<>(); map.put("userName",userName); map.put("password",password); //将map作为用户信息存储到session/会话中 session.setAttribute("cookie", map); log.info("登录成功"); return Result.success(result); }}运行本项目java运行4.修改前端代码: function login() { $.ajax({ url: '/user/login', type: "post", data:{ userName:$('#username').val(), password:$('#password').val(), }, success: function(result) { if (result.code === "SUCCESS") { alert(result.data) }else { alert(result.error) } }, }) }运行本项目javascript运行4.图形验证码图形验证码(captcha)是一种区分用户是人类还是自动化程序的技术,主要通过视觉或交互任务实现。其核心意义体现在以下方面:防止自动化攻击:通过复杂图形或扭曲文字,阻止爬虫、暴力破解工具等自动化程序批量注册或登录,降低服务器压力提升安全性:在敏感操作(如支付、修改密码)前增加验证步骤,减少数据泄露或恶意操作风险Hutool提供了CaptchaUtil类用于快速生成验证码,支持图形验证码和GIF动态验证码。在pom.xml文件中添加图下配置:<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <!-- 版本号应与springboot版本兼容 --> <version>5.8.40</version></dependency>运行本项目xml1.创建CaptchaController类,用于生成验证码并返回给前端import cn.hutool.captcha.CaptchaUtil;import cn.hutool.captcha.LineCaptcha;import jakarta.servlet.http.HttpServletResponse;import jakarta.servlet.http.HttpSession;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.IOException;@RestController@RequestMapping("/captcha")@Slf4jpublic class CaptchaController { //设置过期时间 public final static long delay = 60_000L; @RequestMapping("/get") public void getCaptcha(HttpSession session, HttpServletResponse response) { log.info("getCaptcha"); LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); //设置返回类型 response.setContentType("image/jpeg"); //禁止缓存 response.setHeader("Pragma", "No-cache"); try { //通过响应输出生成的图形验证码 lineCaptcha.write(response.getOutputStream()); //保存code session.setAttribute("CAPTCHA_SESSION_CODE", lineCaptcha.getCode()); //保存当前时间 session.setAttribute("CAPTCHA_SESSION_DATE", System.currentTimeMillis()); //关闭输出流 response.getOutputStream().close(); } catch (IOException e) { throw new RuntimeException(e); } }}运行本项目java运行2.修改前端代码:最终版<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>微信登录</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="css/login.css"></head><body> <div class="login-container"> <div class="logo"> <i class="fab fa-weixin"></i> </div> <h2>微信登录</h2> <form id="loginForm"> <div class="input-group"> <i class="fas fa-user"></i> <label for="username"></label><input type="text" id="username" placeholder="请输入用户名" required> </div> <div class="input-group"> <i class="fas fa-lock"></i> <label for="password"></label><input type="password" id="password" placeholder="请输入密码" required> </div> <div class="input-group"> <div class="captcha-container"> <label for="inputCaptcha"></label><input type="text" id="inputCaptcha" class="captcha-input" placeholder="输入验证码"> <img id="verificationCodeImg" src="/captcha/get" class="captcha-img" title="看不清?换一张" alt="验证码"> </div> </div> <div class="agreement"> <input type="checkbox" id="agreeCheck" checked> <label for="agreeCheck">我已阅读并同意<a href="#">《服务条款》</a>和<a href="#">《隐私政策》</a></label> </div> <button type="submit" class="login-btn" onclick="login()">登录</button> </form> <div class="footer"> <p>版权所有 ©九转苍翎</p> </div> </div> <!-- 引入jQuery依赖 --> <script src="js/jquery.min.js"></script> <script> //刷新验证码 $("#verificationCodeImg").click(function(){ //new Date().getTime()).fadeIn()防止前端缓存 $(this).hide().attr('src', '/captcha/get?dt=' + new Date().getTime()).fadeIn(); }); //登录 function login() { $.ajax({ url: '/user/login', type: "post", data:{ userName:$('#username').val(), password:$('#password').val(), captcha:$('#inputCaptcha').val(), }, success: function(result) { console.log(result); if (result.code === "SUCCESS") { alert(result.data) }else { alert(result.error) } }, }) } </script></body></html>运行本项目html3.在UserController类新增captcha形参接收来自CaptchaController类的请求,并传递给UserServiceimport jakarta.servlet.http.HttpSession;import org.example.springlogin.controller.CaptchaController;import org.example.springlogin.mapper.UserMapper;import org.example.springlogin.model.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class UserService { private final UserMapper userMapper; @Autowired public UserService(UserMapper userMapper) { this.userMapper = userMapper; } public String getUserInfoByUserName(String userName, String password, String captcha, HttpSession session) { UserInfo userInfo = userMapper.getUserInfoByUserName(userName); if (userInfo == null) { return "用户不存在"; } if (!password.equals(userInfo.getPassword())) { return "密码错误"; } long saveTime = (long)session.getAttribute("CAPTCHA_SESSION_DATE"); if (System.currentTimeMillis() - saveTime > CaptchaController.delay) { return "验证码超时"; } if (!captcha.equalsIgnoreCase((String) session.getAttribute("CAPTCHA_SESSION_CODE"))) { return "验证码错误"; } return "登录成功"; }}运行本项目java运行实现效果:5.MD5加密MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希函数,可将任意长度数据生成固定长度(128位,16字节)的哈希值,通常表示为32位十六进制字符串,常用于校验数据完整性或存储密码。但因其安全性不足,通常结合盐值(Salt)配合使用不可逆性:无法通过哈希值反推原始数据唯一性:理论上不同输入产生相同哈希值的概率极低(哈希碰撞)固定长度:无论输入数据大小,输出均为32位十六进制字符串1.创建SecurityUtil类用于生成和验证密文import org.springframework.util.DigestUtils;import org.springframework.util.StringUtils;import java.util.UUID;public class SecurityUtil { //加密 public static String encrypt(String inputPassword){ //生成随机盐值 String salt = UUID.randomUUID().toString().replaceAll("-", ""); //(密码+盐值)进行加密 String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes()); return salt + finalPassword; } //验证 public static boolean verify(String inputPassword, String sqlPassword){ if (!StringUtils.hasLength(inputPassword)){ return false; } if (sqlPassword == null || sqlPassword.length() != 64){ return false; } //取出盐值 String salt = sqlPassword.substring(0,32); //(输入密码 + 盐值)重新生成 加密密码 String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes()); //判断数据库中储存的密码与输入密码是否一致 return (salt + finalPassword).equals(sqlPassword); } public static void main(String[] args) { System.out.println(SecurityUtil.encrypt("123456")); }}运行本项目java运行2.将数据库中的密码替换为加密后的值3.修改验证密码的逻辑(UserService类) if (!SecurityUtil.verify(password,userInfo.getPassword())) { return "密码错误"; }运行本项目java运行6.拦截器Spring拦截器(Interceptor)是一种基于AOP的机制,用于在请求处理的不同阶段插入自定义逻辑。常用于权限校验、日志记录、参数预处理等场景1.创建拦截器类并实现HandlerInterceptor接口,该接口提供了三种方法:preHandle:在Controller方法执行前调用postHandle:Controller方法执行后、视图渲染前调用afterCompletion:请求完成、视图渲染完毕后调用import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;@Slf4j@Componentpublic class Interceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //1.获取token String cookie = request.getHeader("cookie"); if (cookie == null) { response.setStatus(401); return false; } log.info("接收到cookie:{}",cookie); //2.校验token return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { log.info("postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { log.info("afterCompletion"); }}运行本项目java运行2.注册拦截器import org.example.springlogin.intercepter.Interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.Arrays;import java.util.List;@Configurationpublic class Config implements WebMvcConfigurer { private final Interceptor Interceptor; @Autowired public Config(Interceptor interceptor) { Interceptor = interceptor; } //排除不需要拦截的路径 private static final List<String> excludes = Arrays.asList( "/**/login.html", "/user/login", "/captcha/get" ); @Override public void addInterceptors(InterceptorRegistry registry) { //注册拦截器 registry.addInterceptor(Interceptor) //拦截所有路径 .addPathPatterns("/**") .excludePathPatterns(excludes); }}运行本项目java运行3.创建home.html文件,并且在登录成功后跳转到该页面(在login.html中添加location.href="/home.html")<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>home</title></head><body> <h1>Hello World</h1></body></html>运行本项目html实现效果:成功登陆时未登录直接访问home.html页面时————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/153261457
-
前言在现代企业级Java应用开发中,事务管理是确保数据一致性和完整性的核心机制。Spring框架作为Java生态系统中最重要的框架之一,提供了强大而灵活的事务管理功能。本文将从基础概念出发,深入探讨Spring事务管理的各个方面,通过丰富的代码示例和实践案例,帮助开发者全面掌握Spring事务管理的精髓。无论你是刚接触Spring事务的新手,还是希望深化理解的资深开发者,本文都将为你提供有价值的见解和实用的技巧。我们将先概览Spring事务的整体架构,然后深入各个具体模块,最后进行知识总结和扩展思考。第一章:Spring事务基础概念与核心原理1.1 事务的基本概念事务(Transaction)是数据库操作的基本单位,它是一个不可分割的工作逻辑单元。事务必须满足ACID特性:原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚一致性(Consistency):事务执行前后,数据库的完整性约束没有被破坏隔离性(Isolation):并发执行的事务之间不能互相干扰持久性(Durability):事务一旦提交,其结果就是永久性的1.2 Spring事务管理架构Spring事务管理基于以下核心组件:1.2.1 PlatformTransactionManager接口public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException;}运行本项目java运行这是Spring事务管理的核心接口,定义了事务的基本操作。不同的数据访问技术有不同的实现:DataSourceTransactionManager:用于JDBC和MyBatisJpaTransactionManager:用于JPAHibernateTransactionManager:用于Hibernate1.2.2 TransactionDefinition接口public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; // ... 其他传播行为常量 int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName();}运行本项目java运行1.3 Spring事务管理的实现原理Spring事务管理基于AOP(面向切面编程)实现,通过代理模式在方法调用前后添加事务逻辑:@Componentpublic class UserService { @Autowired private UserRepository userRepository; @Transactional public void createUser(User user) { // Spring会在此方法执行前开启事务 userRepository.save(user); // 方法正常结束时提交事务,异常时回滚 }}运行本项目java运行当Spring容器创建UserService的代理对象时,会织入事务管理逻辑:// 简化的代理逻辑示意public class UserServiceProxy extends UserService { private PlatformTransactionManager transactionManager; @Override public void createUser(User user) { TransactionStatus status = null; try { // 开启事务 status = transactionManager.getTransaction(new DefaultTransactionDefinition()); // 调用实际的业务方法 super.createUser(user); // 提交事务 transactionManager.commit(status); } catch (Exception e) { // 回滚事务 if (status != null) { transactionManager.rollback(status); } throw e; } }}运行本项目java运行第二章:Spring事务管理器详解2.1 事务管理器的选择与配置2.1.1 DataSourceTransactionManager配置对于使用JDBC或MyBatis的应用,通常选择DataSourceTransactionManager:@Configuration@EnableTransactionManagementpublic class TransactionConfig { @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb"); dataSource.setUsername("root"); dataSource.setPassword("password"); return dataSource; } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); }}运行本项目java运行2.1.2 JpaTransactionManager配置对于JPA应用,使用JpaTransactionManager:@Configuration@EnableTransactionManagement@EnableJpaRepositories(basePackages = "com.example.repository")public class JpaConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.example.entity"); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); Properties properties = new Properties(); properties.setProperty("hibernate.hbm2ddl.auto", "update"); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); em.setJpaProperties(properties); return em; } @Bean public PlatformTransactionManager transactionManager( EntityManagerFactory entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); return transactionManager; }}运行本项目java运行2.2 多数据源事务管理在复杂的企业应用中,经常需要处理多个数据源的事务:@Configuration@EnableTransactionManagementpublic class MultiDataSourceConfig { @Bean @Primary public DataSource primaryDataSource() { // 主数据源配置 return DataSourceBuilder.create() .url("jdbc:mysql://localhost:3306/primary_db") .username("root") .password("password") .build(); } @Bean public DataSource secondaryDataSource() { // 从数据源配置 return DataSourceBuilder.create() .url("jdbc:mysql://localhost:3306/secondary_db") .username("root") .password("password") .build(); } @Bean @Primary public PlatformTransactionManager primaryTransactionManager( @Qualifier("primaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public PlatformTransactionManager secondaryTransactionManager( @Qualifier("secondaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }}运行本项目java运行使用时可以通过@Transactional注解指定事务管理器:@Servicepublic class MultiDataSourceService { @Transactional("primaryTransactionManager") public void operateOnPrimaryDB() { // 操作主数据库 } @Transactional("secondaryTransactionManager") public void operateOnSecondaryDB() { // 操作从数据库 }}运行本项目java运行2.3 分布式事务管理对于跨多个资源的分布式事务,Spring提供了JTA支持:@Configuration@EnableTransactionManagementpublic class JtaConfig { @Bean public JtaTransactionManager transactionManager() { JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(); jtaTransactionManager.setUserTransaction(userTransaction()); jtaTransactionManager.setTransactionManager(atomikosTransactionManager()); return jtaTransactionManager; } @Bean public UserTransaction userTransaction() throws SystemException { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(300); return userTransactionImp; } @Bean public TransactionManager atomikosTransactionManager() { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; }}运行本项目java运行第三章:声明式事务配置与使用3.1 @Transactional注解详解@Transactional是Spring声明式事务的核心注解,提供了丰富的配置选项:@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional { String value() default ""; String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};}运行本项目java运行3.2 注解使用最佳实践3.2.1 类级别与方法级别注解@Service@Transactional(readOnly = true) // 类级别默认只读事务public class UserService { @Autowired private UserRepository userRepository; // 继承类级别的只读事务 public List<User> findAllUsers() { return userRepository.findAll(); } // 方法级别覆盖类级别配置 @Transactional(readOnly = false, rollbackFor = Exception.class) public User createUser(User user) { validateUser(user); return userRepository.save(user); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUserStatus(Long userId, UserStatus status) { User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException("User not found")); user.setStatus(status); userRepository.save(user); } private void validateUser(User user) { if (user.getEmail() == null || user.getEmail().isEmpty()) { throw new IllegalArgumentException("Email cannot be empty"); } }}运行本项目java运行3.2.2 异常处理与回滚配置@Servicepublic class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private InventoryService inventoryService; @Autowired private PaymentService paymentService; // 默认只对RuntimeException和Error回滚 @Transactional public Order createOrder(OrderRequest request) { Order order = new Order(request); orderRepository.save(order); // 如果库存不足,抛出RuntimeException,事务会回滚 inventoryService.reserveItems(request.getItems()); return order; } // 指定对所有异常都回滚 @Transactional(rollbackFor = Exception.class) public void processPayment(Long orderId, PaymentInfo paymentInfo) throws PaymentException { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException("Order not found")); try { paymentService.processPayment(paymentInfo); order.setStatus(OrderStatus.PAID); orderRepository.save(order); } catch (PaymentException e) { // PaymentException是检查异常,但配置了rollbackFor = Exception.class // 所以事务会回滚 throw e; } } // 指定某些异常不回滚 @Transactional(noRollbackFor = {BusinessException.class}) public void updateOrderWithBusinessLogic(Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException("Order not found")); try { // 执行业务逻辑 performBusinessLogic(order); orderRepository.save(order); } catch (BusinessException e) { // BusinessException不会导致事务回滚 // 但数据库操作仍然会提交 log.warn("Business logic failed, but transaction will commit", e); } }}运行本项目java运行3.3 XML配置方式虽然注解方式更加流行,但XML配置在某些场景下仍然有用:<!-- applicationContext.xml --><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 数据源配置 --> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/testdb"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/> <tx:method name="*" rollback-for="Exception"/> </tx:attributes> </tx:advice> <!-- AOP配置 --> <aop:config> <aop:pointcut id="serviceOperation" expression="execution(* com.example.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/> </aop:config></beans>运行本项目xml第四章:编程式事务管理4.1 TransactionTemplate使用TransactionTemplate提供了编程式事务管理的模板方法:@Servicepublic class ProgrammaticTransactionService { @Autowired private TransactionTemplate transactionTemplate; @Autowired private UserRepository userRepository; @Autowired private OrderRepository orderRepository; public User createUserWithOrder(User user, Order order) { return transactionTemplate.execute(status -> { try { // 保存用户 User savedUser = userRepository.save(user); // 设置订单的用户ID order.setUserId(savedUser.getId()); // 保存订单 orderRepository.save(order); return savedUser; } catch (Exception e) { // 手动标记回滚 status.setRollbackOnly(); throw new RuntimeException("Failed to create user with order", e); } }); } public void batchUpdateUsers(List<User> users) { transactionTemplate.execute(status -> { for (User user : users) { try { userRepository.save(user); } catch (Exception e) { log.error("Failed to update user: {}", user.getId(), e); // 可以选择继续处理其他用户,或者回滚整个事务 // status.setRollbackOnly(); } } return null; }); }}运行本项目java运行4.2 PlatformTransactionManager直接使用对于更细粒度的控制,可以直接使用PlatformTransactionManager:@Servicepublic class LowLevelTransactionService { @Autowired private PlatformTransactionManager transactionManager; @Autowired private UserRepository userRepository; public void complexBusinessOperation() { // 定义事务属性 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setTimeout(30); TransactionStatus status = transactionManager.getTransaction(def); try { // 第一阶段操作 performPhaseOne(); // 检查点 - 可以根据业务逻辑决定是否继续 if (!shouldContinue()) { transactionManager.rollback(status); return; } // 第二阶段操作 performPhaseTwo(); // 提交事务 transactionManager.commit(status); } catch (Exception e) { // 回滚事务 transactionManager.rollback(status); throw new RuntimeException("Business operation failed", e); } } private void performPhaseOne() { // 第一阶段业务逻辑 } private void performPhaseTwo() { // 第二阶段业务逻辑 } private boolean shouldContinue() { // 业务判断逻辑 return true; }}运行本项目java运行4.3 编程式事务的嵌套使用@Servicepublic class NestedTransactionService { @Autowired private PlatformTransactionManager transactionManager; public void parentOperation() { DefaultTransactionDefinition parentDef = new DefaultTransactionDefinition(); parentDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus parentStatus = transactionManager.getTransaction(parentDef); try { // 父事务操作 performParentOperation(); // 调用子事务 childOperation(); transactionManager.commit(parentStatus); } catch (Exception e) { transactionManager.rollback(parentStatus); throw e; } } private void childOperation() { DefaultTransactionDefinition childDef = new DefaultTransactionDefinition(); childDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus childStatus = transactionManager.getTransaction(childDef); try { // 子事务操作(独立的事务) performChildOperation(); transactionManager.commit(childStatus); } catch (Exception e) { transactionManager.rollback(childStatus); // 子事务失败不影响父事务(因为使用了REQUIRES_NEW) log.error("Child operation failed", e); } }}运行本项目java运行第五章:事务传播行为和隔离级别深度解析5.1 事务传播行为详解Spring定义了7种事务传播行为,每种都有其特定的使用场景:5.1.1 PROPAGATION_REQUIRED(默认)@Servicepublic class PropagationRequiredService { @Autowired private UserService userService; @Transactional(propagation = Propagation.REQUIRED) public void outerMethod() { // 开启新事务T1 performOperation1(); // 调用内部方法,加入事务T1 userService.innerMethod(); performOperation2(); // 如果任何操作失败,整个事务T1回滚 }}@Servicepublic class UserService { @Transactional(propagation = Propagation.REQUIRED) public void innerMethod() { // 加入外部事务T1,不会创建新事务 performUserOperation(); }}运行本项目java运行5.1.2 PROPAGATION_REQUIRES_NEW@Servicepublic class PropagationRequiresNewService { @Autowired private AuditService auditService; @Transactional public void businessOperation() { try { // 主业务逻辑在事务T1中 performMainBusiness(); // 审计日志使用独立事务T2 auditService.logOperation("Business operation completed"); } catch (Exception e) { // 即使主业务失败,审计日志也会保存(因为是独立事务) auditService.logError("Business operation failed: " + e.getMessage()); throw e; } }}@Servicepublic class AuditService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void logOperation(String message) { // 创建新事务T2,独立于调用者的事务 AuditLog log = new AuditLog(message, new Date()); auditRepository.save(log); // T2独立提交,不受T1影响 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void logError(String errorMessage) { // 错误日志也使用独立事务,确保一定会保存 ErrorLog errorLog = new ErrorLog(errorMessage, new Date()); errorRepository.save(errorLog); }}运行本项目java运行5.1.3 PROPAGATION_NESTED@Servicepublic class PropagationNestedService { @Autowired private OrderService orderService; @Transactional public void processOrderBatch(List<OrderRequest> orders) { for (OrderRequest orderRequest : orders) { try { // 每个订单处理使用嵌套事务 orderService.processOrder(orderRequest); } catch (Exception e) { // 单个订单失败不影响其他订单 log.error("Failed to process order: {}", orderRequest.getId(), e); } } }}@Servicepublic class OrderService { @Transactional(propagation = Propagation.NESTED) public void processOrder(OrderRequest request) { // 创建嵌套事务(保存点) Order order = new Order(request); orderRepository.save(order); // 如果这里抛出异常,只回滚到保存点 // 外部事务可以继续执行 validateAndProcessPayment(order); }}运行本项目java运行5.1.4 其他传播行为示例@Servicepublic class OtherPropagationService { // PROPAGATION_SUPPORTS:支持当前事务,如果没有事务则以非事务方式执行 @Transactional(propagation = Propagation.SUPPORTS) public List<User> findUsers() { // 如果在事务中调用,加入事务 // 如果不在事务中调用,以非事务方式执行 return userRepository.findAll(); } // PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务则挂起 @Transactional(propagation = Propagation.NOT_SUPPORTED) public void performNonTransactionalOperation() { // 总是以非事务方式执行 // 如果当前有事务,会被挂起 performLongRunningOperation(); } // PROPAGATION_MANDATORY:必须在事务中执行,否则抛出异常 @Transactional(propagation = Propagation.MANDATORY) public void mandatoryTransactionMethod() { // 如果没有活动事务,抛出IllegalTransactionStateException performCriticalOperation(); } // PROPAGATION_NEVER:不能在事务中执行,否则抛出异常 @Transactional(propagation = Propagation.NEVER) public void neverInTransactionMethod() { // 如果当前有活动事务,抛出IllegalTransactionStateException performIndependentOperation(); }}运行本项目java运行5.2 事务隔离级别详解5.2.1 隔离级别对比@Servicepublic class IsolationLevelService { // READ_UNCOMMITTED:最低隔离级别,可能出现脏读 @Transactional(isolation = Isolation.READ_UNCOMMITTED) public List<User> readUncommittedExample() { // 可能读取到其他事务未提交的数据 return userRepository.findAll(); } // READ_COMMITTED:防止脏读,但可能出现不可重复读 @Transactional(isolation = Isolation.READ_COMMITTED) public User readCommittedExample(Long userId) { User user1 = userRepository.findById(userId).orElse(null); // 在这期间,其他事务可能修改了用户数据 performSomeOperation(); User user2 = userRepository.findById(userId).orElse(null); // user1和user2可能不同(不可重复读) return user2; } // REPEATABLE_READ:防止脏读和不可重复读,但可能出现幻读 @Transactional(isolation = Isolation.REPEATABLE_READ) public List<User> repeatableReadExample() { List<User> users1 = userRepository.findByStatus(UserStatus.ACTIVE); performSomeOperation(); List<User> users2 = userRepository.findByStatus(UserStatus.ACTIVE); // users1和users2中的现有记录相同,但users2可能包含新插入的记录(幻读) return users2; } // SERIALIZABLE:最高隔离级别,防止所有并发问题 @Transactional(isolation = Isolation.SERIALIZABLE) public void serializableExample() { // 完全串行化执行,性能最低但数据一致性最高 List<User> users = userRepository.findAll(); for (User user : users) { user.setLastAccessTime(new Date()); userRepository.save(user); } }}运行本项目java运行5.2.2 隔离级别实际应用场景@Servicepublic class BankingService { @Autowired private AccountRepository accountRepository; // 转账操作需要高隔离级别确保数据一致性 @Transactional(isolation = Isolation.SERIALIZABLE) public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) { Account fromAccount = accountRepository.findById(fromAccountId) .orElseThrow(() -> new AccountNotFoundException("From account not found")); Account toAccount = accountRepository.findById(toAccountId) .orElseThrow(() -> new AccountNotFoundException("To account not found")); if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException("Insufficient funds"); } fromAccount.setBalance(fromAccount.getBalance().subtract(amount)); toAccount.setBalance(toAccount.getBalance().add(amount)); accountRepository.save(fromAccount); accountRepository.save(toAccount); } // 查询余额可以使用较低的隔离级别 @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true) public BigDecimal getBalance(Long accountId) { Account account = accountRepository.findById(accountId) .orElseThrow(() -> new AccountNotFoundException("Account not found")); return account.getBalance(); } // 生成对账单可以使用REPEATABLE_READ确保数据一致性 @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = true) public AccountStatement generateStatement(Long accountId, Date startDate, Date endDate) { Account account = accountRepository.findById(accountId) .orElseThrow(() -> new AccountNotFoundException("Account not found")); List<Transaction> transactions = transactionRepository .findByAccountIdAndDateBetween(accountId, startDate, endDate); return new AccountStatement(account, transactions, startDate, endDate); }}运行本项目java运行第六章:Spring事务最佳实践和常见问题6.1 事务使用最佳实践6.1.1 事务边界设计// 错误示例:事务边界过大@Servicepublic class BadTransactionService { @Transactional public void processLargeDataSet(List<Data> dataList) { for (Data data : dataList) { // 长时间运行的操作 processComplexData(data); // 外部服务调用 externalService.sendNotification(data); // 文件操作 fileService.writeToFile(data); } // 事务持续时间过长,容易导致锁竞争和超时 }}// 正确示例:合理的事务边界@Servicepublic class GoodTransactionService { public void processLargeDataSet(List<Data> dataList) { for (Data data : dataList) { try { // 每个数据项使用独立事务 processSingleData(data); } catch (Exception e) { log.error("Failed to process data: {}", data.getId(), e); // 单个失败不影响其他数据处理 } } } @Transactional public void processSingleData(Data data) { // 只包含数据库操作的事务 dataRepository.save(processData(data)); // 非事务操作放在事务外 CompletableFuture.runAsync(() -> { externalService.sendNotification(data); fileService.writeToFile(data); }); }}运行本项目java运行6.1.2 只读事务优化@Servicepublic class OptimizedReadService { // 明确标记只读事务 @Transactional(readOnly = true) public List<User> findActiveUsers() { return userRepository.findByStatus(UserStatus.ACTIVE); } // 复杂查询使用只读事务 @Transactional(readOnly = true) public UserStatistics generateUserStatistics() { long totalUsers = userRepository.count(); long activeUsers = userRepository.countByStatus(UserStatus.ACTIVE); long inactiveUsers = userRepository.countByStatus(UserStatus.INACTIVE); return new UserStatistics(totalUsers, activeUsers, inactiveUsers); } // 分页查询优化 @Transactional(readOnly = true) public Page<User> findUsersWithPagination(Pageable pageable) { return userRepository.findAll(pageable); }}运行本项目java运行6.1.3 异常处理策略@Servicepublic class ExceptionHandlingService { @Transactional(rollbackFor = Exception.class) public void robustBusinessOperation(BusinessRequest request) { try { // 主要业务逻辑 performMainOperation(request); } catch (ValidationException e) { // 验证异常,记录日志但不回滚 log.warn("Validation failed: {}", e.getMessage()); throw new BusinessException("Invalid request", e); } catch (ExternalServiceException e) { // 外部服务异常,可能需要重试 log.error("External service failed: {}", e.getMessage()); // 标记为需要重试 markForRetry(request); throw e; } catch (Exception e) { // 其他异常,记录详细信息 log.error("Unexpected error in business operation", e); throw new SystemException("System error occurred", e); } } // 使用自定义异常控制回滚行为 @Transactional(rollbackFor = {DataIntegrityException.class}, noRollbackFor = {BusinessWarningException.class}) public void selectiveRollbackOperation() { try { performDataOperation(); } catch (BusinessWarningException e) { // 业务警告不回滚事务,但记录警告 log.warn("Business warning: {}", e.getMessage()); } }}运行本项目java运行6.2 常见问题与解决方案6.2.1 事务失效问题// 问题:内部方法调用导致事务失效@Servicepublic class TransactionFailureService { public void publicMethod() { // 直接调用内部方法,@Transactional不会生效 this.internalTransactionalMethod(); } @Transactional private void internalTransactionalMethod() { // 事务不会生效,因为是内部调用 performDatabaseOperation(); }}// 解决方案1:使用自注入@Servicepublic class SelfInjectionService { @Autowired private SelfInjectionService self; public void publicMethod() { // 通过代理对象调用,事务生效 self.internalTransactionalMethod(); } @Transactional public void internalTransactionalMethod() { performDatabaseOperation(); }}// ✅ 解决方案2:拆分到不同的Service@Servicepublic class CallerService { @Autowired private TransactionalService transactionalService; public void publicMethod() { transactionalService.transactionalMethod(); }}@Servicepublic class TransactionalService { @Transactional public void transactionalMethod() { performDatabaseOperation(); }}运行本项目java运行6.2.2 事务超时处理@Servicepublic class TimeoutHandlingService { // 设置合理的超时时间 @Transactional(timeout = 30) // 30秒超时 public void normalOperation() { performQuickOperation(); } // 长时间运行的操作需要更长的超时时间 @Transactional(timeout = 300) // 5分钟超时 public void longRunningOperation() { performBatchOperation(); } // 对于可能很长的操作,考虑分批处理 public void processLargeDataset(List<Data> dataList) { int batchSize = 100; for (int i = 0; i < dataList.size(); i += batchSize) { List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size())); processBatch(batch); } } @Transactional(timeout = 60) private void processBatch(List<Data> batch) { for (Data data : batch) { dataRepository.save(data); } }}运行本项目java运行6.2.3 死锁预防@Servicepublic class DeadlockPreventionService { // 按固定顺序获取锁,避免死锁 @Transactional public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) { // 确保按ID顺序获取锁 Long firstId = Math.min(fromAccountId, toAccountId); Long secondId = Math.max(fromAccountId, toAccountId); Account firstAccount = accountRepository.findByIdForUpdate(firstId); Account secondAccount = accountRepository.findByIdForUpdate(secondId); Account fromAccount = fromAccountId.equals(firstId) ? firstAccount : secondAccount; Account toAccount = toAccountId.equals(firstId) ? firstAccount : secondAccount; // 执行转账逻辑 performTransfer(fromAccount, toAccount, amount); } // 使用乐观锁避免死锁 @Transactional public void updateAccountWithOptimisticLock(Long accountId, BigDecimal amount) { int maxRetries = 3; int retryCount = 0; while (retryCount < maxRetries) { try { Account account = accountRepository.findById(accountId) .orElseThrow(() -> new AccountNotFoundException("Account not found")); account.setBalance(account.getBalance().add(amount)); accountRepository.save(account); return; // 成功,退出重试循环 } catch (OptimisticLockingFailureException e) { retryCount++; if (retryCount >= maxRetries) { throw new ConcurrencyException("Failed to update account after " + maxRetries + " retries"); } // 短暂等待后重试 try { Thread.sleep(100 * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("Thread interrupted", ie); } } } }}运行本项目java运行6.3 性能优化技巧6.3.1 批量操作优化@Servicepublic class BatchOptimizationService { // 低效的逐条处理 @Transactional public void inefficientBatchInsert(List<User> users) { for (User user : users) { userRepository.save(user); // 每次都执行SQL } } // 高效的批量处理 @Transactional public void efficientBatchInsert(List<User> users) { int batchSize = 50; for (int i = 0; i < users.size(); i += batchSize) { List<User> batch = users.subList(i, Math.min(i + batchSize, users.size())); userRepository.saveAll(batch); // 每批次后清理持久化上下文 if (i % batchSize == 0) { entityManager.flush(); entityManager.clear(); } } } // 使用JDBC批量操作 @Transactional public void jdbcBatchInsert(List<User> users) { String sql = "INSERT INTO users (name, email, status) VALUES (?, ?, ?)"; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { User user = users.get(i); ps.setString(1, user.getName()); ps.setString(2, user.getEmail()); ps.setString(3, user.getStatus().name()); } @Override public int getBatchSize() { return users.size(); } }); }}运行本项目java运行6.3.2 连接池配置优化@Configurationpublic class DataSourceOptimizationConfig { @Bean public DataSource optimizedDataSource() { HikariConfig config = new HikariConfig(); // 基本连接配置 config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb"); config.setUsername("root"); config.setPassword("password"); // 连接池优化配置 config.setMaximumPoolSize(20); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接数 config.setConnectionTimeout(30000); // 连接超时时间 config.setIdleTimeout(600000); // 空闲连接超时时间 config.setMaxLifetime(1800000); // 连接最大生存时间 // 性能优化配置 config.setLeakDetectionThreshold(60000); // 连接泄漏检测 config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); return new HikariDataSource(config); }}运行本项目java运行第七章:总结与展望7.1 核心知识点回顾通过本文的深入探讨,我们全面了解了Spring事务管理的各个方面:7.1.1 基础概念掌握ACID特性:原子性、一致性、隔离性、持久性是事务的基本保证Spring事务架构:PlatformTransactionManager、TransactionDefinition、TransactionStatus三大核心接口AOP实现原理:基于代理模式的声明式事务管理机制7.1.2 实践技能提升声明式事务:@Transactional注解的灵活使用和最佳实践编程式事务:TransactionTemplate和PlatformTransactionManager的直接使用传播行为:7种传播行为的适用场景和实际应用隔离级别:4种隔离级别的性能与一致性权衡7.1.3 问题解决能力常见陷阱:事务失效、死锁、超时等问题的识别和解决性能优化:批量操作、连接池配置、事务边界设计最佳实践:异常处理、只读事务、多数据源管理7.2 进阶学习路径7.2.1 深入源码研究// 建议研究的核心类// 1. AbstractPlatformTransactionManager - 事务管理器抽象实现// 2. TransactionInterceptor - 事务拦截器// 3. TransactionAspectSupport - 事务切面支持// 4. DefaultTransactionStatus - 事务状态实现// 示例:自定义事务管理器public class CustomTransactionManager extends AbstractPlatformTransactionManager { @Override protected Object doGetTransaction() throws TransactionException { // 获取事务对象的实现 return new CustomTransactionObject(); } @Override protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { // 开始事务的实现 } @Override protected void doCommit(DefaultTransactionStatus status) throws TransactionException { // 提交事务的实现 } @Override protected void doRollback(DefaultTransactionStatus status) throws TransactionException { // 回滚事务的实现 }}运行本项目java运行7.2.2 分布式事务探索// Seata分布式事务示例@GlobalTransactionalpublic class DistributedTransactionService { @Autowired private OrderService orderService; @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService; public void createOrder(OrderRequest request) { // 创建订单 Order order = orderService.createOrder(request); // 扣减库存 inventoryService.deductInventory(request.getItems()); // 处理支付 paymentService.processPayment(order.getId(), request.getPaymentInfo()); // 如果任何服务失败,全局事务会回滚 }}运行本项目java运行7.2.3 响应式事务管理// Spring WebFlux响应式事务@Servicepublic class ReactiveTransactionService { @Autowired private ReactiveTransactionManager transactionManager; public Mono<User> createUserReactive(User user) { return transactionManager.execute(status -> { return userRepository.save(user) .doOnError(error -> status.setRollbackOnly()); }); }}运行本项目java运行7.3 扩展阅读建议7.3.1 官方文档与规范Spring Framework Reference Documentation - Transaction ManagementJDBC Transaction Management7.3.2 深度技术文章《Spring事务管理源码深度解析》《分布式事务解决方案对比》《数据库事务隔离级别实现原理》7.3.3 相关技术栈MyBatis事务集成:深入了解MyBatis与Spring事务的集成机制JPA事务管理:掌握JPA规范下的事务管理特性分布式事务框架:Seata、Saga、TCC等分布式事务解决方案7.4 实践项目建议7.4.1 基础练习项目// 项目1:银行转账系统// 功能要求:// 1. 实现账户间转账功能// 2. 支持并发转账处理// 3. 实现转账记录和审计日志// 4. 处理各种异常情况@Servicepublic class BankTransferProject { @Transactional(isolation = Isolation.SERIALIZABLE) public TransferResult transfer(TransferRequest request) { // 实现转账逻辑 // 考虑并发控制、异常处理、审计日志等 }}运行本项目java运行7.4.2 进阶挑战项目// 项目2:电商订单系统// 功能要求:// 1. 订单创建涉及多个服务(库存、支付、物流)// 2. 实现分布式事务管理// 3. 支持订单状态机和补偿机制// 4. 性能优化和监控@GlobalTransactionalpublic class ECommerceOrderProject { public OrderResult createOrder(OrderRequest request) { // 实现复杂的订单创建流程 // 涉及多个微服务的协调 }}运行本项目java运行7.5 技术发展趋势7.5.1 云原生事务管理随着微服务和云原生架构的普及,事务管理正朝着以下方向发展:Saga模式:长事务的分解和补偿机制事件驱动架构:基于事件的最终一致性服务网格集成:Istio等服务网格中的事务管理7.5.2 响应式编程支持Spring WebFlux和响应式编程的兴起带来了新的事务管理需求:非阻塞事务:基于响应式流的事务处理背压处理:在事务中处理背压和流控异步事务协调:跨异步边界的事务管理7.6 讨论与思考7.6.1 开放性问题性能vs一致性:在高并发场景下,如何平衡事务的性能和数据一致性?微服务事务:在微服务架构中,是否应该避免跨服务事务?有哪些替代方案?事务边界设计:如何设计合理的事务边界来平衡业务完整性和系统性能?7.6.2 实践挑战遗留系统改造:如何将传统的事务管理代码迁移到Spring事务管理?测试策略:如何有效测试事务相关的代码,特别是异常场景?监控和诊断:如何监控事务性能和诊断事务相关问题?7.7 结语Spring事务管理是企业级Java开发中的核心技能,掌握它不仅需要理解理论知识,更需要在实践中不断积累经验。本文提供了从基础到高级的全面指南,但技术的学习永无止境。希望读者能够:持续实践:在实际项目中应用所学知识深入研究:探索更深层次的实现原理分享交流:与同行分享经验和最佳实践关注发展:跟上技术发展的最新趋势记住,优秀的事务管理不仅仅是技术实现,更是对业务逻辑的深刻理解和对系统架构的整体把握。让我们在Spring事务管理的道路上持续前进,构建更加健壮、高效的企业级应用————————————————原文链接:https://blog.csdn.net/weixin_63944437/article/details/153634279
-
引言MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,专为低带宽、高延迟或不稳定的网络环境设计。它广泛应用于物联网(IoT)、机器对机器(M2M)通信以及实时数据传输等领域。本文将详细介绍如何在 Java 中使用 MQTT 协议实现高效的消息传递。为什么选择 MQTT?MQTT 具有以下优点:轻量级:MQTT 协议头非常小,减少了网络带宽的占用。低延迟:MQTT 支持发布/订阅模式,消息传递速度快。可靠性:MQTT 提供了三种服务质量(QoS)级别,确保消息的可靠传递。灵活性:MQTT 支持保留消息、遗嘱消息等功能,满足不同场景的需求。MQTT 基础概念在深入 Java 实现之前,我们需要了解一些 MQTT 的基本概念:Broker:消息代理,负责接收发布者发送的消息并将其分发给订阅者。Publisher:消息发布者,负责将消息发送到 Broker。Subscriber:消息订阅者,负责从 Broker 接收消息。Topic:消息的主题,订阅者通过订阅特定的主题来接收消息。QoS:服务质量级别,MQTT 提供了 0、1、2 三个级别,分别代表不同的可靠性。在 Java 中使用 MQTT要在 Java 中使用 MQTT,我们可以使用 Eclipse Paho 库。Paho 是一个开源的 MQTT 客户端库,支持多种编程语言,包括 Java。1. 添加依赖首先,在项目的 pom.xml 文件中添加 Paho MQTT 客户端的依赖:代码语言:xmlAI代码解释<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version></dependency>AI写代码2. 创建 MQTT 客户端接下来,我们创建一个 MQTT 客户端实例,并连接到 MQTT Broker:代码语言:javaAI代码解释import org.eclipse.paho.client.mqttv3.*;import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class MqttExample { public static void main(String[] args) { String broker = "tcp://broker.hivemq.com:1883"; String clientId = "JavaSample"; MemoryPersistence persistence = new MemoryPersistence(); try { MqttClient client = new MqttClient(broker, clientId, persistence); MqttConnectOptions connOpts = new MqttConnectOptions(); connOpts.setCleanSession(true); System.out.println("Connecting to broker: " + broker); client.connect(connOpts); System.out.println("Connected"); // 订阅主题 String topic = "test/topic"; client.subscribe(topic); // 发布消息 String content = "Hello MQTT"; int qos = 2; MqttMessage message = new MqttMessage(content.getBytes()); message.setQos(qos); client.publish(topic, message); // 断开连接 client.disconnect(); System.out.println("Disconnected"); } catch (MqttException me) { System.out.println("reason " + me.getReasonCode()); System.out.println("msg " + me.getMessage()); System.out.println("loc " + me.getLocalizedMessage()); System.out.println("cause " + me.getCause()); System.out.println("excep " + me); me.printStackTrace(); } }}AI写代码3. 处理消息回调为了接收和处理从 Broker 发送的消息,我们需要实现 MqttCallback 接口,并将其设置为 MQTT 客户端的回调:代码语言:javaAI代码解释import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;import org.eclipse.paho.client.mqttv3.MqttCallback;import org.eclipse.paho.client.mqttv3.MqttMessage; public class MqttExample implements MqttCallback { @Override public void connectionLost(Throwable cause) { System.out.println("Connection lost"); cause.printStackTrace(); } @Override public void messageArrived(String topic, MqttMessage message) throws Exception { System.out.println("Message arrived: " + new String(message.getPayload())); } @Override public void deliveryComplete(IMqttDeliveryToken token) { System.out.println("Delivery complete"); } // 其他代码...}AI写代码4. 运行示例将上述代码整合到一个完整的 Java 类中,并运行该类。你将看到以下输出:代码语言:javaAI代码解释Connecting to broker: tcp://broker.hivemq.com:1883ConnectedMessage arrived: Hello MQTTDisconnectedAI写代码总结通过本文,我们了解了如何在 Java 中使用 MQTT 协议实现高效的消息传递。我们介绍了 MQTT 的基本概念,展示了如何使用 Eclipse Paho 库创建 MQTT 客户端,订阅主题,发布消息以及处理消息回调。MQTT 在物联网和实时数据传输领域具有广泛的应用前景。———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_43044226/article/details/153143643
-
1.前置知识1.1 Tomcat定义:Tomcat是一个开源的轻量级Web(Http)服务器和Servlet容器。它实现了Java Servlet等Java EE规范的核心功能,常用于部署和运行Java Web应用程序 。换言之,Tomcat就是一个严格遵循Servlet规范开发出来的、可以独立安装和运行的Java Web服务器/Servlet容器核心功能:Servlet容器:支持Servlet的执行,处理HTTP请求和响应Web服务器:提供静态资源(如HTML)的访问能力,支持基本的HTTP服务安装与版本对应:tomcat官网:Apache Tomcat®目录结构:bin:存放可执行文件,如startup.batconf:存放配置文件lib:存放Tomcat运行所需的jar文件logs:存储日志文件temp:存放临时文件,如上传的文件或缓存数据webapps:默认web应用部署目录work:服务器的工作目录,存放运行时生成的临时文件(编译文件)1.2 Servlet1.2.1 定义Servlet是Java语言编写的、运行在服务器端的程序,它遵循一套标准的API规范(Tomcat是这套规范的一个具体实现/容器,并提供了让Servlet与前端交互的运行时环境)1.2.2 API示范创建项目/配置文件:(1)在IEDA中创建Maven项目(2)在pom.xml文件中添加servlet依赖(置于< project >< /project >标签下)<dependencies> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <!--servlet依赖版本应与jdk和tomcat的版本相匹配--> <version>6.1.0</version> <scope>provided</scope> </dependency></dependencies>(3)在main路径下创建webapp/Web-INF/web.xml,在xml文件中添加以下内容<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name></web-app>(4)下载插件:Smart Tomcat(为了方便启动项目)API示例:import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;//设置访问路径(url)@WebServlet("/method")//继承HttpServlet并重写doGet和doPost方法public class MethodServlet extends HttpServlet { //接收method=post的请求 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { System.out.println("doPost"); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("doPost"); } //接收method=put的请求 @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { System.out.println("doPut"); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("doPut"); } //接收method=delete的请求 @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { System.out.println("doDelete"); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("doDelete"); }}1.2.3 生命周期定义:Servlet 生命周期由 Web容器(如Tomcat)管理,包含加载、初始化、处理请求和销毁四个阶段。每个阶段对应特定的方法调用,开发者可通过重写这些方法实现自定义逻辑1.类加载:Web容器通过类加载器加载 Servlet 类(通常首次请求触发或容器启动时预加载,字节码文件被加载到内存但未实例化)。具体类加载流程请阅读:Java虚拟机——JVM(JavaVirtualMachine)解析一1.5 实例化:确认Servlet类成功加载后立刻执行,在整个Web容器中每种Servlet类(如HttpServlet)只会有一个实例化对象2.初始化:Web容器调用刚刚创建好的Servlet实例的init(ServletConfig config)方法,在整个servlet实例的生命周期中仅调用一次,主要作用是读取配置和资源加载。若初始化失败,抛出ServletException,Servlet不会被加入可用队列3.处理请求:Web容器为每个请求创建线程,调用service(ServletRequest req, ServletResponse res)方法。service() 方法根据HTTP请求类型(get/post)调用doGet()或doPost()4.销毁:Web容器调用 destroy()方法,Servlet 实例被标记为垃圾回收2.SpringBootServlet是Java EE规范中处理Web请求的核心组件,但随着应用复杂度提升,Servlet的直接使用显得笨重。Spring框架通过一系列抽象和扩展,简化了企业级应用开发我们可以用一个非常形象的比喻来贯穿始终:建造一座房子第一阶段:Servlet 时代 - 自己烧砖砌墙目标: 建造一个能遮风挡雨的房子(一个能处理HTTP请求的Web应用)你的工作状态:材料: 你有最基础的原材料——泥土(Java 语言)和水(JVM)。你需要自己烧制砖块(编写Servlet类)工具: 只有简单的泥瓦刀(Servlet API)过程:1.你为每一面墙、每一扇门都亲手烧制一块特定的砖(编写 LoginServlet, UserServlet, OrderServlet)2.你亲自规划每块砖的位置(在web.xml中配置大量的 < servlet > 和 < servlet-mapping >)3.你亲自搅拌水泥,一块一块地砌墙(在每个 Servlet 的doGet/doPost 方法中手动解析参数、处理业务、组装 HTML)核心特点:高度灵活: 你可以造出任何形状的砖极其繁琐: 大量重复性劳动(每个 Servlet 都有获取参数、关闭连接等样板代码)难以维护: 砖块之间紧密耦合(对象依赖硬编码),想换一扇窗(修改功能)可能会牵动整面墙依赖外部: 房子建在别人的地上(需要将war包部署到外部的Tomcat服务器)总结:Servlet提供了Web开发的基础能力,但开发效率极低,代码冗余且难以维护第二阶段:Spring 时代 - 使用预制件和设计图纸目标: 用更高效、更标准化的方式建造一个结构更好、更易扩展的房子你的工作状态:材料: 你不再烧砖,而是使用工厂提供的标准化预制件(Spring Bean,如 @Controller, @Service)核心创新: 你聘请了一位神奇的管家(IoC 容器)你不再亲自“砌砖”(用new实例化对象),只需告诉管家你需要什么(用@Autowired声明依赖)管家会自动把预制件(Bean)按照图纸(配置)组装好,送到你手上(依赖注入DI)过程:1.一个总大门(DispatcherServlet): 房子只有一个入口,所有访客(请求)都先到这里2.管家调度: 总大门处的接待员(DispatcherServlet)根据访客需求,呼叫房子里对应的专业房间(@Controller中的方法)来接待3.开发者只需专注于房间内的专业服务(业务逻辑),而不用关心访客是怎么进来的核心特点:解耦: 预制件之间是松耦合的,易于更换和测试专业化: AOP(面向切面编程)可以像“装修队”一样,非侵入式地为所有房间统一安装中央空调(日志、安全、事务)效率提升: 避免了大量重复劳动,结构清晰配置复杂: 绘制详细的“组装图纸”(配置 Spring)本身成了一项复杂的工作总结:Spring框架通过IoC/DI和AOP等理念,解决了代码耦合和重复劳动问题,但引入了显著的配置复杂度Spring Boot 1.0.0正式发布于2014年4月1日,标志着该框架的首次稳定版本发布。SpringBoot基于SpringFramework 4进行设计,显著减少了开发者的配置工作量,彻底消除了Spring的配置地狱约定大于配置:约定了默认配置Start机制:是一种依赖管理机制,每个Starter包含特定功能所需的依赖库和自动配置类,开发者只需引入对应Starter即可快速启用功能模块嵌入式容器:内置了Tomcat等嵌入式容器,无需部署war文件到外部容器,直接运行即可启动应用3.Spring Web MVC3.1 概述官方描述:Spring Web MVC是基于Servlet API构建的原始Web框架,并从一开始就在 Spring框架中。正式名称“Spring Web MVC”, 来自其源模块的名称(spring-webmvc),但它通常被称为“Spring MVC”MVC的起源与发展:MVC(Model-View-Controller)模式最初由挪威计算机科学家Trygve Reenskaug于1978年在施乐帕克研究中心(Xerox PARC)提出,目的是为Smalltalk编程语言设计用户界面。其核心思想是将应用程序的逻辑分为三个独立组件:Model:处理数据逻辑和业务规则View:负责数据展示和用户界面Controller:接收用户输入并协调Model与View的交互Spring MVC与MVC的关系:Spring MVC是MVC模式在Spring框架中的具体化,同时扩展了传统MVC的功能以适应现代Web开发需求3.2 必需工具Postman:主要用于 API 的开发和测试。它提供了一个用户友好的界面,支持发送HTTP请求、管理请求历史、自动化测试以及团队协作下载地址:Download PostmanFiddler:是一个网络调试代理工具,主要用于监控和分析HTTP/HTTPS流量。它可以捕获设备与服务器之间的所有请求和响应,支持修改请求、重放请求以及性能分析下载地址:Fiddler - Download3.3 RequestMapping作用:是Spring MVC中最核心、最基础的注解之一,用于将HTTP请求映射到具体的方法上注解级别:类+方法作为类注解:可以为整个类提供一个统一的url前缀(可有可无)作为方法注解:指定该方法负责处理哪个url的请求(强制要求)@RequestMapping("/HelloController")//@RestController声明该类是一个Spring MVC控制器@RestControllerpublic class HelloController { //0.不接收参数 //作为 方法注解 时,@RequestMapping(value = "/hello",method = RequestMethod.GET)等同于@GetMapping(value = "/hello") //设置返回的响应是json格式,produces = "application/json" @RequestMapping(value = "/hello",method = RequestMethod.GET,produces = "application/json") public String hello() { return "{\"Hello\" : World}"; } //1.一个参数 @RequestMapping("/receiveAge1") //不传参或者传递的参数名不匹配时默认为null public String receiveAge1(Integer age) { return "接收到参数 age:" + age; } @RequestMapping("/receiveAge2") //不传参或者传递的参数名不匹配时尝试设置为null,但int无法被设置为null,所以抛出IllegalStateException public String receiveAge2(int age) { return "接收到参数 age:" + age; } //2.接收数组 @RequestMapping("/receiveArray") public String receiveArray(String[] array) { return "接收到参数 array:" + Arrays.toString(array); } //3.接收对象,需要保证传递的参数名称和数量与Java对象保持一致 @RequestMapping("/receivePerson") public String receivePerson(Person person) { return "接收到参数 person:" + person; }}3.4 RequestBody作用:将HTTP请求体中的json数据绑定到Java对象(方法注解)注解级别:方法 @RequestMapping("/receivePerson") //@RequestBody接收JSON格式的数据 public String receivePerson(@RequestBody Person person) { return "接收到参数 person:" + person; }3.5 RequestParam作用:是Spring MVC框架中从HTTP请求中提取参数/查询字符串的注解,主要用于将请求参数绑定到控制器方法的参数上注解级别:方法 @RequestMapping("/receiveRename") //@RequestParam将url中key=name的查询字符串绑定到控制器的userName参数上 //required = false设置该参数为非必传(默认为true,必传) public String receiveRename(@RequestParam(value = "name",required = false) String userName) { return "接收到参数name:" + userName; }注意:需要接收多个同名参数时(如param=value1¶m=value2),直接绑定到List类型需通过该注解明确声明 @RequestMapping("/receiveList1") public String receiveList1(ArrayList<String> list) { //返回的list为空 return "接收到参数 list:" + list;(1)在Spring MVC中,参数绑定机制对集合类型和数组类型的处理存在差异(2)使用ArrayList< String >作为方法参数时,必须显式添加@RequestParam注解,原因如下:默认绑定规则:Spring默认将单个请求参数的值绑定到简单类型(如 String、int)或单个对象。对于集合类型,框架无法自动推断是否需要将多个同名参数合并为集合需要明确指示:@RequestParam注解会告知Spring将同名请求参数的值收集到一个集合中(3)数组(如 String[])无需 @RequestParam 注解即可正确接收,原因如下:内置支持:Spring对数组类型有原生支持,能自动将多个同名请求参数值绑定到数组。这是框架的默认行为,无需额外配置 @RequestMapping("/receiveList2") public String receiveList2(@RequestParam(required = false) ArrayList<String> list) { //正确返回 return "接收到参数 list:" + list; } @RequestMapping("/receiveList3") public String receiveList3(List<String> list) { //报错 return "接收到参数 list:" + list; }后端报错:java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List。receiveList3方法使用List< String >接口类型而非具体实现类。Spring虽然支持接口类型参数绑定,但需要满足特定条件:必须配合@RequestParam注解使用不能直接使用未注解的接口类型参数报错根本原因:Spring尝试实例化List接口失败(接口不可实例化)3.6 PathVariable作用:用于从URL路径中提取变量值并绑定到方法的参数上注解级别:方法 @RequestMapping("/receivePath/{article}/{blog}") //required = false设置该参数为非必传(默认为true,必传) public String receivePath(@PathVariable(value = "article",required = false)Integer title,@PathVariable(value = "blog",required = false)String content) { return "接收到参数 article:" + title + " blog:" + content; }3.7 RequestPart作用:用于处理 HTTP 请求中的 multipart/form-data 类型数据,通常用于文件上传或同时上传文件和其他表单字段的场景注解级别:方法 @RequestMapping("/receiveFile") public String receiveFile(@RequestPart(value = "file",required = false) MultipartFile imgFile,@RequestParam(value = "userName",required = false) String name) { //返回原始的文件名 return "用户:" + name+ ",接收到文件:" +imgFile.getOriginalFilename(); }3.8 Controller&ResponseBody&RestControllerController作用:是Spring MVC中的核心注解,用于标记一个类作为Web请求的处理器(声明一个类是一个Spring MVC控制器),负责处理HTTP请求并返回视图注解级别:类ResponseBody作用:指示方法返回值应直接写入HTTP响应体,而非通过视图解析器渲染注解级别:类+方法@RequestMapping("/ControllerResponse")@Controllerpublic class ControllerResponse { //返回视图 @RequestMapping("/HTMLView") public String HTMLView(){ return "/show.html"; } //返回数据 @ResponseBody @RequestMapping("/HTMLData") public String HTMLData(){ return "/show.html"; }}RestController作用:是Spring MVC中的一个组合注解,它结合了@Controller和@ResponseBody的功能,标记的类所有方法返回值默认直接作为 HTTP 响应体(JSON/XML 等格式),无需额外视图渲染注解级别:类4.GiteeGitee地址:九转苍翎本文源码:SpringBoot_Demo1———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2401_89167985/article/details/148194312
-
前言 自古以来,中秋佳节便与圆月紧密相连,成为人们寄托思念与团圆之情的象征。在民间流传着这样一种说法:“十五的月亮十六圆”,仿佛这已成为一种铁律,深入人心。然而,这种说法是否真的站得住脚呢?在这背后,隐藏着怎样的天文奥秘?又是否可以通过科学的方法来验证这一传统观念呢?在科技飞速发展的今天,我们不妨借助编程的力量,运用Java语言来实证求解,揭开中秋满月的真相。 中秋赏月的传统由来已久,早在《周礼》中就有“中秋夜迎寒”的记载,而到了唐代,中秋赏月、玩月的风俗开始盛行。文人墨客们更是留下了许多描写中秋月夜的佳作,如苏轼的“但愿人长久,千里共婵娟”,将中秋的月与人间的思念紧密相连,赋予了中秋月深厚的文化内涵。在这样的文化背景下,“十五的月亮十六圆”这一说法也逐渐流传开来,成为人们茶余饭后的话题之一。然而,这种说法真的准确无误吗? 本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。在接下来的章节中,我们将详细介绍如何使用Java语言进行天文数据的处理和计算,以及如何通过模拟实验来验证“十五的月亮十六圆”这一说法。我们将逐步展开这一探索之旅,最终揭示中秋满月的真相。让我们一起踏上这段充满趣味和挑战的旅程,用科学的视角重新审视中秋的圆月,探索其中隐藏的奥秘。一、天文上的满月 在天文学中,月亮的圆缺变化是一个非常有趣且复杂的自然现象,这种变化主要源于月球绕地球的公转运动。月球绕地球运行一周的时间大约是29.5天,这个周期被称为一个“朔望月”。在这个周期中,月球相对于太阳的位置不断变化,从而导致我们从地球上看到的月相也随之改变。博主不是专业天文专业,这里仅分享一些简单的满月基础知识,让大家有一个概念。1、形成原理及定义 说到满月就必须提及月相,月相的形成是由于太阳光照射月球的不同部分,而我们从地球上看到的只是月球被太阳照亮的那一部分。随着月球绕地球的公转,被太阳照亮的部分逐渐增加,依次出现“娥眉月”“上弦月”“凸月”“满月”“下弦月”“残月”等不同的月相。其中满月是指月球完全被太阳照亮的那一面朝向地球,此时月球与太阳在地球的两侧,三者几乎在一条直线上。理论上,满月应该出现在农历的十五或十六,但实际的情况并非总是如此。由于月球的公转轨道是椭圆形的,且受到多种因素的影响,如地球的引力、太阳的引力等,月球的实际运行轨迹并非完全规律,因此满月出现的时间也会有所变化。2、出现时间及观测 “十五的月亮十六圆”这一说法广为流传,但实际上满月并不总是出现在农历的十六。根据天文观测数据,满月可能出现在农历的十四到十七之间的任何一天。例如,在某些年份,满月可能出现在农历十四的晚上,而在另一些年份,满月可能出现在农历十七的早晨。这种变化是由于月球的公转速度和轨道形状的不规则性所导致的。满月是观测月球的最佳时机之一,因为此时月球的整个盘面都被照亮,可以清晰地看到月球表面的山脉、陨石坑和月海等特征。在满月期间,月球的亮度会达到最大,这使得它在夜空中格外明亮。3、文化意义 在许多文化中,满月都具有重要的象征意义。在中国文化中,满月象征着团圆和完满,因此中秋节成为了家人团聚的重要节日。在西方文化中,满月也常常与神秘和浪漫联系在一起,许多文学作品和民间传说都以满月为背景。二、Java模拟月满计算 随着计算机技术的发展,我们有了更强大的工具来探索和验证这些天文现象。Java作为一种广泛使用的编程语言,具有强大的功能和灵活性,可以用来编写各种复杂的算法和程序。在本研究中,我们将利用Java语言编写程序,通过计算月球在不同时间的位置,来确定中秋满月的具体时间。我们将收集多年来的天文数据,包括月球的公转周期、轨道参数等,然后利用这些数据进行模拟计算。通过这种方式,我们可以得到一个较为准确的中秋满月时间表,从而验证“十五的月亮十六圆”这一说法的准确性。1、整体实现逻辑 使用Java求解中秋满月整体时间逻辑如下:public class MidAutumnFullMoonCalculator { // 主计算方法 public static Date calculateFullMoonTime(int year, int month, int day) { ... } // 核心天文算法 private static double calculateFullMoonJulianDay(double jd) { ... } // 辅助方法 private static double normalizeAngle(double angle) { ... } private static double calendarToJulianDay(Calendar cal) { ... } private static Calendar julianDayToCalendar(double jd) { ... }}AI写代码java运行2、主计算方法详解 功能:这是程序的入口点,接收农历中秋的公历日期,返回精确的满月时刻,核心方法如下:/** * -计算指定农历中秋日期的月亮最圆时刻 * @param year 年份 * @param month 农历月份(八月) * @param day 农历日期(十五) * @return 月亮最圆时刻的Date对象 */public static Date calculateFullMoonTime(int year, int month, int day) { // 创建农历中秋日期(使用中午12点作为基准时间) Calendar midAutumnDate = Calendar.getInstance(); midAutumnDate.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); midAutumnDate.set(year, month - 1, day, 12, 0, 0); // month-1因为Calendar月份从0开始 // 计算精确的满月时刻 return calculatePreciseFullMoonTime(midAutumnDate);}AI写代码java运行 参数说明:year:公历年份(如2024)month:公历月份(如9)day:公历日期(如17) 处理流程:创建Calendar对象,设置为北京时间将时间设为中午12点作为计算基准调用核心算法计算精确的满月时刻3、核心天文算法详解 核心算法是相关计算中最核心的内容,主要包括儒略日的计算、时间参数的计算、天文参数计算和周期项修正等内容,这里的天文计算采用近似计算,如需精度计算,请使用更精准的天文算法。3.1 儒略日计算基础/** * -计算满月时刻的儒略日 * -基于Jean Meeus的天文算法 */private static double calculateFullMoonJulianDay(double jd) { // 计算从2000年1月6日(基准新月)开始的月相周期数 double k = Math.floor((jd - 2451550.09765) / 29.530588853); // 满月对应的k值(新月+0.5) k = k + 0.5; // 计算T(儒略世纪数) double T = k / 1236.85; //----其它计算}AI写代码java运行 儒略日(Julian Day):天文学中常用的连续时间计数法,从公元前4713年1月1日格林尼治平午开始计算。月相周期数k:2451550.09765:2000年1月6日18:14的儒略日,作为一个基准新月时刻29.530588853:一个朔望月的平均长度(天)k:从基准时间开始经过的月相周期数k + 0.5:从新月到满月是半个周期3.2 时间参数计算// 计算T(儒略世纪数)double T = k / 1236.85; // 计算基础儒略日double JDE = 2451550.09765 + 29.530588853 * k + 0.0001337 * T * T - 0.000000150 * T * T * T + 0.00000000073 * T * T * T * T;AI写代码java运行 T(儒略世纪数):以36525天为一世纪的时间单位,用于高阶项的计算。 (儒略历书日):考虑了长期项修正的基础满月时刻。3.3 天文参数计算 // 计算太阳平近点角 double M = normalizeAngle(2.5534 + 29.10535669 * k - 0.0000218 * T * T - 0.00000011 * T * T * T); // 计算月亮平近点角double Mprime = normalizeAngle(201.5643 + 385.81693528 * k + 0.1017438 * T * T + 0.00001239 * T * T * T - 0.000000058 * T * T * T * T); // 计算月亮升交点平黄经double F = normalizeAngle(160.7108 + 390.67050274 * k - 0.0016341 * T * T - 0.00000227 * T * T * T + 0.000000011 * T * T * T * T); // 计算Omega(月亮轨道升交点经度)double Omega = normalizeAngle(124.7746 - 1.56375580 * k + 0.0020691 * T * T + 0.00000215 * T * T * T);AI写代码java运行 天文参数说明:M(太阳平近点角):太阳在轨道上的平均位置角度系数:2.5534° + 29.10535669°/周期反映地球公转轨道的椭圆性影响M'(月亮平近点角):月亮在轨道上的平均位置角度系数:201.5643° + 385.81693528°/周期反映月球公转轨道的椭圆性影响F(月亮升交点平黄经):月球轨道与黄道交点的平均位置系数:160.7108° + 390.67050274°/周期反映月球轨道平面的进动Ω(月亮轨道升交点经度):更精确的轨道交点位置系数:124.7746° - 1.56375580°/周期3.4 周期项修正计算// 转换为弧度double M_rad = Math.toRadians(M);double Mprime_rad = Math.toRadians(Mprime);double F_rad = Math.toRadians(F);double Omega_rad = Math.toRadians(Omega);// 计算周期项修正double correction = 0;// 主要修正项correction += -0.40720 * Math.sin(Mprime_rad);correction += 0.17241 * 0.016708617 * Math.sin(M_rad);correction += 0.01608 * Math.sin(2 * Mprime_rad);correction += 0.01039 * Math.sin(2 * F_rad);correction += 0.00739 * 0.016708617 * Math.sin(Mprime_rad - M_rad);correction += -0.00514 * 0.016708617 * Math.sin(Mprime_rad + M_rad);correction += 0.00208 * 0.016708617 * 0.016708617 * Math.sin(2 * M_rad);correction += -0.00111 * Math.sin(Mprime_rad - 2 * F_rad);correction += -0.00057 * Math.sin(Mprime_rad + 2 * F_rad);correction += 0.00056 * 0.016708617 * Math.sin(2 * Mprime_rad + M_rad);correction += -0.00042 * Math.sin(3 * Mprime_rad);correction += 0.00042 * 0.016708617 * Math.sin(M_rad + 2 * F_rad);correction += 0.00038 * 0.016708617 * Math.sin(M_rad - 2 * F_rad);correction += -0.00024 * 0.016708617 * Math.sin(2 * Mprime_rad - M_rad);correction += -0.00017 * Math.sin(Omega_rad);correction += -0.00007 * Math.sin(Mprime_rad + 2 * M_rad);correction += 0.00004 * Math.sin(2 * Mprime_rad - 2 * F_rad);correction += 0.00004 * Math.sin(3 * M_rad);correction += 0.00003 * Math.sin(Mprime_rad + M_rad - 2 * F_rad);correction += 0.00003 * Math.sin(2 * Mprime_rad + 2 * F_rad);correction += -0.00003 * Math.sin(Mprime_rad + M_rad + 2 * F_rad);correction += 0.00003 * Math.sin(Mprime_rad - M_rad + 2 * F_rad);correction += -0.00002 * Math.sin(Mprime_rad - M_rad - 2 * F_rad);correction += -0.00002 * Math.sin(3 * Mprime_rad + M_rad);correction += 0.00002 * Math.sin(4 * Mprime_rad); // 应用修正double preciseJDE = JDE + correction;AI写代码java运行修正项原理:每个修正项都对应一个特定的天文效应:-0.40720 × sin(M'):月球椭圆轨道的主要修正(中心差)0.17241 × e × sin(M):地球轨道偏心率对月相的影响0.01608 × sin(2M'):月球轨道的二阶椭圆项0.01039 × sin(2F):月球轨道倾角的影响0.00739 × e × sin(M' - M):地球和月球轨道相互影响-0.00514 × e × sin(M' + M):地球和月球轨道的组合效应e = 0.016708617:地球轨道偏心率这些修正项基于布朗月球运动理论,考虑了月球轨道的各种摄动因素。4、辅助方法详解 本小节将对辅助方法进行简单介绍。4.1 角度标准化/** * -将角度标准化到0-360度范围内 */private static double normalizeAngle(double angle) { angle = angle % 360; if (angle < 0) { angle += 360; } return angle;}AI写代码java运行 功能:将角度限制在0-360度范围内,避免数值溢出。4.2 日历与儒略日转换/** * -将Calendar转换为儒略日 */private static double calendarToJulianDay(Calendar cal) { int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH) + 1; int day = cal.get(Calendar.DAY_OF_MONTH); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND); double decimalHour = hour + minute / 60.0 + second / 3600.0; if (month <= 2) { year--; month += 12; } int a = year / 100; int b = 2 - a + a / 4; return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + decimalHour / 24.0 + b - 1524.5;}AI写代码java运行 转换公式:标准的天文儒略日计算公式,考虑了:闰年规则格里高利历改革(1582年)时间的小数部分处理4.3 儒略日转日历/** * -将儒略日转换为Calendar */private static Calendar julianDayToCalendar(double jd) { jd += 0.5; double z = Math.floor(jd); double f = jd - z; double a; if (z < 2299161) { a = z; } else { double alpha = Math.floor((z - 1867216.25) / 36524.25); a = z + 1 + alpha - Math.floor(alpha / 4); } double b = a + 1524; double c = Math.floor((b - 122.1) / 365.25); double d = Math.floor(365.25 * c); double e = Math.floor((b - d) / 30.6001); double day = b - d - Math.floor(30.6001 * e) + f; int month = (int) (e < 14 ? e - 1 : e - 13); int year = (int) (month > 2 ? c - 4716 : c - 4715); double time = day - Math.floor(day); int hour = (int) (time * 24); int minute = (int) ((time * 24 - hour) * 60); int second = (int) Math.round((((time * 24 - hour) * 60 - minute) * 60)); // 处理秒数进位 if (second >= 60) { second = 0; minute++; } if (minute >= 60) { minute = 0; hour++; } Calendar cal = Calendar.getInstance(); cal.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); cal.set(year, month - 1, (int) Math.floor(day), hour, minute, second); cal.set(Calendar.MILLISECOND, 0); return cal;}AI写代码java运行 关键点:jd += 0.5:儒略日从中午开始,调整为从午夜开始处理格里高利历改革(1582年10月4日后跳过10天)精确的时间分量计算三、近年中秋满月计算及对比 本节将结合实例对每年的中秋月满时间进行计算,通过本小节就可以获取每年的满月日期和具体的时间,并且与官方提供的时间进行对比,大家通过对比就可以知晓问题的开始,是不是所有的月亮都是十六圆了。 1、近年中秋满月计算/** * -测试方法 - 计算未来几年的中秋节月亮最圆时刻 */public static void main(String[] args) { // 已知的农历中秋日期(公历日期) int[][] midAutumnDates = { {2019, 9, 13}, // 2019年中秋节 {2020, 10, 1}, // 2020年中秋节 {2021, 9, 21}, // 2021年中秋节 {2022, 9, 10}, // 2022年中秋节 {2023, 9, 29}, // 2023年中秋节 {2024, 9, 17}, // 2024年中秋节 {2025, 10, 6}, // 2025年中秋节 {2026, 9, 25}, // 2026年中秋节 }; SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); System.out.println("中秋节月亮最圆时刻计算结果:"); System.out.println("================================="); for (int[] date : midAutumnDates) { int year = date[0]; int month = date[1]; int day = date[2]; Date fullMoonTime = calculateFullMoonTime(year, month, day); System.out.printf("%d年中秋节(公历%d月%d日)月亮最圆时刻: %s%n", year, month, day, sdf.format(fullMoonTime)); }}AI写代码java运行 接下来我们在IDE中运行意以上成就可以得到以下结果: 以上就是实现一个从2019年到2026年,跨度为7年的中秋满月计算过程。2、近年计算与公布时间对比 通过以上7年的计算,再结合官方公布的满月日期及时刻,来对比一下我们的计算方法与官方公布的时间相差是多少?年份 中秋(公历) 满月时间(本地) 是否当天 满月时间(官方公布) 误差2019 2019-9-13 09月14日 08时39分21秒 否(十六) 9月14日12时33分 3时54分2020 2020-10-1 10月02日 01时28分17秒 否(十六) 10月2日 05时5分 3时37分2021 2021-9-21 09月21日 03时58分38秒 是(十五) 9月21日 07时54分 3时56分2022 2022-9-10 09月10日 13时34分12秒 是(十五) 9月10日 17时59分 4时25分2023 2023-9-29 09月29日 13时49分05秒 是(十五) 9月29日 17时58分 4时8分2024 2024-9-17 09月18日 06时15分39秒 否(十六) 9月18日 10时34分 4时19分2025 2024-10-06 10月07日 07时41分10秒 否(十六) 10月7日 11时48分 4时7分 结合近七年的满月日期及时刻来看,并不是所有的中秋月圆都是十六圆,有的是当天就圆了。所以,从这个角度来定义,十五的月亮十六圆可不是准确的哦。通过这种本地近似的计算,虽然在具体的时刻上有一些误差,但是日期是与官方公布的是完全一致的,时刻的误差通过近7年的验证,相差时间在4个小时左右,所以未来可以结合更长序列的时间进行相应的修正。四、总结 以上就是本文的主要内容,本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。通过Java满月近似求解,并结合2019年到2025年的中秋满月日期时刻的计算,得出了重要的一个结论,十五的月亮不一定十六圆,通过严谨的程序计算得到的数据支撑。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/152717752
-
1.配置文件1.1 概述计算机配置文件:用于存储系统、应用程序的设置信息,通常以文本或结构化数据格式(如JSON、XML、INI等)保存。其核心功能包括但不限于:参数定制:允许用户或管理员调整软件或硬件的运行参数环境适配:根据不同设备或场景加载特定配置(如开发/生产环境)持久化存储:确保重启后设置仍生效SpringBoot配置文件:SpringBoot支持多种类型的配置文件,常见的格式包括properties、yaml和yml,主要用于集中管理应用程序的各种配置参数,简化部署和开发过程中的环境切换YAML和YML本质上是相同的文件格式,只是文件扩展名的不同,两者在功能和使用上没有区别1.2 propertiesproperties配置文件是最早期的配置⽂件格式,也是创建SpringBoot项⽬默认的配置⽂件采用常见的键值对格式(key=value)支持#开头的注释#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root1.3 yml采用键值对格式(key: value),冒号后必须有空格数据序列化格式,通过缩进表示层级关系支持#开头的注释spring: application: #应用程序名称 name: configuration #数据库连接信息 datasource: url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false username: root password: root#应用程序端口号server: port: 80801.4 优缺点对比properties优点:语法简单直观,采用key=value形式,适合初学者快速上手与Java生态兼容性极强缺点:缺乏层次结构,复杂配置时容易冗余。上述配置数据库连接信息时spring.datasource前缀冗余不支持数据类型定义,所有值均为字符串,需手动转换yml优点:层次化结构清晰,通过缩进表示层级,适合复杂配置场景支持数据类型(如布尔值、数字),减少手动类型转换缺点:格式错误易导致解析失败(容易忽略冒号后空格)部分旧版工具链兼容性较差,需额外依赖解析库注:SpringBoot同时支持两种格式,混合使用时若key重复,properties优先级高于yml1.5 @Value注解作用:是Spring框架提供了一个@Value注解(org.springframework.beans.factory.annotation.Value),用于将外部配置文件中的值注入到Spring管理的Bean中示例:(properties和yml的读取方式相同)package org.example.configuration.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class Config { @Value("${spring.application.name}") private String applicationName; @Value("${server.port}") private Integer port; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; public void print() { System.out.println("applicationName=" + applicationName); System.out.println("port=" + port); System.out.println("url=" + url); System.out.println("username=" + username); System.out.println("password=" + password); }}package org.example.configuration;import org.example.configuration.config.Config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class ConfigurationApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(ConfigurationApplication.class, args); Config config = context.getBean(Config.class); config.print(); }}运行结果:applicationName=configurationport=8080url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falseusername=rootpassword=root2.mybatis2.1 概述MyBatis是一款优秀的持久层框架,支持自定义 SQL、存储过程、高级映射以及多种配置方式。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索支持存储过程:指的是数据库管理系统(DBMS)允许用户创建、存储和执行存储过程的能力。存储过程是一组预编译的SQL语句,存储在数据库中,可以被应用程序调用执行支持高级映射:指通过配置或注解实现复杂SQL查询结果与Java对象之间的灵活转换。其核心目标是简化数据库关联操作,提升开发效率支持多种配置方式:mybatis支持注解和xml两种配置方式2.2 前置操作引入依赖:Spring Web,Mybatis Framework,MySQL Driver,Lombok在application.properties/yml中添加数据库连接信息:#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#自动驼峰转换mybatis.configuration.map-underscore-to-camel-case=truespring: application: #应用程序名称 name: configuration #数据库连接信息 datasource: url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false username: root password: root#应用程序端口号server: port: 8080mybatis: configuration: map-underscore-to-camel-case: true #自动驼峰转换SQL命名规范:采用下划线分隔单词(如order_detail)Java命名规范:大驼峰/小驼峰2.3 注解2.3.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogMapper { //其他代码}@Mapper注解:允许开发者直接在接口方法上通过注解配置SQL语句,无需编写XML映射文件。适用于简单SQL场景,能显著减少配置量2.初始化数据create table blog (id int primary key auto_increment,name varchar(128),age int);insert into blog values (null,'刘备',30),(null,'关羽',28),(null,'张飞',25);3.创建对应实体类import lombok.Data;@Datapublic class PersonInfo { private Integer id; private String name; private Integer age; public PersonInfo(Integer id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public PersonInfo() { }}2.3.2 CRUDimport com.example.spring_mybatis.model.PersonInfo;import org.apache.ibatis.annotations.*;@Mapperpublic interface BlogMapper { @Select("select * from blog") List<PersonInfo> getPersonInfoAll(); @Insert("insert into blog values (#{id},#{name},#{age})") Integer addPerson(PersonInfo person); @Update("update blog set name = #{name},age = #{age} where id = #{id}") Integer updatePerson(PersonInfo personInfo); @Delete("delete from blog where id = #{id}") Integer deletePerson(Integer id);}按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogMapperTest { private final BlogMapper blogMapper; @Autowired public BlogMapperTest(BlogMapper blogMapper) { this.blogMapper = blogMapper; } @Test void getPersonInfoAll() { List<PersonInfo> personInfoAll = blogMapper.getPersonInfoAll(); log.info("查询成功,personInfoAll:{}",personInfoAll.toString()); //查询成功,personInfoAll:[PersonInfo(id=1, name=刘备, age=30), //PersonInfo(id=2, name=关羽, age=28), //PersonInfo(id=3, name=张飞, age=25)] } @Test void addPerson() { Integer ret = blogMapper.addPerson(new PersonInfo(null, "赵云", 25)); log.info("添加成功,影响行数:{}",ret.toString());//添加成功,影响行数:1 } @Test void updatePerson() { Integer ret = blogMapper.updatePerson(new PersonInfo(1, "刘备", 35)); log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1 } @Test void deletePerson() { Integer ret = blogMapper.deletePerson(4); log.info("删除成功,影响行数:{}",ret.toString());//删除成功,影响行数:1 }}2.3.3 @Param作用:用于在Mapper接口方法中为形式参数指定名称。当方法有多个参数时,通过该注解明确SQL中引用的参数名,避免依赖参数顺序@Mapperpublic interface BlogMapper { //@Param @Update("update blog set name = #{name},age = #{age} where id = #{id}") Integer updatePersonInfo(@Param("id") Integer userId,@Param("name") String userName,@Param("age") Integer userAge);}AI写代码java运行123456@SpringBootTest@Slf4jclass BlogMapperTest { private final BlogMapper blogMapper; @Autowired public BlogMapperTest(BlogMapper blogMapper) { this.blogMapper = blogMapper; } @Test void updatePersonInfo() { Integer ret = blogMapper.updatePersonInfo(1, "刘玄德", 30); log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1 }}2.4 xml2.4.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogXMLMapper { //其他代码}2.配置mybatis的xml文件路径mybatis: mapper-locations: classpath:mybatis/**Mapper.xml #配置mybatis的xml文件路径 #标识位于resources/mybatis路径下任何以Mapper结尾的xml文件为mybatis的配置文件3.在resources/mybatis路径下创建以Mapper结尾的xml文件,并添加如下代码<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:用于指定该XML文件对应的Java接口或类的全限定名--><mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"></mapper>2.4.2 示例在接口中声明方法import com.example.spring_mybatis.model_blog.PersonInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface BlogXMLMapper { List<PersonInfo> getPersonInfoAll();}在对应xml文件中实现接口方法id:是MyBatis映射文件中SQL语句的唯一标识符。需与Mapper接口中的方法名一致,保证映射正确resultType:指定SQL查询结果映射的Java对象类型,需为全限定类名<mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"> <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo"> select * from blog </select></mapper>按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model_blog.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogXMLMapperTest { private final BlogXMLMapper blogXMLMapper; @Autowired public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) { this.blogXMLMapper = blogXMLMapper; } @Test void getPersonInfoAll() { List<PersonInfo> personInfoAll = blogXMLMapper.getPersonInfoAll(); log.info("查询成功,personInfoAll:{}",personInfoAll.toString()); }}运行结果:2.5 动态SQL动态SQL:指在程序运行时根据条件或参数动态生成的SQL语句。与静态SQL相比,动态SQL更具灵活性,适用于需要根据不同条件构建查询的场景。例如,在某些web/app进行账号注册时会出现非必填选项mybatis的注解和xml两种方式都能实现动态SQL,但xml较为方便,所以下文使用xml来实现动态SQL2.5.1 trim标签作用:用于自定义字符串截取规则。包含四个属性:prefix:最终结果添加前缀suffix:最终结果添加后缀prefixOverrides:去除首部指定内容suffixOverrides:去除尾部指定内容2.5.2 if标签作用:用于条件判断,通常在where或set语句中使用。当test表达式的值为true时,包含标签内的SQL片段 <insert id="addPersonInfo"> insert into blog <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="age != null"> age, </if> </trim> values <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> #{id}, </if> <if test="name != null"> #{name}, </if> <if test="age != null"> #{age}, </if> </trim> </insert>2.5.3 where标签作用:替代SQL中的where关键字。当if条件成立时才会加入SQL片段,并自动去除第一个子句的and/or <select id="getPersonInfoByNameAndAge"> select * from blog <where> <if test="name != null"> and name = #{name} </if> <if test="age != null"> and age = #{age} </if> </where> </select>2.5.4 set标签作用:用于update语句。当if条件成立时才会加入SQL片段,并自动去除最后一个子句的逗号、 <update id="updatePersonInfo"> update blog <set> <if test="name != null"> name = #{name}, </if> <if test="age != null"> age = #{age}, </if> </set> <where> and id = #{id} </where> </update>2.5.5 foreach标签作用:用于集合遍历。主要属性:collection:集合参数名item:当前元素变量名open/close:包围符号separator:分隔符 @Test void getPersonInfoById() { ArrayList<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(2); ids.add(3); List<PersonInfo> personInfoById = blogXMLMapper.getPersonInfoById(ids); System.out.println(personInfoById); } <select id="getPersonInfoById" resultType="com.example.spring_mybatis.model_blog.PersonInfo"> select * from blog where id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </select>2.5.6 include标签作用:用于引用SQL片段,通过refid指定要引用的片段id。需配合sql标签使用,实现代码复用 <sql id="collection"> id,name,age </sql> <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo"> select <include refid="collection"> </include> from blog </select>2.6 主键返回主键返回:指在数据库插入操作后,自动获取刚插入记录的主键值。在mybatis中使用注解和xml都能获取到返回的主键1.注解实现@Mapperpublic interface BlogMapper { @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into blog values (#{id},#{name},#{age})") Integer addPerson(PersonInfo person);}@SpringBootTest@Slf4jclass BlogMapperTest { private final BlogMapper blogMapper; @Autowired public BlogMapperTest(BlogMapper blogMapper) { this.blogMapper = blogMapper; } @Test void addPerson() { PersonInfo personInfo = new PersonInfo(null, "黄忠", 60); Integer ret = blogMapper.addPerson(personInfo); log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:6 }}2.通过xml实现@SpringBootTest@Slf4jclass BlogXMLMapperTest { private final BlogXMLMapper blogXMLMapper; @Autowired public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) { this.blogXMLMapper = blogXMLMapper; } @Test void addPersonInfo() { PersonInfo personInfo = new PersonInfo(); personInfo.setAge(40); personInfo.setName("曹操"); Integer ret = blogXMLMapper.addPersonInfo(personInfo); log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:7 }} <insert id="addPersonInfo" useGeneratedKeys="true" keyProperty="id"> insert into blog <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="age != null"> age, </if> </trim> values <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> #{id}, </if> <if test="name != null"> #{name}, </if> <if test="age != null"> #{age}, </if> </trim> </insert>2.7 预编译/即时SQL预编译SQL(Prepared Statements):SQL语句在程序运行前被预先编译并存储在数据库中。执行时只需传递参数,无需重新编译SQL语句安全性高:通过参数化查询避免SQL注入攻击。参数化查询是一种将SQL语句与用户输入数据分离的数据库操作方式,查询语句中使用占位符(如?、@param等)代替直接拼接用户输入,执行时通过预编译机制将参数动态绑定到占位符位置性能优化:编译一次,多次执行,减少数据库开销即时SQL(Dynamic SQL):在程序运行时动态生成并立即编译执行,每次执行都可能涉及完整的SQL解析和编译过程灵活性高:可根据运行时条件动态拼接SQL语句潜在风险:直接拼接用户输入可能导致SQL注入性能开销:每次执行需重新编译#占位符会使用预编译机制,将参数值安全地绑定到SQL语句中,防止SQL注入攻击。MyBatis会将#替换为?,然后通过JDBC的预编译功能设置参数值$占位符直接进行字符串替换,将参数值拼接到SQL语句中,不会进行预编译或转义处理SQL注入攻击:当恶意输入" 'or 1 = '1 "时1.预编译SQL@Select("select * from blog where name= #{name}")List<UserInfo> queryByName(String name)预编译SQL会根据参数的类型判断是否需要加引号,上述name参数是String类型,需要加引号,这就是参数化查询的作用。最终SQL:select * from blog where name = " 'or 1='1 "; # 整个 'or 1='1 会作为name的值2.即时SQL@Select("select * from blog where name= '${name}'")List<UserInfo> queryByName(String name)即时SQL不会判断参数类型从而是否添加引号,所以需要手动加上单引号。最终SQL:select * from blog where name = ''or 1 = '1'; # 因为1=1是恒等式,所以该表的数据会被全部查———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2401_89167985/article/details/152804543
-
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。Freemarker研究1 FreeMarker 研究1.1 FreeMarker介绍1 、freemarker是一个用Java开发的模板引擎 常用的java模板引擎还有哪些?Jsp、Freemarker、Thymeleaf 、Velocity 等。2 、模板+数据模型=输出freemarker并不关心数据的来源,只是根据模板的内容,将数据模型在模板中显示并输出文件(通常为html,也可以生成其它格式的文本文件)1 、数据模型数据模型在java中可以是基本类型也可以List、Map、Pojo等复杂类型。2 、来自官方的例子:(cid:link_0)数据模型: 模板: 输出: 1.2 FreeMarker快速入门freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。需要创建Spring Boot+Freemarker工程用于测试模板。1.2.1 创建测试工程创建一个freemarker 的测试工程专门用于freemarker的功能测试与模板的测试。pom.xml如下<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>framework-parent</artifactId> <groupId>com.lxw</groupId> <version>1.0-SNAPSHOT</version> <!-- <relativePath>../framework-parent/pom.xml</relativePath>--> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>test-freemarker</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>1.2.2 配置文件配置application.yml和 logback-spring.xml,从cms工程拷贝这两个文件,进行更改, logback-spring.xml无需更改,application.yml内容如下:server: port: 8888spring: application: name: test-freemarker freemarker: charset: UTF-8 #suffix: cache: false settings: template_update_delay: 0 #检查模板更新延迟时间,设置为 0 表示立即检查,如果时间大于 0 会有缓存不方便进行模板测试1.2.3 创建模型类在freemarker的测试工程下创建模型类型用于测试package com.yh.pojo;import lombok.Data;import lombok.ToString;import java.util.Date;import java.util.List;@Data@ToStringpublic class Student { private String name;//姓名 private int age;//年龄 private Date birthday;//生日 private Float money;//钱包 private List<Student> friends;//朋友列表 private Student bestFriend;//最好的朋友}1.2.4 创建模板在 src/main/resources下创建templates,此目录为freemarker的默认模板存放目录。在templates下创建模板文件test1.ftl,模板中的${name}最终会被freemarker替换成具体的数据。<!DOCTYPE html><html><head> <meta charset="utf‐8"> <title>Hello World!</title></head><body> Hello ${name}!</body></html>1.2.5 创建controller创建Controller类,向Map中添加name,最后返回模板文件。package com.lxw.test.freemarker.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.client.RestTemplate;import java.util.Map;@RequestMapping("/freemarker")@Controllerpublic class FreemarkerController { @Autowired RestTemplate restTemplate; @RequestMapping("/test1") public String freemarker(Map<String, Object> map){ map.put("name","高級程序员"); //返回模板文件名称 return "test1"; }}1.2.6 创建启动类@SpringBootApplicationpublic class FreemarkerTestApplication { public static void main(String[] args) { SpringApplication.run(FreemarkerTestApplication.class,args); }}1.2.7 测试请求:http://localhost:8088/freemarker/test1屏幕显示:Hello 高級程序员!1.3 FreeMarker基础1.3.1 核心指令1.3.1.1 数据模型Freemarker静态化依赖数据模型和模板,下边定义数据模型:下边方法形参map即为freemarker静态化所需要的数据模型,在map中填充数据:@RequestMapping("/test1") public String freemarker(Map<String, Object> map){ //向数据模型放数据 map.put("name","高級程序员"); Student stu1 = new Student(); stu1.setName("小明"); stu1.setAge(18); stu1.setMoney(1000.86f); stu1.setBirthday(new Date()); Student stu2 = new Student(); stu2.setName("小红"); stu2.setMondy(200.1f); stu2.setAge(19); //stu2.setBirthday(new Date()); List<Student> friends = new ArrayList<>(); friends.add(stu1); stu2.setFriends(friends); stu2.setBestFriend(stu1); List<Student> stus = new ArrayList<>(); stus.add(stu1); stus.add(stu2); //向数据模型放数据 map.put("stus",stus); //准备map数据 HashMap<String,Student> stuMap = new HashMap<>(); stuMap.put("stu1",stu1); stuMap.put("stu2",stu2); //向数据模型放数据 map.put("stu1",stu1); //向数据模型放数据 map.put("stuMap",stuMap); //返回模板文件名称 return "test1"; }1.3.1.2 List指令本节定义freemarker模板,模板中使用freemarker的指令,关于freemarker的指令需要知道:1、注释,即<#‐‐和‐‐>,介于其之间的内容会被freemarker忽略2、插值(Interpolation):即${..}部分,freemarker会用真实的值代替${..}3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内 容。在test1.ftl模板中使用list指令遍历数据模型中的数据:<table> <tr> <td>序号</td> <td>姓名</td> <td>年龄</td> <td>钱包</td> </tr> <#list stus as stu> <tr> <td>${stu_index + 1}</td> <td>${stu.name}</td> <td>${stu.age}</td> <td>${stu.mondy}</td> </tr> </#list> </table>输出:Hello 高級程序员! 序号 姓名 年龄 钱包 1 小明 18 1,000.86 2 小红 19 200.说明: index:得到循环的下标,使用方法是在stu后边加"index",它的值是从 0 开始1.3.1.3 遍历Map数据1 、数据模型使用map指令遍历数据模型中的stuMap。2 、模板输出stu1的学生信息:<br/>姓名:${stuMap['stu1'].name}<br/>输出stu1的学生信息:<br/>姓名:${stuMap.stu1.name}<br/>年龄:${stuMap.stu1.age}<br/>遍历输出两个学生信息:<br/><table> <tr> <td>序号</td> <td>姓名</td> <td>年龄</td> <td>钱包</td> </tr> <#list stuMap?keys as k> <tr> <td>${k_index + 1}</td> <td>${stuMap[k].name}</td> <td>${stuMap[k].age}</td> <td >${stuMap[k].mondy}</td> </tr> </#list></table>3 、输出输出stu1的学生信息:姓名:小明年龄: 18输出stu1的学生信息:姓名:小明年龄: 18遍历输出两个学生信息:序号 姓名 年龄 钱包1 小红 19 2 00.2 小明 18 1 ,000.1.3.1.4 if指令if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。1 、数据模型:使用list指令中测试数据模型。2 、模板:<table> <tr> <td>姓名</td> <td>年龄</td> <td>钱包</td> </tr> <#list stus as stu> <tr> <td <#if stu.name =='小明'>style="background:red;"</#if>>${stu.name}</td> <td>${stu.age}</td> <td >${stu.mondy}</td> </tr> </#list></table>通过阅读上边的代码,实现的功能是:如果姓名为“小明”则背景色显示为红色。3 、输出:通过测试发现 姓名为小明的背景色为红色。1.3.2 其它指令1.3.2.1 运算符1 、算数运算符 FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , %2、逻辑运算符 逻辑运算符有如下几个: 逻辑与:&& 逻辑或:|| 逻辑非:! 逻辑运算符只能作用于布尔值,否则将产生错误3 、比较运算符 表达式中支持的比较运算符有如下几个: 1 =或者==:判断两个值是否相等. 2 !=:判断两个值是否不等. 3 >或者gt:判断左边值是否大于右边值 4 >=或者gte:判断左边值是否大于等于右边值 5 <或者lt:判断左边值是否小于右边值 6 <=或者lte:判断左边值是否小于等于右边值注意: =和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,"x","x ","X"是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>1.3.2.2 空值处理1 、判断某变量是否存在使用 “??” 用法为:variable??,如果该变量存在,返回true,否则返回false例:为防止stus为空报错可以加上判断如下: <#if stus??> <#list stus as stu>...... </#list> </#if>2 、缺失变量默认值使用 “!” 使用!要以指定一个默认值,当变量为空时显示默认值。例: ${name!''}表示如果name为空显示空字符串。如果是嵌套对象则建议使用()括起来。例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name为空默认显示空字符串。1.3.2.3 内建函数内建函数语法格式: 变量+?+函数名称1 、和到某个集合的大小${集合名?size}2 、日期格式化显示年月日: ${today?date}显示时分秒:${today?time} 显示日期+时间:${today?datetime} <br> 自定义格式化: ${today?string("yyyy年MM月")}3 、内建函数cmap.put("point", 102920122);point是数字型,使用${point}会显示这个数字的值,不并每三位使用逗号分隔。如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出${point?c}4 、将json字符串转成对象一个例子:其中用到了 assign标签,assign的作用是定义一个变量。<#assign text="{'bank':'工商银行','account':'10101920201920212'}" /><#assign data=text?eval />开户行:${data.bank} 账号:${data.account}1.3.2.4 完整的模板上边测试的模板内容如下,可自行进行对照测试。<!DOCTYPE html><html><head> <meta charset="utf‐8"> <title>Hello World!</title></head><body>Hello ${name}!<br/><table> <tr> <td>序号</td> <td>姓名</td> <td>年龄</td> <td>钱包</td> </tr> <#list stus as stu> <tr> <td>${stu_index + 1}</td> <td <#if stu.name =='小明'>style="background:red;"</#if>>${stu.name}</td> <td>${stu.age}</td> <td >${stu.mondy}</td> </tr> </#list></table><br/><br/>输出stu1的学生信息:<br/>姓名:${stuMap['stu1'].name}<br/>年龄:${stuMap['stu1'].age}<br/>输出stu1的学生信息:<br/>姓名:${stu1.name}<br/>年龄:${stu1.age}<br/>遍历输出两个学生信息:<br/><table> <tr> <td>序号</td> <td>姓名</td> <td>年龄</td> <td>钱包</td> </tr><#list stuMap?keys as k><tr> <td>${k_index + 1}</td> <td>${stuMap[k].name}</td> <td>${stuMap[k].age}</td> <td >${stuMap[k].mondy}</td></tr></#list></table></br><table> <tr> <td>姓名</td> <td>年龄</td> <td>出生日期</td> <td>钱包</td> <td>最好的朋友</td> <td>朋友个数</td> <td>朋友列表</td> </tr> <#if stus??> <#list stus as stu> <tr> <td>${stu.name!''}</td> <td>${stu.age}</td> <td>${(stu.birthday?date)!''}</td> <td>${stu.mondy}</td> <td>${(stu.bestFriend.name)!''}</td> <td>${(stu.friends?size)!0}</td> <td> <#if stu.friends??> <#list stu.friends as firend> ${firend.name!''}<br/> </#list> </#if> </td> </tr> </#list> </#if></table><br/><#assign text="{'bank':'工商银行','account':'10101920201920212'}" /><#assign data=text?eval />开户行:${data.bank} 账号:${data.account}</body></html>1.3.3 静态化测试在cms中使用freemarker将页面生成html文件,本节测试html文件生成的方法:1 、使用模板文件静态化定义模板文件,使用freemarker静态化程序生成html文件。2 、使用模板字符串静态化定义模板字符串,使用freemarker静态化程序生成html文件。1.3.3.1 使用模板文件静态化在test下创建测试类,并且将main下的resource/templates拷贝到test下,本次测试使用之前我们在main下创建的模板文件。//基于模板生成静态化文件@Testpublic void testGenerateHtml() throws IOException, TemplateException { //创建配置类 Configuration configuration=new Configuration(Configuration.getVersion()); //设置模板路径 String classpath = this.getClass().getResource("/").getPath(); configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates/")); //设置字符集 configuration.setDefaultEncoding("utf‐8"); //加载模板 Template template = configuration.getTemplate("test1.ftl"); //数据模型 Map<String,Object> map = new HashMap<>(); map.put("name","高級程序员"); //静态化 String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map); //静态化内容 System.out.println(content); InputStream inputStream = IOUtils.toInputStream(content); //输出文件 FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/test1.html")); int copy = IOUtils.copy(inputStream, fileOutputStream);}1.3.3.2 使用模板字符串静态化//基于模板字符串生成静态化文件@Testpublic void testGenerateHtmlByString() throws IOException, TemplateException { //创建配置类 Configuration configuration=new Configuration(Configuration.getVersion()); //模板内容,这里测试时使用简单的字符串作为模板 String templateString="" + "<html>\n" + " <head></head>\n" + " <body>\n" + " 名称:${name}\n" + " </body>\n" + "</html>"; //模板加载器 StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); stringTemplateLoader.putTemplate("template",templateString); configuration.setTemplateLoader(stringTemplateLoader); //得到模板 Template template = configuration.getTemplate("template","utf‐8"); //数据模型 Map<String,Object> map = new HashMap<>(); map.put("name","高級程序员"); //静态化 String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map); //静态化内容 System.out.println(content); InputStream inputStream = IOUtils.toInputStream(content); //输出文件 FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/test1.html")); IOUtils.copy(inputStream, fileOutputStream);}
-
在 Java 中,int 和 Integer 是处理整数时最常用的两种类型,但二者在本质、用途、特性上有根本区别,核心差异可从以下维度清晰区分:一、本质区别:基本数据类型 vs 引用数据类型类型归属类别存储方式核心特点int基本数据类型直接存储数值(栈内存)不包含额外方法,仅存原始值Integer引用数据类型(类)存储对象引用(堆内存存对象,栈内存存地址)是 int 的包装类,包含工具方法简单理解:int 是 “裸数值”,Integer 是 “装着数值的盒子”—— 盒子里除了数值,还附带了操作数值的工具。二、核心差异对比1. 默认值不同int 作为基本类型,默认值为 0(如类中定义 private int num;,未赋值时 num 自动为 0);Integer 作为引用类型,默认值为 null(如 private Integer num;,未赋值时 num 为 null)。 ⚠️ 注意:若直接调用 Integer 变量的方法(如 num.toString())且未赋值,会触发 NullPointerException,而 int 不会。2. 适用场景不同int:适用于简单的数值计算、存储基本整数(如循环变量、年龄、分数等),性能更优(无需创建对象,节省内存);Integer:适用于需要对象特性的场景,例如:集合框架(List<Integer>、Map<String, Integer> 等,集合仅支持引用类型);泛型(<T> 类型参数必须是引用类型,不能是 int);需使用工具方法时(如数值转字符串 Integer.toString(num)、字符串转整数 Integer.parseInt("123"))。3. 特性差异:缓存机制Integer 有缓存池机制(JDK 1.5+),默认缓存 -128 ~ 127 范围内的整数对象,重复使用该范围的 Integer 时,不会创建新对象;而 int 无缓存,每次使用都是独立的数值。示例验证:java 运行 // Integer 缓存机制Integer a = 127; // 从缓存池获取对象Integer b = 127; // 复用同一缓存对象System.out.println(a == b); // true(引用地址相同)Integer c = 128; // 超出缓存范围,创建新对象Integer d = 128; // 新对象System.out.println(c == d); // false(引用地址不同)// int 无缓存,直接比较数值int e = 128;int f = 128;System.out.println(e == f); // true(数值相同) 4. 比较方式不同int 比较:用 == 直接比较数值(正确,因为存储的是原始值);Integer 比较:== 比较的是对象引用地址(仅在缓存范围内的整数会相等,超出范围则不等,易出错);正确比较方式:用 equals() 方法(比较数值),或先拆箱为 int 再用 ==。示例:java 运行 Integer x = 100;Integer y = new Integer(100); // 强制创建新对象(不走缓存)System.out.println(x == y); // false(引用地址不同)System.out.println(x.equals(y)); // true(数值相同)System.out.println((int)x == y); // true(拆箱后比较数值) 三、自动拆箱与装箱(JDK 1.5+)为简化 int 和 Integer 的转换,Java 提供自动拆箱(Unboxing) 和 自动装箱(Boxing):自动装箱:int 自动转为 Integer(如 Integer num = 10;,等价于 Integer num = Integer.valueOf(10););自动拆箱:Integer 自动转为 int(如 int val = num;,等价于 int val = num.intValue();)。注意:频繁拆装箱可能影响性能(如循环中大量转换),需避免不必要的操作。四、总结:如何选择?若仅需存储 / 计算基本整数,优先用 int(性能好、无空指针风险);若需集合、泛型、工具方法,或可能存储 null(如数据库字段允许为空),用 Integer;Integer 比较时务必用 equals(),避免用 == 踩缓存范围的坑。
-
在Java中,正则表达式(Regular Expression)通过 java.util.regex 包实现,主要用于字符串匹配、分割、查找和替换。以下是详细的使用指南和示例:1. 核心类介绍Pattern:编译正则表达式,生成匹配模式。Matcher:执行匹配操作,提供查找、替换等方法。String 类方法:如 matches()、replaceAll() 等内置正则支持。2. 基本用法(1) 字符串匹配import java.util.regex.Pattern; import java.util.regex.Matcher; public class RegexExample { public static void main(String[] args) { String input = "Java is fun"; String regex = "Java"; // 方法1:String.matches()(完全匹配) boolean isMatch = input.matches(regex); // false(需完全匹配"Java is fun") System.out.println(isMatch); // 方法2:Pattern + Matcher(部分匹配) Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); boolean found = matcher.find(); // true(找到"Java") System.out.println(found); } } (2) 提取匹配内容String text = "Email: user@example.com, Backup: backup@test.org"; Pattern emailPattern = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); Matcher emailMatcher = emailPattern.matcher(text); while (emailMatcher.find()) { System.out.println("Found email: " + emailMatcher.group()); } // 输出: // Found email: user@example.com // Found email: backup@test.org (3) 字符串分割String csv = "apple,banana,orange"; String[] fruits = csv.split(","); // 按逗号分割 for (String fruit : fruits) { System.out.println(fruit); } (4) 字符串替换String phone = "123-456-7890"; String formatted = phone.replaceAll("(\\d{3})-(\\d{3})-(\\d{4})", "($1) $2-$3"); System.out.println(formatted); // 输出:(123) 456-7890 3. 常用正则语法符号含义示例.任意字符(除换行符)a.c → “abc”, “a1c”\\d数字\\d{3} → “123”\\w字母、数字、下划线\\w+ → “Java_123”+1次或多次go+l → “gol”, “goool”*0次或多次ab*c → “ac”, “abbc”?0次或1次colou?r → “color”{n,m}n到m次\\d{2,4} → “12”, “123”[abc]a、b或c[a-z]+ → “hello”^字符串开头^Hello → “Hello…”$字符串结尾world$ → “…world”``或\\s空白字符(空格、制表符等)\\s+ → " \t\n"\\b单词边界\\bcat\\b → “cat”4. 高级用法(1) 分组捕获String date = "2023-10-05"; Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})"); Matcher dateMatcher = datePattern.matcher(date); if (dateMatcher.matches()) { String year = dateMatcher.group(1); // "2023" String month = dateMatcher.group(2); // "10" String day = dateMatcher.group(3); // "05" System.out.println(year + "-" + month + "-" + day); } (2) 非贪婪匹配String html = "<div>Hello</div><div>World</div>"; Pattern greedyPattern = Pattern.compile("<div>.*</div>"); // 贪婪匹配 Pattern lazyPattern = Pattern.compile("<div>.*?</div>"); // 非贪婪匹配 Matcher greedyMatcher = greedyPattern.matcher(html); greedyMatcher.find(); // 匹配整个"<div>Hello</div><div>World</div>" Matcher lazyMatcher = lazyPattern.matcher(html); lazyMatcher.find(); // 仅匹配第一个"<div>Hello</div>" (3) 预定义字符类String text = "Price: $19.99, Discount: 10%"; Pattern numberPattern = Pattern.compile("\\$\\d+\\.\\d{2}"); // 匹配价格 Matcher numberMatcher = numberPattern.matcher(text); numberMatcher.find(); // 匹配"$19.99" 5. 性能优化预编译 Pattern:重复使用时,避免每次创建 Pattern 对象。private static final Pattern EMAIL_PATTERN = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); public boolean isValidEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); } 避免过度回溯:复杂正则可能导致性能问题(如 (a+)+ 匹配 "aaaaaaaaaaaaaX")。6. 常见正则示例需求正则表达式验证邮箱^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$提取URL参数[?&](\\w+)=([^&#]+)匹配手机号(中国)^1[3-9]\\d{9}$去除多余空格\\s+ → 替换为 " "验证密码强度(大小写+数字)^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$7. 工具推荐在线测试工具:Regex101、RegExrIDE插件:IntelliJ IDEA的正则表达式测试器(Ctrl+Alt+F7)。总结简单匹配:直接使用 String.matches() 或 String.split()。复杂操作:使用 Pattern + Matcher 组合。性能关键场景:预编译 Pattern 并复用。调试技巧:通过 Matcher.group() 和在线工具逐步验证正则逻辑。通过合理使用正则表达式,可以高效处理文本验证、解析和转换任务。
-
在Java中,接口(Interface)是一种核心的抽象机制,它通过定义契约(方法签名和常量)来规范类的行为。接口的使用带来了多重好处,尤其在大型项目、团队协作和系统扩展中具有关键作用。以下是Java中使用接口的主要优势及具体场景说明:1. 解耦与依赖倒置(DIP)核心思想:接口将**调用方(客户端)与实现方(具体类)**解耦,使两者仅依赖抽象而非具体实现。示例:// 定义接口 interface PaymentService { void pay(double amount); } // 具体实现1 class CreditCardPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via Credit Card"); } } // 具体实现2 class PayPalPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via PayPal"); } } // 客户端代码(依赖接口而非具体类) public class PaymentProcessor { private PaymentService paymentService; public PaymentProcessor(PaymentService service) { this.paymentService = service; } public void processPayment(double amount) { paymentService.pay(amount); // 调用接口方法 } } 优势:客户端只需知道接口方法,无需关心具体支付方式(如信用卡、PayPal)的实现细节。切换支付方式时,只需修改PaymentProcessor的构造函数参数,无需改动内部逻辑。2. 多态与灵活扩展核心思想:一个类可实现多个接口,或同一接口可被多个类实现,实现运行时动态绑定。示例:interface Loggable { void log(String message); } class FileLogger implements Loggable { public void log(String message) { System.out.println("Logging to file: " + message); } } class DatabaseLogger implements Loggable { public void log(String message) { System.out.println("Logging to database: " + message); } } // 根据配置动态选择日志方式 public class App { public static void main(String[] args) { Loggable logger; if (args[0].equals("file")) { logger = new FileLogger(); } else { logger = new DatabaseLogger(); } logger.log("System started"); // 多态调用 } } 优势:新增日志方式(如网络日志)时,只需新增一个实现类,无需修改现有代码。符合开闭原则(OCP):对扩展开放,对修改关闭。3. 定义契约与强制规范核心思想:接口明确规定了类必须实现的方法,确保对象行为的一致性。示例:interface Comparable<T> { int compareTo(T other); } class Person implements Comparable<Person> { private String name; private int age; public int compareTo(Person other) { return this.age - other.age; // 强制实现比较逻辑 } } 优势:任何实现Comparable的类必须提供compareTo方法,否则编译失败。框架(如Java集合类)可依赖接口统一处理对象(如排序、查找)。4. 模拟多继承(Java单继承的补充)核心思想:Java类只能单继承,但可通过实现多个接口间接实现多继承的效果。示例:interface Flyable { void fly(); } interface Swimmable { void swim(); } class Duck implements Flyable, Swimmable { public void fly() { System.out.println("Duck is flying"); } public void swim() { System.out.println("Duck is swimming"); } } 优势:避免类继承导致的钻石问题(Diamond Problem)。灵活组合多个行为(如飞行+游泳)。5. 接口与默认方法(Java 8+)核心思想:接口可通过default关键字提供默认实现,减少重复代码。示例:interface Listener { void onEvent(); // 抽象方法 default void logEvent() { // 默认方法 System.out.println("Event occurred"); } } class ButtonListener implements Listener { public void onEvent() { System.out.println("Button clicked"); } // 无需实现logEvent(),可直接使用默认实现 } 优势:向旧接口添加方法时,无需强制所有实现类修改代码。共享通用逻辑(如日志、监控)。6. 接口与函数式编程(Java 8+)核心思想:接口(如Runnable、Function)是函数式编程的基础,支持Lambda表达式。示例:// 传统匿名类 Runnable task1 = new Runnable() { public void run() { System.out.println("Task 1"); } }; // 使用Lambda(接口简化) Runnable task2 = () -> System.out.println("Task 2"); 优势:代码更简洁,适合事件处理、并发编程等场景。结合Stream API实现声明式数据处理。7. 接口与测试驱动开发(TDD)核心思想:接口便于编写测试替身(Mock/Stub),隔离依赖。示例:// 真实依赖 interface UserRepository { User findById(int id); } // 测试时使用Mock class MockUserRepository implements UserRepository { public User findById(int id) { return new User(1, "Test User"); // 返回固定数据 } } // 测试类 public class UserServiceTest { @Test public void testGetUser() { UserRepository mockRepo = new MockUserRepository(); UserService service = new UserService(mockRepo); User user = service.getUser(1); assertEquals("Test User", user.getName()); } } 优势:无需启动数据库或外部服务,加快测试速度。明确测试边界,聚焦被测逻辑。总结:何时使用接口?场景接口的优势需要解耦模块客户端依赖抽象,实现可替换预期未来行为会扩展新增实现类不影响现有代码定义框架或库的规范强制用户实现特定方法(如Servlet、JPA Entity)实现多行为组合类通过接口组合飞行、游泳等能力需要函数式编程支持使用Lambda表达式简化代码(如Predicate、Consumer)编写可测试的代码通过Mock隔离依赖,提高测试覆盖率最佳实践:优先使用接口定义行为,而非抽象类(除非需要共享状态或复杂初始化逻辑)。接口命名应体现行为(如Runnable、Serializable),而非具体类(避免UserInterface)。结合设计模式(如策略模式、工厂模式)充分发挥接口的灵活性。
-
一、Java依赖管理的核心挑战:从“版本地狱”到“依赖爆炸”1.1 依赖管理的三大痛点版本冲突:项目中多个依赖库引用同一组件的不同版本(如Log4j 1.2.17 vs Log4j 1.2.18)。后果:运行时异常(如NoSuchMethodError)、日志框架绑定冲突。依赖膨胀:项目依赖树层级过深,导致构建时间延长、JAR包臃肿。案例:某电商系统依赖树包含12,000+ JAR包,构建耗时45分钟。隐性依赖传递:依赖库的间接依赖未被显式声明,导致环境一致性问题。1.2 Maven vs Gradle:谁更适合依赖管理维度MavenGradle依赖声明XML配置(pom.xml)DSL配置(build.gradle)版本冲突解决路径优先(最近依赖优先)灵活策略(resolutionStrategy)构建速度依赖缓存慢支持增量构建、并行任务学习成本低(XML语法)高(Groovy/Kotlin DSL)企业适配传统项目主流微服务/云原生项目主流二、Java依赖管理的5大黄金法则2.1 法则一:统一依赖版本(BOM管理)核心思想:通过Bill of Materials(BOM)统一依赖版本,避免版本碎片化。Maven实践:123456789101112<!-- 定义BOM文件 --><dependencyManagement> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>common-bom</artifactId> <version>1.0.0</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies></dependencyManagement>Gradle实践:12345// 使用平台依赖(Platform)dependencies { implementation platform('com.example:common-bom:1.0.0') implementation 'org.springframework.boot:spring-boot-starter-web'}2.2 法则二:依赖排除与强制版本Maven排除依赖:1234567891011<dependency> <groupId>com.example</groupId> <artifactId>example-dependency</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <groupId>org.conflicting</groupId> <artifactId>conflicting-library</artifactId> </exclusion> </exclusions></dependency>Gradle强制版本:12345configurations.all { resolutionStrategy { force 'org.springframework:spring-core:5.3.20' }}2.3 法则三:依赖声明顺序的艺术推荐顺序:本模块子模块依赖(如example-dao、example-service)通用基础组件(Spring、Jackson、Log4j)公司内部框架(RPC、Redis客户端)特定业务依赖测试依赖(JUnit、Mockito)示例:123456789101112131415161718192021<dependencies> <!-- 子模块依赖 --> <dependency> <groupId>com.example</groupId> <artifactId>example-dao</artifactId> <version>1.0.0</version> </dependency> <!-- 通用组件 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.20</version> </dependency> <!-- 测试依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency></dependencies>2.4 法则四:依赖树分析与可视化Maven命令:1mvn dependency:tree > dependency-tree.txtGradle命令:1gradle dependencies --configuration runtimeClasspath工具推荐:Maven Dependency Plugin:分析冗余依赖。Gradle Dependency Report:生成可视化依赖图。2.5 法则五:依赖注入与模块化设计Spring依赖注入示例:123456789@Componentpublic class MyService { private final MyDependency dependency; @Autowired public MyService(MyDependency dependency) { this.dependency = dependency; }}模块化设计:将公共依赖定义在父POM中,减少重复声明。使用<optional>标记可选依赖,避免传递性污染。三、3个实战案例:从崩溃到高效3.1 案例一:某金融系统依赖冲突修复问题:项目依赖Spring Boot 2.5.5和Hadoop 3.3.0,导致Guava版本冲突(Spring Boot依赖Guava 26.0,Hadoop依赖Guava 31.0)。解决方案:123456<!-- 强制使用Guava 31.0 --><dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version></dependency>效果:构建时间从30分钟降至6秒,运行时异常消失。3.2 案例二:某电商系统的依赖优化问题:依赖树包含12,000+ JAR包,构建耗时45分钟。解决方案:使用mvn dependency:analyze分析冗余依赖。排除无用依赖(如jackson-databind的多余模块)。启用Gradle的--parallel和--build-cache。效果:依赖数量减少60%,构建时间降至12分钟。3.3 案例三:微服务项目的BOM统一管理问题:100+微服务项目依赖版本不一致(如Spring Boot 2.4.0 vs 2.5.5)。解决方案:创建公司级BOM文件company-bom:1234567891011<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.5</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies></dependencyManagement>所有微服务继承company-bom。效果:版本一致性提升100%,升级成本降低90%。四、10个避坑指南:Java依赖管理的“致命陷阱”版本冲突陷阱:使用mvn dependency:tree或gradle dependencies定期检查依赖树。隐性依赖传递陷阱:标记非必需依赖为<optional>true</optional>。依赖膨胀陷阱:使用mvn dependency:purge-local-repository清理本地仓库冗余文件。测试依赖污染生产环境:确保测试依赖(<scope>test</scope>)不被意外打包到生产JAR中。Maven默认仓库陷阱:自定义仓库优先级,避免拉取恶意依赖:1234567<repositories> <repository> <id>company-nexus</id> <url>https://nexus.company.com/repository/maven-public/</url> <releases><enabled>true</enabled></releases> </repository></repositories>Gradle插件冲突陷阱:使用gradle properties显式指定插件版本。多模块项目依赖顺序陷阱:在父POM中定义子模块依赖顺序,避免构建失败。依赖范围混淆陷阱:区分compileOnly、runtimeOnly、provided等依赖范围。依赖版本“最新”陷阱:避免使用LATEST或RELEASE版本,固定版本号以确保稳定性。CI/CD环境依赖一致性陷阱:在CI/CD流水线中强制拉取依赖,避免本地缓存差异。五、未来趋势:AI驱动的依赖管理革命5.1 AI辅助依赖分析GitHub Copilot生成依赖配置:12345// Copilot生成的Gradle依赖声明dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'}5.2 云原生依赖管理Nexus Repository Manager 3.0:支持实时依赖分析、安全漏洞扫描。Azure Artifactory集成:自动同步Maven Central依赖,减少网络延迟。5.3 量子计算与依赖优化量子算法解决依赖冲突:理论上可通过量子叠加态同时计算多个依赖版本的兼容性。六、你的问题答案在这里!问题1:Java依赖管理真的能彻底解决版本冲突吗?答案:通过BOM统一管理、强制版本、依赖排除等策略,可大幅减少冲突,但无法完全避免。问题2:Maven和Gradle哪个更适合大型项目?答案:Gradle更适合复杂项目(支持增量构建、DSL灵活),Maven更适合传统项目(学习成本低)。问题3:如何快速定位依赖冲突?答案:使用mvn dependency:tree或gradle dependencies,结合exclude排除冲突依赖。七、结语:依赖管理的“黄金法则”“没有银弹,只有适合的策略!”Maven:适合传统项目,稳定性强。Gradle:适合复杂项目,灵活性高。未来方向:AI+云原生驱动的“全自动依赖管理”。
-
odejs我想很多人在使用,已经得到广泛运用。但今天介绍一款比node.js高阶的一个新组件Bun,它在HTTP服务器性能、文件系统操作、启动时间、包安装时间性能上高于node.js。什么是bun,Bun的设计理念是开箱即用,减少配置和依赖,让开发者可以更专注于编写代码。Bun是一个全新的JavaScript运行时和工具链,它的核心目标是替代Node.js,提供更快的性能、更简洁的API和更好的开发体验。Bun使用JavaScriptCore引擎(也是Safari浏览器使用的引擎),V8引擎是Node.js使用的引擎,这是其性能优势的主要来源之一。Bun不仅是一个运行时,它还集成了包管理器、打包工具、测试运行器等功能,目标成为一站式的JavaScript开发平台。我这里重点对这两位前端的主角在性能、内置功能、环境、兼容性、nodejs项目迁移、bun的适用场景进行对比总结。性能优势启动速度更快Bun的底层做了大量的优化,启动速度比Node.js快10-20倍。这主要是因为Bun使用了JavaScriptCore引擎,特别是在微服务和serverless环境中,由于快速启动尤其重要,bun就可以明显缩短冷启动的时间了。// 启动时间对比// Node.js: ~50ms vs Bun: ~5msAI运行代码javascript包管理器更快bun有自己内置的包管理器,它和npm、yarn比较,通过bun install速度要快5-10倍。因为它使用了Zig来编写,它依赖解析算法很高效、而且还使用了高效的缓存策略,可直接与文件系统交互,并行下载安装依赖。# npm安装: ~15秒 vs bun安装: ~2秒AI运行代码bash运行时性能更好bun对比常见的操作也比node.js要快。比如文件系统的操作快2-5倍,http请求速度快2倍,json解析快2倍,流处理场景约快4倍。内置功能原生TypeScript支持它内置了TypeScript支持,无需编译可直接运行ts文件,极大的简化了开发流程。// Node.js需要: tsc user.ts && node user.js// Bun只需: bun user.tsAI运行代码typescript内置测试框架内置了测试框架,语法和Jest相似,但速度应该要快3-5倍(消除了安装和配置单独测试库的需要)。// 测试示例import { expect, test } from "bun:test";test("加法运算", () => { expect(2 + 2).toBe(4);});AI运行代码javascript内置打包工具打包包含了一个高性能的javascript打包器,则可以替代掉webpack、Rollup或esbuild等工具,并支持代码分割、压缩等。// 打包示例await Bun.build({ entrypoints: ['./src/index.js'], outdir: './dist', minify: true});AI运行代码javascript文件操作更简单文件操作相关的API更为友好、简洁和直观,极大简化了常见的文件操作任务。// Bun文件操作const text = await Bun.file("data.txt").text();const config = await Bun.file("config.json").json();await Bun.write("output.txt", "Hello Bun!");AI运行代码javascript我们对比Node.js的方式:// Node.js文件操作const text = await fs.readFile("data.txt", "utf8");const json = JSON.parse(await fs.readFile("config.json", "utf8"));await fs.writeFile("output.txt", "Hello Node!");AI运行代码javascriptHTTP服务器高性能的HTTP服务器API,它比Node.js的http模块或Express框架更简单,同时性能更好,更为标准化Web API(如URL、Response)。// Bun HTTP服务器Bun.serve({ port: 3000, fetch(req) { const url = new URL(req.url); if (url.pathname === "/") { return new Response("欢迎访问首页"); } return new Response("404 Not Found", { status: 404 }); },});AI运行代码javascript对比Node.js + Express的实现:// Node.js + Expressconst app = express();app.get('/', (req, res) => { res.send('欢迎访问首页');});app.listen(3000);AI运行代码javascript环境变量和配置简化了环境变量的管理,Bun自动加载项目根目录中的.env文件,无需安装dotenv等第三方库。// .env文件DATABASE_URL=postgresql://localhost:5432/mydb// 在代码中直接使用console.log(process.env.DATABASE_URL);AI运行代码javascript脚本工具Bun很适合编写高性能的脚本工具,特别是需要处理大量文件或数据的场景。// 批量处理文件示例const files = await Array.fromAsync(new Bun.Glob("**/*.md").scan());for (const filePath of files) { const content = await Bun.file(filePath).text(); await Bun.write(filePath, content.replace(/TODO/g, "待办事项"));}AI运行代码javascriptAPI服务器Bun的高性能HTTP服务器和内置SQLite支持使其成为构建API服务的理想选择。// SQLite集成示例import { Database } from 'bun:sqlite';const db = new Database('data.sqlite');db.exec(`CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY, title TEXT)`);// API服务器Bun.serve({ port: 3000, async fetch(req) { if (req.url.endsWith('/api/todos')) { const todos = db.prepare('SELECT * FROM todos').all(); return Response.json(todos); } return new Response('Not Found', { status: 404 }); }});AI运行代码javascript兼容性Bun设计为与Node.js高度兼容,可以运行大多数Node.js代码和npm包。但仍有一些区别和限制:兼容的功能兼容绝大多数场景,比如CommonJS和ES模块系统、核心模块(fs, path, http等)、npm生态系统中的大多数包、process全局对象、Buffer API、EventEmitter、大多数Node.js标准库API。不完全兼容的功能某些原生模块可能需要重新编译一些依赖Node.js内部API的库可能不工作某些流API的实现有差异某些特定于Node.js的功能(如cluster模块)可能有不同行为从Node.js迁移到Bun如果你正在考虑从Node.js迁移到Bun,以下是一些建议和注意事项:迁移步骤安装Bun:curl -fsSL https://bun.sh/install | bash测试兼容性:在不修改代码的情况下,尝试用Bun运行你的应用替换包管理器:将npm/yarn命令替换为bun命令利用Bun特性:逐步采用Bun特有的API和功能更新测试:考虑迁移到Bun的内置测试框架何时选择Bun适合Bun的场景开发环境和工具链:利用Bun的快速启动时间和集成工具API和微服务:利用Bun的高性能HTTP服务器脚本和自动化:利用Bun的快速启动时间和文件操作性能,做些零活儿效率特别高新项目:从头开始的项目可以充分利用Bun的现代API和性能,从一开始就发挥它的全部优势性能关键应用:需要最大化性能的应用,需要速度和性能的场合可能不适合Bun的场景大型企业应用:需要稳定性、成熟性较高的关键业务依赖特定Node.js功能:不完全支持的Node.js特性的应用,而Bun不完全支持需要广泛部署选项:需要跨平台,在各种环境中部署的应用依赖大量原生模块:使用许多需要重新编译的C++扩展的应用Bun如同一股清流,为JavaScript运行时领域带来了新的活力。它以现代化的设计和惊人的性能,让开发体验焕然一新。虽然它还在成长的路上,不能立刻满足所有生产需求,但它的变化和创新精神,已经让它成为技术圈的新星。如果你要开启一个新项目,尤其是追求极致性能和开发体验的,Bun绝对值得一试,让我们的编程之路更加轻松愉快。如果本文能给你提供启发和帮助,还请留下你的一健三连(点赞、转发、评论),给我一些鼓励,谢谢。————————————————原文链接:https://blog.csdn.net/m0_57874805/article/details/150647891
-
Java霸主未逝:不可撼动的生态与新特性的革命潜力引言:在编程语言的巨变时代重新审视Java在技术飞速演进的时代,编程语言的世界仿佛一片汹涌的海洋,每天都有新的语言和框架涌现,声称要颠覆现有秩序。从Python在数据科学和人工智能领域的崛起,到Go语言在并发处理和高性能网络服务中的优异表现,再到Rust在系统编程和安全关键型应用中的强势进攻,似乎每一种语言都在挑战Java这个长期占据TIOBE指数前三的"老牌王者"。不少初学者和技术爱好者开始质疑:在这个充满变数的时代,Java是否已经失去了往日的光彩?它那长达二十多年的霸主地位是否正在被动摇?更重要的是,对于今天的开发者来说,投入时间学习Java还是一项值得的投资吗?事实上,尽管新语言层出不穷,Java依然在全球企业级应用开发中占据着绝对主导地位。根据2023年的最新数据,全球仍有超过90%的企业使用Java作为主要开发语言,包括金融、电信、电商等关键行业。更有趣的是,Java并没有像某些预言中那样逐渐老去,反而通过持续的创新和生态扩展,不断巩固和扩大着自己的领土。从Android移动开发到大数据处理框架,从微服务架构到云原生应用,Java的身影无处不在。甚至在新兴的人工智能和物联网领域,Java也通过不断完善的特性和性能优化,找到了自己的立足点。本文将从多个维度深入分析Java的现状和未来,客观评估其核心地位是否真的遭到其他语言的撼动,详细探讨Java生态系统中那些难以被替代的核心优势,并重点分析Project Loom的虚拟线程、Project Panama的原生镜像等新特性如何为Java注入新的活力。无论你是Java的忠实拥护者,还是对Java未来持怀疑态度的观察者,抑或是正在选择技术方向的初学者,这篇文章都将为你提供一个全面而深入的视角,帮助你在技术的浪潮中做出更明智的决策。第一章 Java的现状:数字背后的真相1.1 市场占有率与行业渗透Java自1995年问世以来,已经走过了近30个年头。在技术领域,这几乎是一个永恒的时间跨度,足以让大多数技术从诞生、繁荣走向衰落。然而Java却打破了这个规律,依然保持着惊人的活力。根据TIOBE编程社区指数的最新统计,Java在2023年依然稳居前三甲,与Python和C语言共同占据着主导地位。更令人惊讶的是,这一排名在过去的20多年间几乎没有发生过实质性变化。深入行业内部,Java的统治地位更加明显。在财富500强企业中,有超过90%的公司将Java作为主要的企业级应用开发语言。金融行业几乎完全建立在Java生态系统之上——全球最大的投资银行、商业银行和保险公司都依赖Java构建其核心交易系统、风险管理系统和客户服务平台。电信行业同样如此,从基站控制器到计费系统,Java无处不在。甚至在传统的制造业和零售业,Java也扮演着关键角色,支撑着供应链管理、库存控制和电子商务平台。Android平台的兴起进一步扩展了Java的疆域。虽然近年来Kotlin在Android开发中获得了官方支持并逐渐流行,但事实是绝大多数现有的Android应用仍然基于Java构建,而且新的Java代码仍在不断被添加到这些应用中。据统计,Google Play商店中超过70%的应用主要使用Java开发,这个数字在企业级应用中甚至更高。1.2 就业市场与开发者社区从就业市场的角度观察,Java开发者的需求持续旺盛。根据Indeed和LinkedIn等招聘平台的数据,Java开发岗位的数量长期位居所有编程语言中的前三位,且平均薪资水平高于许多其他语言的开发者。更重要的是,Java开发者的就业领域极为广泛,从初创公司到科技巨头,从互联网行业到传统制造业,都需要Java人才。Java开发者社区的规模和活跃度也是其生命力的重要指标。Stack Overflow的年度开发者调查显示,Java一直是使用最广泛的语言之一,拥有数百万活跃开发者。GitHub上的Java项目数量超过100万,且持续增长。这种规模的社区意味着任何Java开发者都能轻松找到学习资源、开源库和技术支持,极大降低了学习和开发成本。JVM(Java虚拟机)语言家族的出现进一步丰富了Java生态系统。虽然Scala、Kotlin和Groovy等语言在某些特性上超越了Java本身,但它们都运行在JVM之上,共享Java庞大的库和工具生态系统。这种现象实际上强化而非削弱了Java的地位,因为它使JVM成为了更多开发者的选择,即使他们不直接使用Java语言。第二章 Java生态系统的不可替代性2.1 JVM:工程学的奇迹Java虚拟机构成了Java生态系统的基石,也是Java最难以被替代的核心优势。JVM是一个工程学上的奇迹,它通过"一次编写,到处运行"的理念,解决了跨平台兼容性这一长期困扰软件开发者的难题。经过二十多年的持续优化,现代JVM已经成为一个极其复杂和精密的运行环境,提供了许多独特而强大的特性。即时编译技术是JVM最引人注目的特性之一。HotSpot JVM通过解释执行和即时编译的结合,实现了近乎原生代码的性能,同时又保持了跨平台优势。它采用自适应优化技术,监控代码执行情况,识别热点代码并将其编译为高度优化的机器码。这种运行时常量优化甚至比静态编译的语言更具优势,因为JVM可以根据实际运行情况做出更智能的优化决策。垃圾回收机制是JVM的另一大亮点。从最初的串行收集器到现在的G1、ZGC和Shenandoah等先进收集器,Java的垃圾回收技术已经发展到令人惊叹的水平。现代垃圾收集器可以实现毫秒级的暂停时间,即使处理数TB规模的堆内存也能保持稳定性能。这对于需要高可用性和低延迟的企业级应用至关重要。JVM的监控和诊断能力同样无与伦比。Java管理扩展提供了丰富的监控接口,VisualVM、JConsole等工具可以深入洞察应用运行状态。Java飞行记录器和JDK任务控制系统提供了生产环境下的低开销性能分析能力。这些工具和特性使得Java应用在复杂企业环境中的调试和优化变得更加可行。2.2 企业级框架与库的成熟度Java拥有世界上最成熟和完善的企业级开发生态系统。Spring框架已经成为事实上的Java企业开发标准,提供了从依赖注入到Web开发,从数据访问到安全认证的全方位解决方案。Spring Boot进一步简化了Java应用的初始化和部署过程,使开发者能够快速创建独立运行的生产级应用。Java持久化API和Hibernate框架解决了对象-关系映射这一复杂问题,大大简化了数据库操作。Jakarta EE(前身为Java EE)提供了一套标准化的企业级API,得到了所有主要应用服务器厂商的支持。这些经过时间检验的框架和库为企业级应用开发提供了可靠的基础。微服务架构的兴起进一步证明了Java生态系统的适应性。Spring Cloud、Micronaut和Quarkus等框架为构建云原生Java应用提供了完整解决方案,支持服务发现、配置管理、熔断器等微服务模式。Netflix、Twitter和Amazon等大型互联网公司都使用Java构建其微服务架构,处理每天数十亿次的请求。2.3 安全性与企业支持在企业环境中,安全性往往是首要考虑因素。Java拥有经过严格测试和安全审计的安全模型,提供了细粒度的访问控制机制。Java安全架构包括字节码验证器、安全管理器和访问控制器等组件,构成了一个多层次的安全防御体系。Java长期支持版本为企业提供了稳定性保证。Oracle和OpenJDK社区都提供长期支持版本,保证关键安全更新和错误修复能够持续多年。这种可预测性和稳定性对于需要长期运行的企业应用至关重要。大型科技公司对Java的持续投入也确保了其未来发展。Oracle、IBM、Red Hat、Amazon和Microsoft等公司都雇佣了大量Java核心开发者,参与OpenJDK项目的开发。这种多厂商支持的模式避免了单一公司控制带来的风险,为Java的未来发展提供了坚实保障。第三章 新特性的革命性潜力3.1 Project Loom与虚拟线程Java最大的传统挑战之一是并发编程的复杂性。虽然Java很早就提供了强大的线程模型和并发工具包,但基于操作系统线程的实现限制了其扩展性。每个Java线程都对应一个操作系统线程,这意味着创建数千个线程就会消耗大量系统资源,导致性能下降。Project Loom旨在彻底改变这一现状,引入轻量级的虚拟线程。虚拟线程是由JVM管理的轻量级线程,与操作系统线程解耦。一个操作系统线程可以运行数千个虚拟线程,极大地提高了并发规模和效率。虚拟线程的使用方式与传统线程几乎完全相同,大大降低了学习成本。虚拟线程的革命性在于它使阻塞式编程模型重新变得可行。开发者可以编写简单直观的阻塞代码,而无需担心性能问题。JVM会在虚拟线程遇到阻塞操作时自动将其挂起,切换到其他虚拟线程,从而高效利用硬件资源。这种模型既保持了简单性,又获得了高性能。初步基准测试显示,虚拟线程能够轻松支持百万级别的并发连接,而内存占用仅为传统线程模型的几分之一。这对于需要处理大量并发请求的Web服务器、微服务和反应式应用具有重大意义。虚拟线程有可能使Java在高并发领域重新获得领先地位,甚至超越Go和Erlang等以并发能力著称的语言。3.2 GraalVM与原生镜像Java传统上因启动时间较长和内存占用较高而受到批评,这在容器化和无服务器计算时代显得尤为突出。GraalVM和原生镜像技术正是为了解决这些问题而诞生的。GraalVM是一个高性能的JDK发行版,采用了先进的即时编译技术,能够显著提高应用程序性能。更引人注目的是它的原生镜像功能,允许将Java应用程序提前编译为本地可执行文件。这些原生镜像具有极快的启动速度(通常只需几毫秒)和较低的内存占用,同时仍然保持了Java的类型安全和内存安全特性。Spring Boot、Micronaut和Quarkus等框架已经深度集成了原生镜像支持,使开发者能够轻松构建云原生Java应用。AWS Lambda等无服务器平台也加强了对Java原生镜像的支持,使Java成为函数即服务场景中的可行选择。原生镜像技术不仅解决了Java在云原生环境中的痛点,还开辟了新的应用领域。现在Java可以用于命令行工具、嵌入式系统和边缘计算场景,这些传统上由C++和Go主导的领域。这种扩展能力进一步巩固了Java的生态系统优势。3.3 Project Panama与外部函数接口Java通过JNI调用本地代码的机制一直以复杂和性能低下而闻名。Project Panama旨在革新Java与本地代码的交互方式,引入更简单、更高效的外部函数接口。新的外部函数API提供了类型安全的方式调用本地库,避免了JNI的复杂性和开销。Memory API提供了安全高效的内存访问方法,减少了内存泄漏和错误的风险。这些改进使Java能够更好地与本地库交互,同时保持Java的安全性和可移植性。Project Panama的重要意义在于它使Java能够更有效地利用硬件特性和专用库。现在Java程序可以轻松调用CUDA库进行GPU加速,或者使用TensorFlow和PyTorch进行机器学习推理。这为Java在人工智能和高性能计算领域的发展打开了新的大门。3.4 其他语言特性改进Java的持续更新带来了许多其他重要特性。记录类简化了不可变数据载体的定义,模式匹配简化了条件逻辑,密封类提供了更精确的类层次控制。这些特性使Java代码更加简洁、表达力更强,同时保持了类型安全性。Vector API引入了对向量计算的支持,允许开发者编写可移植的高性能向量化代码。这为科学计算、机器学习和多媒体处理等领域的应用提供了重要支持。这些语言改进表明Java并没有停滞不前,而是在保持向后兼容性的同时,不断吸收现代编程语言的最佳特性。Java正在变得更加简洁、表达力更强,同时保持了其核心优势和稳定性。第四章 Java面临的挑战与应对4.1 新兴语言的竞争尽管Java依然强大,但它确实面临着来自新兴语言的激烈竞争。Python在数据科学和机器学习领域的统治地位无可争议,Go语言在云原生基础设施和命令行工具中越来越受欢迎,Rust凭借其内存安全性和高性能在系统编程领域崭露头角。每种语言都有其特定的优势领域:Python拥有丰富的数据科学库和简洁的语法,Go提供了简单的并发模型和快速的编译时间,Rust则提供了无垃圾回收的内存安全保证。这些语言在特定场景下确实比Java更具优势。然而,重要的是认识到Java与这些语言之间的关系不完全是零和游戏。许多组织采用多语言策略,在不同场景中使用最适合的语言。Java的强项在于大型复杂企业应用的开发,这方面的优势仍然难以撼动。Java生态系统通过互操作性来应对多语言趋势。GraalVM支持多种语言运行在同一个运行时环境中,Spring框架提供了与其他语言服务的集成能力。这种开放性使Java能够在一个多语言世界中保持其核心地位。4.2 云原生时代的适应云原生计算对传统Java应用提出了新的挑战。容器环境强调轻量级和快速启动,无服务器平台需要极低的内存占用和瞬时扩展能力。Java传统上较长的启动时间和较高的内存占用在这些环境中处于劣势。Java社区已经积极应对这些挑战。通过模块化系统,开发者可以创建更小的运行时镜像,只包含应用程序实际需要的模块。框架如Quarkus和Micronaut专门为云原生环境设计,提供了极快的启动速度和低内存占用。工具链的改进也帮助Java更好地适应云原生时代。Jib等工具简化了Java应用容器化的过程,云原生构建包提供了自动化的应用容器化方案。这些工具降低了Java应用向云原生迁移的门槛。4.3 开发体验的现代化开发者体验是现代编程语言竞争的重要战场。Java传统上被认为繁琐而冗长,缺乏现代语言的简洁性和表达力。Java通过持续的语言改进来解决这些问题。var局部变量类型推断减少了样板代码,文本块简化了多行字符串的处理,记录类提供了简洁的数据载体定义方式。这些改进使Java代码更加简洁,同时保持了类型安全。开发工具也在不断进步。Visual Studio Code的Java支持提供了轻量级开发体验,GitHub Cop等AI辅助编程工具提高了Java开发效率。现代IDE如IntelliJ IDEA提供了强大的代码分析和重构功能。这些改进使Java开发体验更加现代化,减少了与新兴语言在这方面的差距。第五章 未来展望与学习建议5.1 Java的未来发展轨迹基于当前的发展趋势,Java的未来呈现出几个明确的发展方向。首先,Java将继续巩固其在企业级应用开发中的地位,通过持续改进和生态扩展保持领先优势。大型企业系统的迁移成本极高,这种惯性将成为Java的天然护城河。其次,Java将加速向云原生环境演进。通过Project Loom、GraalVM原生镜像等新技术,Java将更好地适应容器化和无服务器计算环境,夺回在这些新兴领域的竞争力。第三,Java将继续扩展其应用领域。通过改进的本地代码互操作性和向量计算支持,Java将进入传统上由C++和Fortran主导的高性能计算领域。在边缘计算和物联网领域,Java也有巨大潜力。最后,Java将保持其开放性和多样性。多语言运行时的支持使Java能够与其他语言和谐共存,形成一个更加丰富和多样的生态系统。5.2 对Java爱好者们的建议对于现有的Java开发者,以下建议可能有助于应对未来的变化:深入理解Java核心概念和JVM工作原理至关重要。语言特性会变化,但核心原理的价值是持久的。深入了解内存模型、垃圾回收机制和并发模型将使你能够更好地利用Java的新特性。拥抱云原生技术和实践。学习容器化、Kubernetes和无服务器计算将使你能够将Java应用成功部署到现代云环境中。掌握Spring Boot、Quarkus或Micronaut等框架的云原生特性。探索新特性并适时采用。虚拟线程、原生镜像等新特性具有革命性潜力,及早掌握这些技术将为你的职业发展带来优势。但同时要注意评估新特性的成熟度和适用性,避免在生产环境中过早采用。拓宽技术视野,学习互补技术。Java开发者应该了解Python、Go或JavaScript等其他语言,以及数据库、消息队列和分布式系统等相关技术。全栈开发能力越来越有价值。参与Java社区和开源项目。通过参与社区活动、贡献开源项目,你不仅可以扩展知识面,还能建立专业网络,了解行业最新动态。5.3 对初学者的建议对于考虑学习Java的初学者,以下建议可能有所帮助:Java仍然是值得学习的语言。其广泛的应用领域、丰富的就业机会和稳定的生态系统使其成为可靠的职业选择。学习Java为你打开了进入大型企业开发世界的大门。从基础开始,循序渐进。先掌握Java核心概念和面向对象编程原理,再学习常用框架和工具。实践是最好的学习方式,通过实际项目巩固理论知识。不要局限于Java语言本身。了解JVM工作原理、学习设计模式、掌握软件工程最佳实践同样重要。这些知识将使你成为更好的开发者,无论使用什么语言。建立完整的技术栈。除了Java,还应该学习前端技术、数据库、DevOps工具等相关技能。全栈开发者在就业市场上更具竞争力。参与实践项目和社区。通过GitHub贡献开源项目、参与编程社区讨论、完成实际项目,你将获得宝贵经验和反馈。这些实践经验往往比理论知识更有价值。结论Java的霸主地位虽然面临挑战,但远未被撼动。其强大的生态系统、持续的语言创新和企业级应用的深厚根基构成了难以复制的竞争优势。Project Loom的虚拟线程、GraalVM的原生镜像等新特性不仅解决了Java的传统痛点,还为其开辟了新的应用领域。在可预见的未来,Java仍将是企业级应用开发的主导力量,同时在云原生、边缘计算等新兴领域不断扩大影响力。对于开发者而言,Java仍然是值得投入时间和精力学习的语言,提供了稳定的职业发展和丰富的创新机会。技术的世界永远充满变数,但Java通过持续演进和生态扩展证明了其适应性和生命力。无论是Java爱好者还是初学者,都可以对Java的未来保持乐观,同时以开放的心态拥抱变化和多语言共存的未来。参考资料Oracle. (2023). “Java Language and Virtual Machine Specifications”Microsoft. (2023). “Java on Azure Development Guide”VMware. (2023). “Spring Framework Reference Documentation”Red Hat. (2023). “Quarkus: Supersonic Subatomic Java”Oracle Labs. (2023). “Project Loom: Fibers and Continuations for the Java Virtual Machine”GraalVM Team. (2023). “GraalVM: Run Programs Faster Anywhere”JetBrains. (2023). “The State of Developer Ecosystem 2023”TIOBE Software. (2023). “TIOBE Index for Programming Languages”GitHub. (2023). “The State of the Octoverse”Stack Overflow. (2023). “Stack Overflow Developer Survey 2023”————————————————原文链接:https://blog.csdn.net/lilinhai548/article/details/150699267
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签