Spring Boot Starter 封装实践报告

Spring Boot Starter 封装实践报告:从自动装配原理到手写 Starter 🎯 第 1 步:目标说明 某开发者在日常工作中频繁需要为项目集成日志记录、性能监控、消息通知等功能。每次引入新功能时,都要重复编写相似的配置类、注册 Bean、管理依赖——这些步骤机械而繁琐。Spring Boot Starter 正是为解决这一问题而设计的机制:它把自动配置类与依赖管理打包成一个独立的 Jar 包,引用一个 Starter 依赖就能让某个功能"开箱即用"。 本实践报告的目标如下: 理解 Spring Boot 自动装配(Auto Configuration)的核心原理与执行流程 动手封装一个名为 my-logging-spring-boot-starter 的自定义 Starter,功能是自动记录标注了特定注解的方法的执行耗时 在测试项目中引用自定义 Starter,验证功能正常工作 📌 前置知识:本报告假设读者已经掌握 Java 基础语法、Maven 依赖管理与模块化工程、Spring 的 @Bean 与 @Configuration 注解、Spring Boot 基本使用方式。 📋 第 2 步:前置条件 开始实践前,确保以下软件已正确安装。 软件/依赖 最低版本 说明 JDK 17 Spring Boot 3.2.0 要求 Java 17 及以上 Maven 3.6.3 项目构建、依赖管理与打包 IDE 任意 IntelliJ IDEA(社区版即可)或 VS Code 验证安装: java --version # 期望输出示例:openjdk 17.0.9 2023-10-17 LTS mvn --version # 期望输出示例:Apache Maven 3.9.5 ⚠️ 新手提示:如果 java --version 或 mvn --version 提示"命令未找到",说明对应软件没有安装或没有配置环境变量。JDK 需要设置 JAVA_HOME 并将 %JAVA_HOME%\bin 加入 PATH。Maven 需要将 MAVEN_HOME/bin 加入 PATH。完成配置后重新打开终端再执行验证命令。 ...

十月 14, 2022 · 14 分钟 · 2963 字 · yaomingye

开发常用 100 条 Linux 指令全解析

🐧 开发常用 100 条 Linux 指令全解析:从系统监控到性能调优 引言:为什么要掌握这些指令 在日常开发和运维工作中,服务器出现问题时的第一反应往往是 SSH 登录上去排查。能不能在最短的时间内定位到根本原因,取决于对 Linux 诊断指令的熟练程度。这些指令不仅是敲几个字母的组合,更重要的是—— 能看懂输出里每一个数字和字段代表什么 。 下图展示了从服务器出现异常到定位根因的完整诊断链路,以及各个环节对应的核心指令分类: flowchart TD PROBLEM([🚨 服务器异常]) --> CHECK_LOAD{"负载过高\n响应变慢?"} CHECK_LOAD -->|是| PATH_LOAD[📊 系统信息诊断] CHECK_LOAD -->|否| CHECK_MEM{"内存不足\nOOM ?"} CHECK_MEM -->|是| PATH_MEM[🧠 内存诊断] CHECK_MEM -->|否| CHECK_IO{"磁盘问题\nIO 等待?"} CHECK_IO -->|是| PATH_IO[💾 磁盘诊断] CHECK_IO -->|否| CHECK_NET{"网络异常\n连接失败?"} CHECK_NET -->|是| PATH_NET[🌐 网络诊断] CHECK_NET -->|否| CHECK_PROC[🔍 进程级排查] PATH_LOAD --> CMD1["uptime / top / vmstat"] PATH_MEM --> CMD2["free / sar / /proc/meminfo"] PATH_IO --> CMD3["iostat / iotop / df"] PATH_NET --> CMD4["ss / ping / tcpdump"] CHECK_PROC --> CMD5["ps / strace / lsof / journalctl"] style PROBLEM fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#ffffff,font-weight:bold style CHECK_LOAD fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ffffff,font-weight:bold style CHECK_MEM fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ffffff,font-weight:bold style CHECK_IO fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ffffff,font-weight:bold style CHECK_NET fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ffffff,font-weight:bold style PATH_LOAD fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#ffffff style PATH_MEM fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#ffffff style PATH_IO fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#ffffff style PATH_NET fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#ffffff style CHECK_PROC fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#ffffff style CMD1 fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#ffffff,font-weight:bold style CMD2 fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#ffffff,font-weight:bold style CMD3 fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#ffffff,font-weight:bold style CMD4 fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#ffffff,font-weight:bold style CMD5 fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#ffffff,font-weight:bold 本文按照 10 大分类 组织 100 条指令,每一条都包含常用选项、实际输出示例、输出参数逐列解读,以及能从这些数据中看出服务器的什么状态。 ...

