# mini-sync **Repository Path**: nocjb/mini-sync ## Basic Information - **Project Name**: mini-sync - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-22 - **Last Updated**: 2026-04-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mini-sync:从 CRUD 到设计模式,一个下午的成长 > 这是一个我用来学习 Spring Boot + 设计模式的小项目。从一个简单的 CRUD 开始,一步步加上策略模式、工厂模式、模板方法、分布式锁,最终复刻了一个生产级数据同步框架的骨架。 > > 如果你也是只会 CRUD 的新手,希望这篇文章能让你看到:设计模式没有想象中那么难。 ## 起点:只会 CRUD 的我 我目前的技术水平大概是这样的: - Java 基础:会写类、接口、List/Map - Spring:知道 `@Component`、`@Service`,用过 `@Autowired` - 数据库:会基础 CRUD 然后公司有个同事写了一个数据同步模块,我看了代码,一脸懵。什么 Handler、Factory、分布式锁……感觉全是"高级操作"。 于是我决定:从零开始,自己搭一个小项目,一步步学到懂为止。 ## 阶段 1:热身 CRUD 第一步很简单——建一个 Spring Boot 项目,写一个同步任务的 CRUD。 ```java // 实体类 @Entity @Table(name = "sync_task") public class SyncTask { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String taskType; // MEMBER、PRODUCT 等 private String status; // PENDING、SUCCESS、FAILED } // Repository public interface SyncTaskRepository extends JpaRepository {} // Service @Service public class SyncTaskService { private final SyncTaskRepository repo; public SyncTaskService(SyncTaskRepository repo) { this.repo = repo; } public SyncTask create(String name, String taskType) { return repo.save(new SyncTask(name, taskType, "PENDING")); } } // Controller @RestController @RequestMapping("/api/sync-task") public class SyncTaskController { @PostMapping public SyncTask create(@RequestBody CreateRequest req) { ... } @GetMapping public List list() { ... } } ``` 这部分不难,都是我本来就会的。但它教会了我几个新东西: - `record` 可以替代 DTO 类,一行搞定 - 构造器注入比 `@Autowired` 字段注入更专业 - `Collectors.toMap()` + `Function.identity()` 比 for 循环优雅 ## 阶段 2:策略模式——消灭 if-else 老板说:"MEMBER 类型调会员接口同步,PRODUCT 类型调商品接口同步。" 我的第一反应是这样的: ```java // ❌ 新手写法 public String execute(SyncTask task) { if ("MEMBER".equals(task.getTaskType())) { return "同步了 50 个会员"; } else if ("PRODUCT".equals(task.getTaskType())) { return "同步了 30 个商品"; } throw new RuntimeException("未知的任务类型"); } ``` 看起来没问题?但如果老板再加 10 种类型呢?这个方法就变成 200 行的怪物了。 **策略模式的解法:** 把"每种类型怎么执行"抽成一个接口,各自实现。 ```java // 接口:定规矩 public interface SyncHandler { String getTaskType(); String execute(SyncTask task); } // 会员同步的实现 @Component public class MemberSyncHandler implements SyncHandler { public String getTaskType() { return "MEMBER"; } public String execute(SyncTask task) { return "同步了 50 个会员"; } } // 商品同步的实现 @Component public class ProductSyncHandler implements SyncHandler { public String getTaskType() { return "PRODUCT"; } public String execute(SyncTask task) { return "同步了 30 个商品"; } } ``` 然后在 Service 里,用 Map 按 taskType 存起来: ```java @Service public class SyncTaskService { private final Map handlerMap; public SyncTaskService(SyncTaskRepository repo, List handlerList) { this.repo = repo; this.handlerMap = handlerList.stream() .collect(Collectors.toMap(SyncHandler::getTaskType, Function.identity())); } public SyncTask execute(SyncTask task) { SyncHandler handler = handlerMap.get(task.getTaskType()); if (handler == null) { throw new IllegalArgumentException("未知的任务类型: " + task.getTaskType()); } handler.execute(task); task.setStatus("SUCCESS"); return repo.save(task); } } ``` **关键点:** Spring 看到 `List` 这个参数,会自动把所有加了 `@Component` 且实现了 `SyncHandler` 的类找出来,塞进这个 List。你什么都不用配置。 **效果:** 以后每加一种新类型,只需要新建一个 Handler 类。Service 完全不用改。 ## 阶段 3:工厂模式——职责分离 阶段 2 的代码能跑了,但 Service 里又管业务逻辑,又管 handler 的收集和查找,做了两件事。 **工厂模式的做法:** 把"找 handler"抽成一个专门的类。 ```java @Component public class SyncHandlerFactory { private final Map handlerMap; public SyncHandlerFactory(List handlerList) { this.handlerMap = handlerList.stream() .collect(Collectors.toMap(SyncHandler::getTaskType, Function.identity())); } public SyncHandler getHandler(String taskType) { SyncHandler handler = handlerMap.get(taskType); if (handler == null) { throw new IllegalArgumentException("未知的任务类型: " + taskType); } return handler; } } ``` Service 变成了这样: ```java @Service public class SyncTaskService { private final SyncTaskRepository repo; private final SyncHandlerFactory factory; public SyncTaskService(SyncTaskRepository repo, SyncHandlerFactory factory) { this.repo = repo; this.factory = factory; } public SyncTask execute(SyncTask task) { String res = factory.getHandler(task.getTaskType()).execute(task); task.setStatus("SUCCESS"); return repo.save(task); } } ``` 看起来只是"提取了一个方法"?没错。但它的价值在于**职责分离**——一个类只做一件事。SOLID 原则里的 **S(单一职责)**。 真实项目中,Factory 以后可能要加日志、加监控、加热加载。如果这些逻辑都在 Service 里,Service 就变成一坨了。 ## 阶段 4:模板方法——公共流程父类管 现在 Service 的 `execute` 方法做了好几件事:找 handler、执行、记日志、改状态。如果以后还要加锁、加游标、加异常处理,几百行看不懂。 **模板方法的做法:** 把固定流程写在抽象类里,子类只填核心逻辑。 ```java @Slf4j public abstract class AbstractSyncHandler implements SyncHandler { @Override public final String execute(SyncTask task) { log.info("开始执行 {} 类型同步", task.getTaskType()); String result = doExecute(task); log.info("执行结果:{}", result); return result; } protected abstract String doExecute(SyncTask task); } ``` Handler 变精简了: ```java @Component public class MemberSyncHandler extends AbstractSyncHandler { public String getTaskType() { return "MEMBER"; } protected String doExecute(SyncTask task) { return "同步了 50 个会员"; } } ``` **三个关键点:** - `final`:子类不能覆盖 `execute`,保证流程不被打破 - `abstract`:子类必须实现 `doExecute`,填入自己的逻辑 - `protected`:只有子类能调用,外部不能直接调 它们的关系是这样: ``` SyncHandler(接口:定规矩) ↑ implements AbstractSyncHandler(抽象类:定流程) ↑ extends ↑ extends MemberSyncHandler ProductSyncHandler ``` Factory 注入 `List` 时,Spring 找到的是 `MemberSyncHandler` 和 `ProductSyncHandler`(它们通过继承链仍然是 SyncHandler)。Factory 一行都不用改。 ## 阶段 5:分布式锁——防并发 最后一个问题:线上多台服务器,XXL-Job 定时触发和用户手动触发可能同时命中同一个任务。同一个任务同一时刻只能有一个在执行。 **解法:Redisson 分布式锁。** ```java @Slf4j @Service public class SyncTaskService { private final SyncHandlerFactory factory; private final RedissonClient redissonClient; public SyncTask execute(SyncTask task) { String key = "sync_task_" + task.getId(); RLock lock = redissonClient.getLock(key); try { boolean acquired = lock.tryLock(0, 30, TimeUnit.SECONDS); if (acquired) { String res = factory.getHandler(task.getTaskType()).execute(task); task.setStatus("SUCCESS"); return repo.save(task); } else { log.info("任务正在执行中"); return null; } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } } ``` **关键点:** - `tryLock(0, 30, SECONDS)`:等 0 秒(拿不到就算了),最多锁 30 秒(防死锁) - `isHeldByCurrentThread()`:释放前先判断,没拿到锁不能 unlock - `finally` 里释放锁,保证即使异常也能释放 **测试结果:** 在 MemberSyncHandler 里加了 5 秒 sleep 模拟耗时,然后快速连续触发两次—— ``` 17:24:26 开始执行 MEMBER 类型同步 ← 第 1 个请求拿到锁 17:24:27 任务正在执行中 ← 第 2 个请求没拿到锁,1 秒就被拦住了 17:24:31 执行结果:同步了 50 个会员 ← 第 1 个请求执行完毕 ``` ## 最终架构 ``` controller/ SyncTaskController.java ← 接收请求 service/ SyncTaskService.java ← 业务逻辑 + 分布式锁 handler/ SyncHandler.java ← 接口:定规矩 AbstractSyncHandler.java ← 抽象类:定流程(模板方法) MemberSyncHandler.java ← 具体实现 ProductSyncHandler.java ← 具体实现 SyncHandlerFactory.java ← 工厂:找 handler config/ RedissonConfig.java ← Redis 配置 entity/ SyncTask.java ← 数据库实体 repository/ SyncTaskRepository.java ← 数据访问 ``` ## 对应到真实项目 回头看同事写的数据同步模块,跟我这个小项目一一对应: | 我的 mini-sync | 同事的 SIC 模块 | |---|---| | `SyncHandler` 接口 | `SicSyncHandler` 接口 | | `MemberSyncHandler` | `MemberSyncHandler`(目前是空壳) | | `SyncHandlerFactory` | `SicSyncHandlerFactory` | | `AbstractSyncHandler` 的流程 | `SicSyncExecutorService` 的执行流程 | | Redisson 锁 | 一样的 Redisson 锁 | 一模一样的思路,只是同事的多了游标、日志表、多租户这些生产级的东西。 ## 写在最后 我写这个项目之前,看到设计模式就觉得"这是大佬才用的东西"。但自己动手从零搭了一遍之后发现: > 设计模式不是"高级操作",而是"遇到痛点后的自然解法"。 - if-else 写多了 → 策略模式 - 一个类做两件事 → 工厂模式(单一职责) - 公共流程重复了 → 模板方法 - 多节点并发冲突 → 分布式锁 每个模式单独拿出来都不难,难的是把它们组合在一起。而这个组合的能力,只能通过动手写来获得。 --- *如果你也在学 Spring Boot,欢迎 clone 这个项目跑一下。每个阶段都有完整的代码,你可以从阶段 1 开始,一步步看到代码是怎么演进的。*