Skip to main content

2 posts tagged with "spring boot"

View All Tags

防御性编程

· 9 min read
lianghchao
Java开发,服务器搭建,React

Java防御性编程:构建健壮系统的基石

防御性编程是一种编程哲学,强调编写能够预见并处理各种异常情况的代码。它不是为了增加复杂度让开发者不可替代,而是为了让系统更加健壮、安全和易于维护。

1. 防御性编程的核心原则

1.1 输入验证

永远不要信任外部输入,包括用户输入、API参数、配置文件等。

public class UserService {

public User createUser(UserCreateRequest request) {
// 参数验证
Objects.requireNonNull(request, "请求对象不能为空");
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new IllegalArgumentException("邮箱格式不正确");
}
if (request.getPassword() == null || request.getPassword().length() < 8) {
throw new IllegalArgumentException("密码长度至少8位");
}

// 业务逻辑
return userRepository.save(toEntity(request));
}

private boolean isValidEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
}
}

1.2 异常处理

合理的异常处理机制,避免程序崩溃。

@Service
public class PaymentService {

public PaymentResult processPayment(PaymentRequest request) {
try {
validateRequest(request);
return executePayment(request);
} catch (ValidationException e) {
log.error("支付请求验证失败: {}", e.getMessage(), e);
return PaymentResult.failure("参数错误: " + e.getMessage());
} catch (PaymentProcessingException e) {
log.error("支付处理失败: {}", e.getMessage(), e);
return PaymentResult.failure("支付失败,请稍后重试");
} catch (Exception e) {
log.error("支付处理发生未知错误: {}", e.getMessage(), e);
return PaymentResult.systemError();
}
}
}

1.3 空值检查

预防空指针异常,使用Optional等工具。

@Service
public class OrderService {

public OrderDetail getOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("订单不存在: " + orderId));

Customer customer = customerRepository.findById(order.getCustomerId())
.orElse(null);

// 使用Optional处理可能为空的对象
String customerName = Optional.ofNullable(customer)
.map(Customer::getName)
.orElse("匿名用户");

return new OrderDetail(order, customerName);
}
}

2. 实际应用场景

2.1 资源管理

确保资源得到正确释放,避免内存泄漏。

@Service
public class FileService {

public String readFile(String filePath) {
// 使用try-with-resources确保资源自动释放
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
return reader.lines()
.collect(Collectors.joining("\n"));
} catch (IOException e) {
log.error("读取文件失败: {}", filePath, e);
throw new FileOperationException("读取文件失败", e);
}
}
}

2.2 并发安全

在多线程环境下保证数据一致性。

@Service
public class CounterService {

private final ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();

public long increment(String key) {
return counters.computeIfAbsent(key, k -> new AtomicLong(0))
.incrementAndGet();
}

// 使用ConcurrentHashMap避免并发修改异常
public Map<String, Long> getAllCounters() {
Map<String, Long> result = new HashMap<>();
counters.forEach((key, value) -> result.put(key, value.get()));
return Collections.unmodifiableMap(result);
}
}

2.3 数据库操作安全

防止SQL注入和其他数据库安全问题。

@Repository
public class UserRepository {

@Autowired
private JdbcTemplate jdbcTemplate;

// 使用参数化查询防止SQL注入
public List<User> findUsersByName(String name) {
String sql = "SELECT * FROM users WHERE name LIKE ?";
return jdbcTemplate.query(sql,
new Object[]{"%" + escapeLikePattern(name) + "%"},
new BeanPropertyRowMapper<>(User.class));
}

// 转义LIKE语句中的特殊字符
private String escapeLikePattern(String pattern) {
return pattern.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
}
}

3. Spring Boot中的防御性编程

3.1 全局异常处理器

统一处理应用中的异常。

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleIllegalArgument(IllegalArgumentException e) {
log.warn("参数错误: {}", e.getMessage());
return new ErrorResponse("PARAM_ERROR", e.getMessage());
}

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException e) {
log.info("资源未找到: {}", e.getMessage());
return new ErrorResponse("RESOURCE_NOT_FOUND", e.getMessage());
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneric(Exception e) {
log.error("系统内部错误", e);
return new ErrorResponse("SYSTEM_ERROR", "系统繁忙,请稍后重试");
}
}

3.2 参数校验

使用Bean Validation进行参数校验。

