2. Java核心工程化基础
当项目从“能跑”走向“可维护”,决定质量的不再是某个算法写得多巧,而是代码结构是否稳定、异常是否可控、日志是否可查、配置是否可换。本节围绕四个工程化支点展开:设计原则落地、包结构与分层、异常处理规范、日志与配置统一管理。它们共同决定一个 Java 工程能否被多人长期协作。
一、设计原则在工程中的落地方式
SOLID 原则不是口号,落到代码里通常表现为“依赖方向”和“变化隔离”。以订单创建为例:如果 OrderService 直接 new MySQLOrderRepository(),那么更换存储或增加缓存就必须改业务类,违反开闭原则与依赖倒置。工程中的常见做法是让业务层依赖接口,由外部注入实现:
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(long id);
}
public class OrderService {
private final OrderRepository repository;
private final PaymentGateway payment;
public OrderService(OrderRepository repository, PaymentGateway payment) {
this.repository = repository;
this.payment = payment;
}
public Order create(CreateOrderCmd cmd) {
Order order = Order.create(cmd);
payment.charge(order);
return repository.save(order);
}
}
这里 OrderService 只依赖抽象,构造器注入让依赖关系显式化,也便于测试时替换为内存实现。单一职责则体现在:Order 负责业务不变量校验,OrderRepository 负责持久化,PaymentGateway 负责支付,三者变化原因不同,不应混在一个类里。接口隔离要求接口按调用方需要拆分,而不是做一个“大而全”的 OrderFacade。
二、包结构与分层架构
包结构是架构的第一层可见约束。推荐按“层 + 业务域”组织,而不是按类型堆放。例如:
com.example.order
├── api // 对外接口与 DTO
├── application // 用例编排、事务边界
├── domain // 实体、值对象、领域服务、仓储接口
└── infrastructure // 仓储实现、外部网关、消息、配置
依赖方向必须单向:api → application → domain,infrastructure 实现 domain 定义的接口,由启动模块装配。禁止 domain 反向依赖 infrastructure,否则领域模型会被数据库、HTTP 框架污染。分层不是越多越好,小项目可合并 api 与 application,但“领域不依赖基础设施”这条线应尽量守住。
三、异常处理规范
异常处理的核心是“分类 + 边界”。建议把异常分为三类:业务异常(可预期,如余额不足)、系统异常(不可预期,如数据库连接失败)、参数异常(校验失败)。业务异常应继承自定义基类,携带错误码,由 api 层统一转换为响应;系统异常记录完整堆栈并返回通用错误,避免泄露内部细节。
public class BizException extends RuntimeException {
private final ErrorCode code;
public BizException(ErrorCode code, String message) {
super(message);
this.code = code;
}
public ErrorCode getCode() { return code; }
}
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BizException.class)
public ApiResponse<Void> handleBiz(BizException e) {
log.warn("业务异常 code={} msg={}", e.getCode(), e.getMessage());
return ApiResponse.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleUnknown(Exception e) {
log.error("系统异常", e);
return ApiResponse.fail(ErrorCode.SYSTEM_ERROR, "系统繁忙,请稍后重试");
}
}
规范要点:不要用 catch (Exception e) {} 吞掉异常;不要在循环里逐条打印堆栈;业务异常不打 error 级别,避免污染告警;跨层传递时保留原始异常作为 cause,便于定位根因。
四、日志框架集成与统一配置管理
日志应通过 SLF4J 门面编程,底层绑定 Logback 或 Log4j2,避免业务代码直接依赖具体实现。日志级别使用要克制:DEBUG 用于开发排查,INFO 记录关键状态变更,WARN 记录可恢复异常,ERROR 只留给需要人工介入的问题。推荐使用参数化占位符,避免字符串拼接开销:
log.info("订单创建成功 orderId={} userId={} amount={}", order.getId(), order.getUserId(), order.getAmount());
配置管理方面,应把可变参数外置到配置文件,并通过环境变量或配置中心覆盖。典型做法是 application.yml 提供默认值,敏感信息通过环境变量注入,不同环境用 profile 区分。配置项建议按模块前缀命名,例如 order.timeout-ms、order.retry.max,避免全局散落。启动时对关键配置做校验,缺失或非法值应快速失败,而不是运行到一半才报错。
把设计原则、分层、异常、日志与配置放在一起看,它们其实在回答同一个问题:当需求变化、故障发生、环境切换时,系统是否还能被低成本地理解和修改。工程化基础不是额外负担,而是让后续并发、性能与稳定性优化有立足之地。