CNB: Cloud Native Buildpacks 与 Paketo

是什么 Cloud Native Buildpacks(常缩写 CNB)是一套把应用源码或编译产物自动打成 OCI 容器镜像的规范与工具生态,站点在 buildpacks.io。思路类似早年 Heroku、Cloud Foundry 上的 buildpack:平台检测你推上来的是什么(Java jar、Node 项目等),自动选运行时、装依赖、写好启动命令,不必为每种语言手写 Dockerfile。 Paketo(paketo.io)是 CNB 生态里常用的开源实现,提供现成的 builder 镜像和一系列 language buildpack(Java、Node.js、Go、Python 等)。它与 Spring 无隶属关系,但 Spring Boot 的 bootBuildImage / spring-boot:build-image 默认使用 Paketo 的 builder。 CNB ← 规范 + 生态(buildpack / builder / lifecycle) │ ├── Paketo ← 开源实现(Spring Boot 默认路径) ├── Heroku buildpacks └── Google Cloud buildpacks … 调用方: pack CLI、Spring Boot 插件、Tekton、CI … → 拉 builder → 跑 buildpack → 出 OCI 镜像 Spring Boot 侧如何把 fat jar、分层 JAR、jlink 与 Buildpack 串起来,见 Spring Boot Container Packaging。 ...

Created 21216-06-21 · Updated 12126-10-12 · 3 min · 589 words · -

Spring Boot Startup Callbacks: 应用启动后执行逻辑