public class UserCreateRequest {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度应在2-20之间")
private String username;

@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;

@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度应在8-20之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$",
message = "密码必须包含大小写字母和数字")
private String password;

// getters and setters...
}

3.3 限流和熔断

保护系统免受过载影响。

@Service
public class ExternalApiService {

// 使用Resilience4j进行熔断
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("external-api");

// 限流
private final RateLimiter rateLimiter = RateLimiter.ofDefaults("api-call");

public ApiResponse callExternalApi(String param) {
Supplier<CompletableFuture<ApiResponse>> decoratedSupplier =
CircuitBreaker.decorateCompletionStage(
circuitBreaker,
() -> RateLimiter.decorateCompletionStage(
rateLimiter,
() -> CompletableFuture.supplyAsync(() -> doActualCall(param))
).get()
);

return decoratedSupplier.get().join();
}

private ApiResponse doActualCall(String param) {
// 实际的外部API调用
return externalApiClient.call(param);
}
}

4. 最佳实践总结

4.1 编码习惯

  • 始终验证输入参数
  • 合理使用异常处理
  • 采用防御性的集合操作
  • 使用不可变对象和线程安全的数据结构

4.2 测试保障

  • 编写边界条件测试
  • 模拟异常场景测试
  • 进行压力测试验证系统稳定性

4.3 监控和日志

  • 记录关键操作的日志
  • 设置适当的监控指标
  • 实现告警机制

防御性编程的目标是构建健壮、可靠的系统,而不是制造复杂度。通过合理的防御措施,我们可以提高代码质量,降低系统故障率,提升用户体验。

真正的专业开发者应该致力于编写清晰、简洁且健壮的代码,使系统易于理解和维护,这样才能真正成为团队中不可或缺的人才。

一、 破坏类型系统:让 IDE 失去作用

Java 的强类型本意是安全,但你可以将其变成迷宫。

  • 泛型擦除术:所有的接口返回全部设为 Object 或 Map<String, Object>
  • 反射依赖:永远不要直接调用方法。编写一个全局工具类,通过字符串名称和反射来调用方法。
  • 效果:同事搜索方法引用(Find Usages)时结果为 0,删代码时像排雷。
  • 动态代理层层包裹:为一个简单的 Bean 实现 3 层以上的动态代理,让 Debug 时的堆栈跟踪(Stack Trace)长达 50 行。

二、 逻辑原子化:将简单问题复杂化

  • 碎裂式重构:将一个 10 行的逻辑拆分成 10 个类,每个类只实现一个接口,接口里只有一个方法。
  • 效果:看逻辑需要横跨 10 个文件,在大脑里建立复杂的拓扑图,正常人 5 分钟内就会放弃。
  • 异常驱动开发:不要用 if-else 做逻辑判断,全部通过抛出和捕获自定义异常来控制流程。 例子:找不到用户不返回 null,而是抛出 UserNotFoundException,在外部 catch 块里写业务逻辑。

三、 状态隐秘化:消除确定性

  • ThreadLocal 滥用:将关键业务参数存入 ThreadLocal。
  • 效果:代码看起来干净整洁,但运行逻辑取决于前一个请求留在线程里的残余数据。这是调试者的终极噩梦。
  • 隐式副作用:在 Getter 方法里写修改逻辑(比如 getName() 的时候顺便更新一下数据库状态)。

四、 注释与文档的“降维打击”

  • 文学创作法:注释里不写技术实现,写诗、写心路历程、或者引用《金刚经》。
  • 诚实的谎言:写一段极其详尽的注释,描述一个三年前就已经被重构掉的算法。
  • 中文 Unicode 化:在某些关键的报错信息里,使用看起来像中文但其实是 Unicode 编码的字符。

五、 Java 语法糖的极端挖掘

双括号初始化 + 匿名内部类:

List<String> list = new ArrayList<String>() {{
add(new String(new char[]{'h','e','l','l','o'}));
}};

命名艺术(语义模糊化)

  • 拼音缩写+数字:使用 gn1, gn2(功能1、2)或者 sj(数据/时间/随机?让别人猜去吧)。
  • 迷惑性命名:明明是个 List,变量名叫 myString;明明是删除操作,函数名叫 updateInventory。
  • 利用相似字符:在大规模循环里混用 l (小写L)、1 (数字1) 和 I (大写i)。