十月 14, 2022 · 32 分钟 · 6673 字 · yaomingye

Spring Boot 开发中的上下文

Spring Boot 开发必知:那些高频使用的核心上下文类 🐛 从一个 NPE 说起 同事在 IdUtils 工具类里写了一个生成订单号的方法,需要调用数据库序列服务。代码部署到生产环境后,每隔几天就会抛出一个 NullPointerException,而且总是在凌晨 2 点左右。 排查后发现问题:生成订单号的逻辑需要从 Spring 容器中获取 SequenceService,但 IdUtils 是一个纯静态工具类,不归 Spring 管理。同事的写法是: public class IdUtils { // 这样永远拿不到 Bean——IdUtils 自己都没被 Spring 管理,谁来注入? @Autowired private static SequenceService sequenceService; public static String genOrderId() { return sequenceService.nextVal("order"); // NPE! sequenceService == null } } 这是一个典型场景: 需要在不受 Spring 管理的类中获取 Spring Bean 。解决它的钥匙就是本篇要讲的"上下文类"(Context Classes)——Spring 框架提供的一系列能让你在任何位置获取框架运行时状态的工具。 flowchart LR classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe,font-weight:bold; classDef branch fill:#2d1a05,stroke:#f59e0b,stroke-width:2px,color:#fde68a,font-weight:bold; classDef leaf fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; ROOT[Spring Boot 核心上下文类] ROOT --> B1(1. Web 请求上下文) B1 --> L1["RequestContextHolder\n持有当前请求的 ThreadLocal"] B1 --> L2["ServletRequestAttributes\n封装 HttpServletRequest/Response"] B1 --> L3["RequestContextUtils\nLocale / FlashMap / 输入输出流"] ROOT --> B2(2. Security 安全上下文) B2 --> L4["SecurityContextHolder\n持有当前认证信息的 ThreadLocal"] B2 --> L5["Authentication\nPrincipal / Credentials / Authorities"] ROOT --> B3(3. 事务上下文) B3 --> L6["TransactionSynchronizationManager\n事务状态判断 / 回调注册\n事务资源绑定"] ROOT --> B4(4. 容器上下文) B4 --> L7["ApplicationContext\nSpring 容器本身"] B4 --> L8["ApplicationContextAware\n回调注入容器引用"] B4 --> L9["Environment\n配置属性 / Profile"] ROOT --> B5(5. 其他) B5 --> L10["LocaleContextHolder\n国际化语言上下文"] B5 --> L11["BeanFactory\n底层 IoC 容器"] class ROOT root; class B1,B2,B3,B4,B5 branch; class L1,L2,L3,L4,L5,L6,L7,L8,L9,L10,L11 leaf; class L1,L4,L7 highlight; 🌐 一、Web 请求上下文 ⚙️ 1.1 核心类与底层原理 RequestContextHolder(请求上下文持有者)通过 ThreadLocal(线程局部变量)将当前请求的 ServletRequestAttributes 绑定到当前线程。DispatcherServlet(Spring MVC 的前端控制器)在处理每个请求时,会自动调用 RequestContextHolder.setRequestAttributes() 将请求对象"挂"到当前线程上。 ...

十月 7, 2022 · 12 分钟 · 2418 字 · yaomingye

数据库迁移实战

🗄️ 数据库迁移实战:不停机迁移方案、数据一致性保障与工具选型全解析 从一个凌晨 3 点的故障说起 某电商平台的订单表 orders 有 2.3 亿行数据,运行在 MySQL 5.7 上,单表体积接近 400GB。团队计划将这张表迁移到 TiDB 分布式数据库,以应对即将到来的双十一流量峰值。 DBA 团队的迁移方案是: 凌晨 2 点,停止所有写入服务 用 mysqldump 导出全量数据(耗时 1 小时 20 分钟) 将 dump 文件导入 TiDB(耗时 3 小时) 凌晨 6 点 20 分,恢复写入服务 结果:凌晨 4 点 30 分,dump 文件导入到一半时报错——导出文件中有 3 行数据包含 MySQL 5.7 特有的 utf8mb4_general_ci 排序规则下的隐藏字符,TiDB 解析失败。此时 MySQL 5.7 已被设置为只读,TiDB 导入中断, 整个订单系统处于不可用状态 。 最终临时回滚 MySQL 只读限制,恢复业务。迁移失败,双十一扩容计划延期。 这次故障暴露了数据库迁移中的核心难题:如何在保证数据一致性的前提下,尽可能缩短甚至消除停机时间,并且始终保留可靠的回滚路径。 数据库迁移策略总览 数据库迁移不是单一操作,而是一整套工程方法论。先通过思维导图建立全局认知: flowchart LR classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe,font-weight:bold; classDef branch fill:#2d1a05,stroke:#f59e0b,stroke-width:2px,color:#fde68a,font-weight:bold; classDef leaf fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; ROOT[数据库迁移策略体系] ROOT --> B1(1. 按停机时间分类) B1 --> L1["🛑 停机迁移\n• 停服→导出→导入→恢复\n• 停机: 小时至天级\n• 风险: 业务中断"] B1 --> L2["⚡ 零停机迁移\n• 双写/CDC/灰度切换\n• 停机: 秒级切换\n• 风险: 数据不一致"] B1 --> L3["🔄 滚动迁移\n• 按分片/租户逐批切\n• 停机: 每批秒级\n• 风险: 跨片依赖"] ROOT --> B2(2. 按数据同步方式分类) B2 --> L4["📦 全量+增量\n• 全量快照 + binlog 追赶\n• 代表: DTS/Canal/Debezium"] B2 --> L5["✍️ 双写\n• 应用层同时写新旧库\n• 全量回溯 + 双写 + 校验"] B2 --> L6["🔁 主从复制\n• 新库作为旧库的从库\n• 追平后切换"] ROOT --> B3(3. 按迁移目标分类) B3 --> L7["🏗️ 同构迁移\n• MySQL→MySQL 版本升级\n• 工具: gh-ost/pt-osc"] B3 --> L8["🔀 异构迁移\n• MySQL→TiDB/PostgreSQL\n• 需处理类型/SQL差异"] B3 --> L9["☁️ 上云迁移\n• 自建→RDS/云原生DB\n• 工具: DTS/DataX"] class ROOT root; class B1,B2,B3 branch; class L1,L2,L3,L4,L5,L6,L7,L8,L9 leaf; class L2,L4 highlight; 三类策略并非互斥——零停机迁移通常是"双写 + 全量快照 + 增量追赶 + 灰度切换"的组合。 ...

十月 6, 2022 · 9 分钟 · 1821 字 · yaomingye

从 Docker Compose 到 Kind

☸️ 从 Docker Compose 到 Kind:在 WSL 上用 Kind 入门 Kubernetes 全指南 📌 一、问题切入:你已经会用 Docker Compose,然后呢 假设你在 WSL(Windows Subsystem for Linux,Windows 内置的 Linux 子系统)上维护着一个项目, docker-compose.yml 里定义了 nginx、应用服务、Redis、MySQL 四个容器: version: "3.8" services: nginx: image: nginx:1.25 ports: ["80:80"] volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"] app: build: ./app ports: ["5000:5000"] environment: REDIS_HOST: redis MYSQL_HOST: mysql depends_on: [redis, mysql] redis: image: redis:7-alpine mysql: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: secret docker compose up -d 一键启动。但当你需要面对以下需求时,Compose 开始显得吃力: ...

十月 4, 2022 · 18 分钟 · 3803 字 · yaomingye

JUC 在中间件中的应用

JUC 在中间件中的应用:线程池与并发集合实战全景 问题切入:道格·李的组件在中间件里是如何落地的 道格·李设计的每一个 JUC 组件都有明确的定位:ThreadPoolExecutor 管理线程资源、ConcurrentHashMap 提供高并发下的安全容器、BlockingQueue 协调生产者与消费者。但这些组件本身只是"积木"——积木搭成什么,看用的人。 Tomcat、Netty、Dubbo、RocketMQ 这些中间件的作者,就是最高水平的积木搭手。他们在道格·李提供的基础上做了大量二次定制:继承 ThreadPoolExecutor 改写拒绝策略、用 ConcurrentHashMap 存储单例对象、用 BlockingQueue 实现异步日志缓冲。 翻开这些中间件的源码,你会发现:标准 JUC 组件很少被直接使用,几乎都被继承或组合包装。这不是因为标准组件不够好,而是因为每个中间件的场景都有自己的约束——Tomcat 的线程池需要在队列满时反过来创建线程(而不是拒绝),Netty 用 NioEventLoopGroup 把线程池拆成了事件循环。 本篇从源码层面逐一拆解道格·李的 JUC 积木如何在中间件中被定制、组合和落地。覆盖的中间件和对应的 JUC 组件如下: flowchart LR classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe,font-weight:bold; classDef branch fill:#2d1a05,stroke:#f59e0b,stroke-width:2px,color:#fde68a,font-weight:bold; classDef leaf fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; ROOT[JUC 在中间件中的应用全景] ROOT --> THREAD["线程池 ThreadPoolExecutor"] THREAD --> T1["Tomcat: 请求处理线程池\n自定义 TaskQueue 配合拒绝策略"] THREAD --> T2["Netty: NioEventLoopGroup\nSingleThreadEventExecutor 模型"] THREAD --> T3["Dubbo: 多种线程池策略\nFixed/Cached/Limited/Eager"] THREAD --> T4["RocketMQ: Broker 线程池组\nSendMessage/PullMessage 等"] ROOT --> MAP["ConcurrentHashMap"] MAP --> M1["Spring IOC: singletonObjects\n所有单例 Bean 的存储容器"] MAP --> M2["Netty: DefaultChannelHandlerContext\nChannel 属性存储"] MAP --> M3["Tomcat: Servlet 映射表\nURL → Servlet 的路由缓存"] ROOT --> QUEUE["BlockingQueue"] QUEUE --> Q1["Logback: AsyncAppender\nArrayBlockingQueue 异步写日志"] QUEUE --> Q2["Tomcat: TaskQueue\n继承 LinkedBlockingQueue"] QUEUE --> Q3["Disruptor: RingBuffer\n虽非JUC但思想同源"] ROOT --> LIST["CopyOnWriteArrayList"] LIST --> L1["Tomcat: Session 监听器列表\n遍历时无需加锁"] LIST --> L2["Spring: ApplicationListener 集合\n事件多播时安全迭代"] class ROOT root; class THREAD,MAP,QUEUE,LIST branch; class T1,T2,T3,T4,M1,M2,M3,Q1,Q2,Q3,L1,L2 leaf; class T1,T2,M1,Q1,L1 highlight; 🏊 线程池在中间件中的应用 🏊 Tomcat:请求处理的线程池引擎 Tomcat 处理 HTTP 请求的核心是一个定制化的 ThreadPoolExecutor 。它没有直接用 JDK 的标准实现,而是继承了 ThreadPoolExecutor 并重写了其中的关键行为。 ...

十月 2, 2022 · 9 分钟 · 1834 字 · yaomingye

Spring Boot 日志

Spring Boot 日志:打点位置、框架选型与线上排查全解析 🐛 问题切入:一段没有日志的代码 下面是一个新手开发者写的 Spring Boot 订单服务: @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public Result<Order> createOrder(@RequestBody CreateOrderRequest req) { Order order = orderService.createOrder(req); return Result.success(order); } } @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private InventoryService inventoryService; @Transactional public Order createOrder(CreateOrderRequest req) { // 扣减库存 boolean deducted = inventoryService.deduct(req.getProductId(), req.getQuantity()); if (!deducted) { throw new BusinessException("库存不足"); } // 创建订单 Order order = new Order(); order.setUserId(req.getUserId()); order.setAmount(req.getAmount()); orderMapper.insert(order); return order; } } 某天线上出现了一个问题:用户投诉"我付了钱但订单没创建成功"。后端同学打开服务器,面对空荡荡的日志文件(只有 Spring Boot 默认的启动 banner),完全不知道从哪里下手。 ...

九月 30, 2022 · 13 分钟 · 2638 字 · yaomingye

云服务器选型实战

☁️ 云服务器选型实战:带宽、CPU、内存容量估算方法论 —— 以阿里云 ECS 为例 📌 一、问题切入:新项目上云,ECS 实例怎么选? 小张接到一个新项目——做一个面向 C 端用户的电商小程序后端,预计日均 UV 5 万,高峰期 QPS(每秒请求数)约 500 📊。技术栈是 Spring Boot + MySQL + Redis,全部部署在阿里云 ECS 上。 他打开阿里云 ECS 购买页面,面对几十种实例规格、上百个配置组合: ecs.g7.large 2vCPU 8GB 最高 10Gbps ecs.c7.xlarge 4vCPU 8GB 最高 12.5Gbps ecs.r7.large 2vCPU 16GB 最高 10Gbps ecs.g7.xlarge 4vCPU 16GB 最高 12.5Gbps ... 选低了——大促时服务崩掉 💥,用户投诉;选高了——老板看账单时脸色不好 😤。 服务器选型的本质是对 三个核心维度 的估算: 带宽(网络吞吐) 、 CPU(计算能力) 、 内存(数据缓存空间) 。三个维度相互独立又彼此制约,高估任何一个都是浪费 💸,低估任何一个都是事故 🚨。本文将给出每个维度的 可量化估算公式 ,结合阿里云 ECS 的具体实例规格,形成一套可复用的选型标准。 🔍 二、估算前置:三个维度的关系 选型之前,先明确三个维度分别决定什么: ...

九月 29, 2022 · 10 分钟 · 1926 字 · yaomingye

API 响应封装

API 响应封装:统一返回格式、全局自动包装与异常处理全解析 🤔 1. 问题切入:一个没有封装的 Controller 是怎样的? 在开始讲解之前,先看一段没有做任何统一封装的 Controller 代码: @RestController @RequestMapping("/api/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/findById") public ProductEntity findById(Long id) { ProductEntity product = productService.findById(id); if (product == null) { // 直接返回 null,前端收到空响应体,不知道发生了什么 return null; } return product; } @PostMapping("/insert") public String insert(@RequestBody ProductEntity product) { try { productService.insert(product); return "success"; // 字符串硬编码,前后端契约不统一 } catch (Exception e) { return e.getMessage(); // 把异常栈暴露给前端,安全风险 } } } 这段代码暴露了三个问题: ...

九月 26, 2022 · 10 分钟 · 2010 字 · yaomingye

Spring MVC 常用注解

Spring MVC 常用注解:企业级全场景用法与实战指南 🤔 1. 问题切入:一个订单查询接口 假设你在开发一个电商系统的订单查询接口,需要实现以下需求: 通过订单 ID 查询订单详情 支持按状态、时间范围过滤订单列表 接收 JSON 请求体来创建订单 处理参数校验失败时的错误返回 统一处理各类异常 以下是一个典型的 Spring MVC Controller 初版实现: @RestController @RequestMapping("/api/orders") public class OrderController { @GetMapping("/{id}") public Result<Order> getOrder(@PathVariable Long id) { // 查询订单 } @GetMapping public Result<Page<Order>> listOrders( @RequestParam(required = false) String status, @RequestParam(required = false) @DateTimeFormat(iso = DATE) LocalDate startDate, @RequestParam(required = false) @DateTimeFormat(iso = DATE) LocalDate endDate, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size) { // 分页查询 } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Result<Order> createOrder(@Validated @RequestBody CreateOrderRequest request) { // 创建订单 } } 短短几行代码用到了 10+ 个注解。这些注解各自承担什么职责?组合使用时有什么坑?在企业级项目中应该如何规范使用?这篇博客将系统性地回答这些问题。 ...

九月 24, 2022 · 12 分钟 · 2520 字 · yaomingye
Cat Radio