若要在 Spring 应用上下文已就绪 之后执行一段初始化代码,常见做法如下。多个 Bean 可同时实现同一类接口,使用 @Order 或 Ordered 控制顺序。框架总览见 Spring Boot。 ApplicationRunner 与 CommandLineRunner 二者都会在 SpringApplication 启动流程末尾被调用。需要结构化解析命令行参数(如 key=value 形式)时,优先使用 ApplicationRunner(入参为 ApplicationArguments);只关心原始字符串数组时用 CommandLineRunner 即可。 @Component @Order(1) public class SeedDataRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { // post-startup logic } } @Component @Order(2) public class BannerRunner implements CommandLineRunner { @Override public void run(String... args) { // post-startup logic } } @PostConstruct 在某个 Spring Bean 完成依赖注入之后、对外使用前执行,适合该 Bean 自身的轻量初始化。不要在 static 方法上使用 @PostConstruct(规范要求实例方法)。 ...

Created 26266-06-26 · Updated 11116-08-11 · 1 min · 135 words · wiloon

Spring AOT

是什么 Spring AOT(Ahead-Of-Time)是 Spring Framework 6 / Spring Boot 3 引入的构建期处理机制:在打包或 Native 编译之前,对 Spring ApplicationContext 做静态分析,生成预先计算好的 bean 定义、反射/资源 hint,以及(可选)面向 GraalVM Native Image 的入口代码。 默认 Spring Boot 应用在运行时做 classpath 扫描、@Configuration 解析、条件装配(@Conditional / @ConditionalOn*)和大量反射;Spring AOT 把这些工作尽量前移到构建期,使: 在 HotSpot JVM 上跑的处理后 JAR,启动更快(仍用 java -jar) 在 GraalVM Native Image 路径上,构建器能拿到闭世界分析所需的元数据 不要和 JDK AOT 混淆 博客与 JDK 发行说明里还有另一类「AOT」,含义不同: 名称 层级 做什么 Spring AOT Spring 框架 / Boot 插件 构建期处理 IoC 容器,生成 bean 与 Native hint GraalVM Native Image AOT GraalVM 工具链 字节码 → 本地机器码 + Substrate VM JDK AOT 缓存(JEP 483 等) HotSpot JVM 训练运行后缓存类加载/链接/剖析,缩短下次 JVM 冷启动 Spring AOT 常与 GraalVM Native Image 配合使用,但不是同一 thing;JDK 24+ 的 AOT 缓存与 Spring AOT 可以并存,也互不替代。JDK 侧见 JDK 25 变更汇总、JVM 编译器。 ...

Created 21216-06-21 · Updated 15156-07-15 · 3 min · 596 words · -

HikariCP: 连接池参数与泄漏排查

HikariCP 是 Spring Boot 2.x 及以上版本的默认数据库连接池,spring-boot-starter-jdbc/spring-boot-starter-data-jpa 在类路径上检测到它时会自动创建对应的 DataSource,见 spring-boot-relational-persistence.md。 核心设计取向 HikariCP 相比 DBCP、C3P0 等老连接池,主打「轻量、少锁竞争」:连接获取路径尽量避免同步阻塞,字节码层面做了不少微优化(如去掉不必要的包装类)。这也是它成为默认选型的主要原因——同等硬件下吞吐更高、延迟更低。 关键参数 参数 作用 建议 maximumPoolSize 连接池最大连接数 不是越大越好,经验公式 连接数 = (核心数 * 2) + 有效磁盘数;通常 10-20 已足够,过大会导致数据库端连接争用、上下文切换开销上升 minimumIdle 最小空闲连接数 建议和 maximumPoolSize 设成一致,避免连接数动态伸缩带来的抖动(官方推荐固定池大小) connectionTimeout 从池中获取连接的等待超时 默认 30s,业务上一般调到 3-5s,超时快速失败优于线程一直阻塞 idleTimeout 空闲连接被回收前的最长空闲时间 默认 10 分钟,仅在 minimumIdle < maximumPoolSize 时生效 maxLifetime 连接最大生命周期 默认 30 分钟,必须比数据库或负载均衡器的连接超时(如 MySQL wait_timeout)短,否则会用到已被服务端关闭的连接 leakDetectionThreshold 连接泄漏检测阈值 默认 0(关闭);生产建议设 30000-60000ms,超过该时间连接还未归还池会打印 WARN 日志和借出时的调用栈 application.yml 配置示例: spring: datasource: hikari: maximum-pool-size: 15 minimum-idle: 15 connection-timeout: 3000 max-lifetime: 1500000 leak-detection-threshold: 60000 maxLifetime 为什么防不住连接泄漏 容易有的误解是:既然配了 maxLifetime,超时的连接应该会被自动回收,不会一直泄漏下去。实际上 maxLifetime 只对归还回池中的空闲连接生效,对被业务代码借出、一直没有 close() 的连接不生效。 ...

Created 2026-07-02 · Updated 2026-07-02 · 1 min · 187 words · -

Spring Boot

Spring Boot 是什么 Spring Boot 是 Pivotal (现为 VMware/Broadcom)在 2014 年推出的框架,建立在 Spring Framework 之上,目标是简化整个 Spring 生态的开发、配置和部署。它不只是简化 Spring MVC,同样简化了 Spring Data(数据库访问)、Spring Security(认证授权)、Spring Messaging(消息队列)等全家桶的配置。 核心理念是 “约定优于配置”(Convention over Configuration):框架提前定好"默认约定",只要遵循这些约定,就不需要写任何配置;只有想偏离默认行为时,才需要显式配置。 例如,classpath 里有 spring-boot-starter-web,Spring Boot 就约定你要启动一个 Web 服务,自动完成:注册 DispatcherServlet、内嵌 Tomcat 监听 8080 端口、注册 Jackson 用于 JSON 序列化。你什么都不写,java -jar app.jar 就能跑起来。需要覆盖默认行为时,只在 src/main/resources/application.yml 中写偏离默认值的部分: server: port: 9090 # 只需声明偏离默认值的配置 也可通过命令行参数临时覆盖:java -jar app.jar --server.port=9090,优先级高于 application.yml。 出现背景 Spring Boot(2014)与微服务架构的兴起同期出现。2014 年 Martin Fowler 发表了微服务架构的奠基性文章,微服务要求每个服务独立部署、快速启动、进程隔离。传统的 WAR 包 + 外部 Tomcat 的部署方式与这一需求存在明显矛盾,Spring Boot 正是为了解决这一问题而生。 ...

Created 11116-05-11 · Updated 30306-06-30 · 9 min · 1853 words · wiloon

Spring Boot Executable JAR: Fat JAR 与 JarLauncher

背景 Spring Boot 把应用打成单个可执行 JAR,java -jar app.jar 即可启动内嵌 Tomcat(或 Jetty/Undertow)与全部依赖。这与传统「WAR 部署到外部 Tomcat、classpath 由容器管理」不同,也不同于 Maven jar-with-dependencies 那种把依赖 class 解压合并进一个 jar 的做法。 本文说明 Spring Boot 可执行 JAR 的内部布局、JarLauncher 如何启动应用,以及 repackage / bootJar 如何生成该产物。容器镜像、分层 JAR、Buildpack 等部署话题见 Spring Boot Container Packaging;框架层面的「内嵌服务器 + 可执行 JAR」见 Spring Boot。 Thin jar 与 fat jar Fat jar、uber jar、executable jar 在 Spring Boot 语境下指同一件事:一个「全包」可执行 jar。Fat jar 与 uber jar 只是不同叫法(uber 来自德语 über,意为「之上 / 全部」);Spring 官方文档更常写 executable jar。 与 Maven/Gradle 默认打出的普通 jar(常称 thin jar / plain jar)对比如下: ...

Created 24246-06-24 · Updated 27276-06-27 · 5 min · 973 words · -

Spring Boot 3.x Migration: 关键特性与约束

Spring Boot 3.0(2022 年底发布)底层依赖 Spring Framework 6.x。本文汇总从 2.x 升级到 3.x 时最常见的破坏性变更与约束。框架总览见 Spring Boot。 Spring Boot 与 Spring Framework 版本对应 Spring Boot Spring Framework 3.0.x 6.0.x 3.2.x 6.1.x 3.3.x / 3.4.x 6.1.x / 6.2.x 3.5.x 6.2.x 1. Java 版本要求 Spring Boot 3.x 最低要求 Java 17。 2. javax → jakarta 命名空间迁移(最大变更) Java EE 已捐赠给 Eclipse 基金会,品牌更名为 Jakarta EE,所有包名从 javax.* 改为 jakarta.*。这是代码层面影响最广的变更,几乎所有用到 Servlet、JPA、Bean Validation 的代码都要修改: 旧包名(javax) 新包名(jakarta) javax.servlet.* jakarta.servlet.* javax.persistence.* jakarta.persistence.* javax.validation.* jakarta.validation.* javax.transaction.* jakarta.transaction.* javax.annotation.* jakarta.annotation.* import jakarta.servlet.http.HttpServletRequest; import jakarta.persistence.Entity; 3. Spring Security 6.x:WebSecurityConfigurerAdapter 彻底移除 WebSecurityConfigurerAdapter 已彻底移除,必须改为注册 SecurityFilterChain Bean,详见 Spring Security。 ...

Created 26266-06-26 · Updated 26266-06-26 · 2 min · 214 words · wiloon

Spring Boot Container Packaging: Fat JAR、分层 JAR 与 Buildpacks

背景 Spring Boot REST API 打成 jar 之后,放进容器常见有三层递进:先有一个能跑的 fat jar,再用分层 JAR 优化 Docker 缓存,最后用 Cloud Native Buildpacks 一键出镜像;若还要缩小运行时,可在 Buildpack 上显式开启 jlink 裁剪 JRE。fat jar 布局与 JarLauncher 见专文 Spring Boot Executable JAR。 Spring Framework 本身几乎不讲容器镜像;相关说明集中在 Spring Boot Reference 的 Packaging → Container Images。本文把分层 JAR、bootBuildImage、Paketo jlink 串成一条阅读路径,并附上官方文档索引。GraalVM Native Image 是另一条路线,见 GraalVM Native Image 简介。 三条路径总览 flowchart TB subgraph build [构建] SRC[源码 mvn package / bootJar] FAT[Fat JAR 可执行 uber jar] LAY[分层 JAR 含 layers.idx] end subgraph deploy [容器化] D1[简单 DockerfileCOPY jar + java -jar] D2[分层 Dockerfilejarmode extract --layers] BP[bootBuildImage / build-imagePaketo Buildpacks] end subgraph runtime [运行时 JVM] JRE[完整 JRE] JLINK[jlink 精简 JREBP_JVM_JLINK_ENABLED] end SRC --> FAT FAT --> LAY FAT --> D1 LAY --> D2 LAY --> BP D1 --> JRE D2 --> JRE BP --> JRE BP -.->|可选| JLINK 路径 典型产物 主要收益 复杂度 Fat jar + 简单镜像 单层 app.jar + JRE 基础镜像 上手最快 低 分层 JAR + Dockerfile 多 COPY 层 + JRE 推送镜像时依赖层可缓存 中 Buildpack(bootBuildImage) OCI 镜像,自动检测 jar 零 Dockerfile、与 Paketo 生态一致 低~中 Buildpack + jlink 同上,运行时 JRE 更小 缩小镜像体积 中(需调 env) 分层 JAR 分层 JAR 在 fat jar 布局不变的前提下,额外写入 BOOT-INF/layers.idx,声明哪些路径属于哪一层、层的写入顺序。Spring Boot 2.3 起默认开启;Maven repackage / Gradle bootJar 的 layered 无需额外配置即可得到。 ...

Created 21216-06-21 · Updated 24246-06-24 · 5 min · 1027 words · -

Forward 与 Redirect

forward vs redirect forward(请求转发):服务器收到请求后,在服务器内部把请求转交给另一个资源处理,再把结果返回客户端;浏览器地址栏不变,客户端不知道发生了转发。 redirect(重定向):服务器返回 3xx 状态码,浏览器再次发起新请求访问目标 URL,地址栏变为新地址;因此有两次往返,通常比 forward 开销大。 数据与作用域 forward:同一 HttpServletRequest 继续传递,request 属性、request 参数可带到目标资源;常用 request.getAttribute() 读取上一环节放入的数据。 redirect:浏览器发的是新请求,上一请求的 request 属性不会保留;URL 查询参数会随新请求带上。HttpSession 仍可通过 cookie 关联(session 级数据一般可用),但不要把「上一 request 的 attribute」误当成 redirect 后还能直接读。 效率:forward 仅在服务端处理,对客户端透明;redirect 多一次客户端往返,通常更慢。 范围:因 request.setAttribute() 的生命周期限于当前 request,redirect 会导致 attribute 丢失;需要跨 redirect 传值时,常用 session、URL 参数或 flash 等机制。 前后端分离下的表现 典型架构为 React 前端 + Spring Boot REST API。页面路由与组件组合由前端负责,后端主要返回 JSON;因此早年 JSP 时代「forward 到另一个 JSP 拼页面」的用法在业务层已基本消失,但两种机制仍以不同形式存在。 redirect:仍然常见 redirect 是 HTTP 层行为,与是否使用 JSP 无关。前后端分离项目中常见场景: OAuth / 第三方登录:授权服务器回调后,后端或网关返回 302 跳转到前端路由(如 /auth/callback) PRG(Post-Redirect-Get):表单或写操作后 return "redirect:/...",避免刷新重复提交(多见于仍保留 @Controller 的服务端表单) 文件下载、外链跳转:API 或网关返回 302 指向实际资源地址 网关 / 反向代理:未登录时重定向到登录页(也可由前端根据 401 自行跳转) 前端日常路由切换由 React Router 完成,不经过 Servlet redirect;但跨域认证、登录回调、下载等仍会碰到 HTTP 重定向。 ...

Created 21216-09-21 · Updated 21216-06-21 · 1 min · 206 words · -

Servlet 生命周期

Servlet 运行在 Servlet 容器中,生命周期由容器管理。规范通过 Servlet 接口的 init()、service()、destroy() 描述这一过程;Java EE 8 及以前包名为 javax.servlet.*,Spring Boot 3 起对应 Jakarta Servlet(jakarta.servlet.*),语义不变。 四个阶段 加载和实例化 Servlet 容器负责加载和实例化 Servlet。容器启动时,或在首次需要该 Servlet 处理请求时,创建实例。容器通过类加载器找到 Servlet 类,再用反射调用无参构造方法实例化,因此业务 Servlet 一般不应定义带参构造方法。 初始化 实例化后,容器调用 init() 做一次性初始化(如读取配置、建立连接)。每个 Servlet 实例的 init() 只执行一次。初始化期间可通过 ServletConfig 读取配置(传统项目多在 web.xml,Spring Boot 中常由框架代劳)。失败时可抛出 ServletException 或 UnavailableException 通知容器。 请求处理 请求到达后,容器调用 service();HttpServlet 会按 HTTP 方法分派到 doGet()、doPost() 等。service() 执行前 init() 必须已成功完成。处理中通过 ServletRequest / ServletResponse(或 HttpServletRequest / HttpServletResponse)读写请求与响应。若抛出表示永久不可用的 UnavailableException,容器会调用 destroy() 并返回 404;暂时不可用则返回 503。 服务终止 容器卸载 Servlet 或关闭时调用 destroy(),释放资源。之后实例被回收;若再次需要,容器会创建新实例。 ...

Created 30306-10-30 · Updated 21216-06-21 · 2 min · 227 words · -

Spring Boot DevTools

背景 开发 Spring Boot 时,spring-boot-devtools 提供 快速重启 与 静态资源热刷新,常被误认为「和 IDE HotSwap 一样改 Java 就能生效」。本文说明它实际做什么,并与 开发期热替换、JPDA 区分。 Spring Boot 总览见 Spring Boot。 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> 仅在开发 classpath 使用;生产打包应排除(optional + 不打进 fat jar 的常见做法)。 两个 ClassLoader(Restart) DevTools 默认启用 Restart: ClassLoader 加载内容 变更时 base 第三方依赖(spring-boot-starter-* 等) 一般不 reload restart 你的 target/classes 业务代码 监控到变更后 快速重启 应用上下文 「快速重启」不是 HotSpot 的 类热替换:而是 停掉当前 Spring 上下文再拉起,但 base ClassLoader 保留,比冷启动整个 JVM 快。 因此: 改 业务 Java 类 → 触发 restart(仍是一次上下文重建,不是 JPDA 改方法体) 改 静态资源(src/main/resources 下 html/css 等)→ LiveReload 通知浏览器刷新,无需重启 JVM 与 HotSwap / BTrace 的对比 机制 层级 典型场景 DevTools Restart Spring 上下文 + 双 ClassLoader 本地改代码后几秒内重新跑 Bean IDE HotSwap JVM + JPDA / 有限 redefine Debug 下改 方法体 DCEVM + HotSwapAgent 增强 HotSpot + javaagent 开发机放宽 redefine、重载 Spring 配置 BTrace attach Attach + ASM 已运行 进程观测,非开发热更 DevTools 不 使用 Attach API 给业务类插探针;也 不 替代 mvn spring-boot:run 外的生产部署。 ...

Created 31316-05-31 · Updated 31316-05-31 · 1 min · 189 words · -

Spring Boot 关系型数据库持久层概览

概述 在 Spring Boot 里访问 MySQL、PostgreSQL、SQLite 等关系型数据库,常见有几条路线。它们最终都经过 JDBC 连到数据库,差别在于中间抽象层有多高、SQL 由谁写。 应用代码 │ ├── Spring Data JDBC ← Repository 风格,底层仍是 JDBC ├── Spring Data JPA ← 对象为主,框架生成 SQL │ └── JPA(标准)→ Hibernate(实现) ├── MyBatis (+ Plus) ← SQL 手写 + 映射 └── JdbcTemplate / JDBI / jOOQ ← SQL 路线(字符串或类型安全 DSL) 下面分别介绍 Spring Data JDBC、Spring Data JPA、MyBatis、JdbcTemplate、JDBI、jOOQ,并简要说明 JPA 本身是什么。 Spring Data JDBC Spring Data JDBC 是 Spring 官方提供的 轻量持久化方案,依赖 spring-boot-starter-data-jdbc。它提供与 Spring Data JPA 类似的 CrudRepository 接口风格,但 不是 ORM,底层仍通过 JdbcTemplate 访问数据库。 ...

Created 22226-05-22 · Updated 20206-05-20 · 5 min · 974 words · -

Spring Boot + PostgreSQL 持久层框架

主流持久层框架 Spring Data JPA + Hibernate Spring Boot 默认推荐,依赖 spring-boot-starter-data-jpa ORM 框架,用注解映射对象和表 自动生成 SQL,支持 JPQL / Criteria API 适合领域模型复杂的场景 MyBatis / MyBatis-Plus 半自动 ORM,SQL 写在 XML 或注解中 对 SQL 控制力强,适合复杂查询 MyBatis-Plus 提供代码生成、分页等增强功能 JOOQ 类型安全的 SQL DSL,SQL 风格写法 编译期检查 SQL,适合喜欢写 SQL 的团队 商业数据库需付费版 Spring Data JDBC 比 JPA 更轻量,无懒加载、缓存等复杂特性 SQL 更透明,适合简单 CRUD R2DBC(响应式) 非阻塞响应式驱动,配合 WebFlux 使用 依赖 spring-boot-starter-data-r2dbc 选型建议 场景 推荐 快速开发,标准 CRUD Spring Data JPA 复杂 SQL,精细控制 MyBatis-Plus 强类型 SQL,重查询业务 JOOQ 响应式架构 R2DBC 连接池 Spring Boot 默认使用 HikariCP 作为连接池。 ...

Created 25256-04-25 · Updated 11116-05-11 · 3 min · 467 words · -