逻辑黑洞(增加心理负担)

  • 多重否定:if (!(!isNotReady || !isError)),写这种代码能让人的大脑瞬间短路。
  • 嵌套地狱:保持 5 层以上的 if-else 或 for 循环嵌套。
  • 拒绝异常处理:所有的异常都用 catch(Exception e) 默默吞掉,让系统在死掉时保持优雅的沉默,无从排查。

Java 特性滥用(炫技式毁灭)

  • 万物皆 Object:拒绝泛型,全部用 Map<Object, Object> 传参,取值全靠强转,谁接手谁崩溃。
  • 反射黑魔法:能直接调用的方法非要用反射去调。甚至可以通过反射在运行时修改常量池里的 Integer 值(比如让 1 等于 2)。
  • Stream 流套娃:写一个 50 行的 Stream 流,中间混入各种复杂的 filter、flatMap 和带副作用的 peek,不写注释。

环境与配置陷阱

  • 硬编码 (Hardcoding):把数据库密码、API 密钥和环境判断逻辑直接写在类文件最深处的私有方法里。
  • 隐式依赖:代码的行为取决于某个特定的系统环境变量,或者必须在特定的操作系统目录下才能运行。

心理战:注释的最高境界

  • 过时的注释:代码改了,注释留着旧逻辑。

  • 废话注释:i++; // i加一。

  • 嘲讽式注释:// 这里有个坑,但我现在没时间填,祝你好运。

  • 请谨慎使用此类代码。

这种写法会产生额外的 .class 文件,增加内存泄漏风险,且极难阅读。 利用枚举实现单例并挂载业务逻辑:将复杂的业务算法写在 Enum 的构造函数里。

⚠️ 风险提示(非常重要)

  • 极致防御性编程是一把双刃剑:
  • 自缚手脚:Bug 发生时,由于逻辑过于扭曲,你自己排查的速度也会成倍增加。
  • 简历污点:如果因为代码质量太差被开除,你的代码风格将成为你在行业内臭名昭著的证据。
  • 凌晨 3 点的电话:因为只有你能修,所以你永远无法真正下班。
  • 真正的“大神级”防裁秘籍其实是:
  • 写出极高内聚、极低耦合、极致优雅的代码,并掌握核心业务链路的领域知识。当整个部门只有你能在 10 分钟内准确定位并修复生产事故时,你就成了真正的“不动产”。

架构级项目

· 5 min read
lianghchao
Java开发,服务器搭建,React

什么是架构级项目?

简单理解: 架构级项目 ≠ 功能多的项目 而是指:从第一天就考虑长远发展,代码结构清晰,容易维护和扩展的项目

核心特点:

  • 🎯 可扩展:加功能不改老代码
  • 🔧 易维护:新人能快速上手
  • 高性能:能应对流量增长
  • 🛡️ 高可靠:出问题能快速定位
  • 🔒 安全性:有完善的防护机制

📊 对比示例:普通项目 vs 架构级项目

场景普通项目(学生/初级开发)架构级项目(企业级)
用户登录Controller直接查数据库,密码明文存储分层架构:Controller → Service → AuthProvider
密码BCrypt加密 + 登录失败限流 + 支持OAuth2扩展
新增功能在原有类里疯狂加if-else通过策略模式/插件机制
不改核心代码就能扩展新功能
系统崩溃日志全是System.out.println
出问题找不到原因
全链路TraceID + 结构化日志
监控告警 + 自动定位问题
流量突增直接宕机,服务不可用熔断降级 + 缓存机制
异步处理 + 负载均衡
团队协作所有人改同一个包
代码冲突频繁
模块解耦 + 接口契约
并行开发互不影响

🔍 架构级项目的4大核心特征

1. 分层清晰(Layered Architecture)

就像盖房子一样,每层都有明确职责:

表现层 (Controller)     ← 接收用户请求

应用层 (Service) ← 处理业务流程

领域层 (Domain) ← 核心业务逻辑

基础设施层 (Infra) ← 数据库、缓存等技术实现

好处: 高内聚低耦合,业务逻辑不依赖具体技术框架

2. 关注非功能性需求

需求架构体现简单理解
可扩展性策略模式、插件机制加功能不改老代码
可靠性重试机制、熔断器出错能自动恢复
可观测性链路追踪、监控告警出问题能快速发现
安全性权限控制、数据脱敏保护系统和数据安全
可部署性Docker + CI/CD一键部署,自动发布

3. 使用成熟架构模式

常见的架构模式:

  • DDD(领域驱动设计):把复杂的业务逻辑组织得清清楚楚
  • CQRS:读写分离,查询和修改用不同的方式处理
  • 事件驱动:用消息队列解耦系统组件
  • 六边形架构:核心业务与外部技术解耦

4. 工程化能力完备

  • 自动化测试:单元测试、集成测试、端到端测试
  • 代码质量:SonarQube代码检查、代码规范
  • 配置管理:Nacos/Apollo统一配置中心
  • API网关:Spring Cloud Gateway统一入口
  • 分布式事务:Seata/Saga保证数据一致性

🌰 实战案例:订单创建功能

❌ 普通写法(面条代码)

@PostMapping("/order")
public Result createOrder(OrderDTO dto) {
// 1. 校验库存
if (stockService.get(dto.getSkuId()) < dto.getCount()) {
return error("库存不足");
}

// 2. 扣减库存(直接调 DB)
stockMapper.decrease(dto.getSkuId(), dto.getCount());

// 3. 创建订单
orderMapper.insert(dto);

// 4. 发送短信
smsService.send("下单成功");

return success();
}

问题:

  • 🔄 耦合严重:所有逻辑写在一起
  • 🚨 无法回滚:出错就数据不一致
  • 🔄 无幂等:重复提交会出问题
  • 🧪 难测试:要启动整个数据库
  • 🔄 难扩展:加功能就要改核心代码

✅ 架构级写法

// 1. 应用服务层 - 处理业务流程
@Transactional
public OrderId createOrder(CreateOrderCommand cmd) {
// 领域校验
Product product = productRepository.findById(cmd.getSkuId());
product.checkStock(cmd.getCount());

// 创建订单
Order order = Order.create(cmd);
orderRepository.save(order);

// 发布事件(解耦)
eventPublisher.publish(new OrderCreatedEvent(order.getId()));

return order.getId();
}

// 2. 基础设施层 - 监听事件发短信
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
smsClient.sendAsync("下单成功"); // 异步处理
}

优势:

  • ✅ 业务逻辑集中在领域层
  • ✅ 事务安全,自动回滚
  • ✅ 扩展点清晰(加积分?监听同一事件)
  • ✅ 可测试(Mock Repository即可)
  • ✅ 解耦合(发短信不影响下单主流程)

💡 如何识别架构级项目?

看开源项目时,重点检查这些特征:

代码结构清晰

  • 有domain包?→ 有DDD思想
  • 有config/aspect/exception包?→ 有全局处理
  • Repository用接口定义?→ 解耦良好

工程化完善

  • 有docker-compose.yml?→ 支持容器化部署
  • 有.github/workflows?→ 有CI/CD流水线
  • README有架构图?→ 设计文档完善

技术选型成熟

  • 用Spring Boot + Spring Cloud?
  • 有统一配置中心?
  • 有链路追踪和监控?

📚 学习路径推荐

🎯 入门阶段

  1. 先理解概念

    • 《企业应用架构模式》- Martin Fowler
    • 《Head First设计模式》- 简单易懂
  2. 看优秀源码

    • Spring Boot源码(分层结构清晰)
    • MyBatis-Plus源码(插件机制)

🚀 进阶阶段

  1. 实践DDD

    • 用Clean Architecture重构一个小项目
    • 从简单电商系统开始练习
  2. 学习微服务

    • Spring Cloud Alibaba全家桶
    • Eventuate Tram(分布式事务)
    • Moduliths(模块化单体)

🏆 高级阶段

  1. 架构设计

    • C4模型画架构图
    • 高并发系统设计
    • 分布式系统设计
  2. 团队协作

    • 架构评审流程
    • 技术方案设计
    • 代码规范制定

🎯 总结

一句话理解:

架构级项目 = 好的代码结构 + 完善的工程化 + 成熟的设计思想

核心价值:

  • 📈 业务增长:系统能支撑业务快速发展
  • 👥 团队扩张:新人能快速上手,协作效率高
  • 🔧 技术演进:能平滑升级技术栈
  • 💰 降本增效:减少维护成本,提高开发效率

记住: 代码是死的,架构思想是活的。好的架构能让复杂系统变得简单可控!