Java/Spring
01. 面向微服务的 Spring 基础
这一章补齐 Spring 的底层理解。读者已经会写 Spring MVC/Spring Boot 接口,但如果不理解容器、代理、配置和事务,进入 SOFA、Spring Cloud、金融交易链路后会频繁踩坑。
1. Spring 到底解决什么问题
Spring 的核心不是 @RestController,而是一个应用装配和运行时扩展体系。
在传统 Java 代码中,你可能这样写:
public class TransferService {
private final AccountRepository accountRepository = new JdbcAccountRepository();
private final RiskClient riskClient = new HttpRiskClient();
}
问题是:
- 业务类自己创建依赖,无法替换实现。
- 测试时很难注入 fake/mock。
- 日志、事务、鉴权、指标等横切逻辑会散落在业务代码中。
- 不同环境的配置要改代码。
Spring 的做法是:
- 对象由容器创建和管理。
- 依赖由容器注入。
- 横切逻辑由 AOP、Filter、Interceptor、BeanPostProcessor 等机制统一挂载。
- 配置从代码中分离出来。
用一句工程化的话概括:Spring 把“对象怎么创建、依赖怎么连接、运行期怎么增强”从业务代码里拿出来,让业务代码更专注于业务规则。
2. IoC/DI:容器与依赖注入
2.1 Bean 是什么
Bean 是被 Spring 容器管理的对象。一个类不是天然的 Bean,只有被容器识别、实例化、注册之后才是 Bean。
常见注册方式:
@Component
public class PaymentService {}
@Configuration
public class ClientConfig {
@Bean
RiskClient riskClient(RestTemplateBuilder builder) {
return new RiskClient(builder.build());
}
}
@Service
public class TransferService {
private final AccountRepository accountRepository;
public TransferService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
}
建议优先使用构造器注入。原因:
- 依赖不可变,字段可以
final。 - 单元测试时可以直接 new。
- 启动阶段就能发现缺少依赖。
- 比字段注入更容易理解依赖关系。
2.2 容器装配流程
简化理解:
- 扫描配置类、组件注解和自动配置。
- 生成 BeanDefinition,也就是“如何创建 Bean”的元数据。
- 处理条件装配、Profile、属性绑定。
- 实例化 Bean。
- 注入依赖。
- 执行初始化回调和 BeanPostProcessor。
- 应用启动完成,Bean 对外提供能力。
理解这个流程后,很多问题会变清楚:
@Value为什么读不到某个配置。@Autowired为什么出现多个候选 Bean。@Transactional为什么在同类内部调用时失效。@ConfigurationProperties为什么没绑定成功。
3. Spring Boot:不是“新框架”,而是约定和自动配置
Spring Boot 主要提供:
- starter 依赖组合。
- 自动配置。
- 外部化配置。
- 嵌入式 Web 容器。
- Actuator 运维端点。
- 测试支持。
3.1 Starter 的意义
spring-boot-starter-web 不是一个神秘框架,而是一组常用依赖和自动配置入口。它把 Web 开发常用的 Spring MVC、Jackson、嵌入式容器等组合起来。
金融微服务中,starter 的价值在于统一团队默认能力。例如可以做内部 starter:
- 统一日志 traceId。
- 统一异常响应。
- 统一审计拦截器。
- 统一鉴权客户端。
- 统一 metrics tag。
- 统一 Feign/RPC 超时。
但 starter 不能变成“黑盒魔法”。每个 starter 都应该有说明文档、默认值、覆盖方式和测试。
3.2 自动配置的核心模型
自动配置大致可以理解为:
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
DataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties);
}
}
关键点:
- 条件装配不是强制装配。
- 用户自己定义 Bean 时,自动配置通常会退让。
- 自动配置依赖 classpath 和配置属性。
- 排查问题时要看条件是否命中。
学习建议:
- 先会用 starter。
- 再学
@ConditionalOnClass、@ConditionalOnMissingBean、@ConfigurationProperties。 - 最后写一个内部 starter,把日志、审计或统一响应封装进去。
4. 配置体系:微服务的运行时入口
微服务最大的区别之一是同一份代码会运行在本地、测试、预发、生产、灰度等环境。配置必须外置。
常见配置来源:
application.ymlapplication-dev.yml- 环境变量
- 命令行参数
- 配置中心,例如 Spring Cloud Config、Nacos
- Kubernetes ConfigMap/Secret
推荐模式:
server:
port: ${SERVER_PORT:8080}
payment:
risk:
endpoint: ${RISK_ENDPOINT:http://localhost:8081}
timeout-ms: ${RISK_TIMEOUT_MS:800}
@ConfigurationProperties(prefix = "payment.risk")
public class RiskProperties {
private URI endpoint;
private Duration timeout = Duration.ofMillis(800);
}
不要把配置散落在 @Value("${...}") 中。结构化配置的好处:
- 可以校验。
- 可以生成元数据。
- 可以在测试中整体替换。
- 可以清晰表达一个外部依赖的全部参数。
5. AOP 与代理:事务、审计、指标的基础
AOP 适合处理横切关注点:
- 事务。
- 方法级审计。
- 访问控制。
- 统一日志。
- 指标计时。
- RPC 过滤器思想中的前后处理。
Spring AOP 通常基于代理。重要限制:
- 同一个类内部
this.method()调用不会经过代理。 private方法无法被代理增强。- final 类/方法在某些代理模式下不能增强。
- 代理增强发生在 Bean 容器管理对象上,自己 new 出来的对象不会生效。
事务失效示例:
@Service
public class TransferService {
public void transfer() {
this.debit(); // 同类内部调用,不经过事务代理
}
@Transactional
public void debit() {
// update account
}
}
解决方式:
- 把事务边界放在外部入口方法上。
- 拆到另一个 Bean。
- 避免把事务注解当作“随手贴”的标记。
6. 事务:先掌握本地事务,再谈分布式事务
本地事务关注一个数据库连接内的原子性。常见写法:
@Transactional
public void createPayment(CreatePaymentCommand command) {
paymentRepository.insert(command.toPayment());
paymentEventRepository.insert(command.toEvent());
}
常见误区:
- 捕获异常后不抛出,事务不会按预期回滚。
- 默认只对 unchecked exception 回滚。
- 事务方法内部调用导致注解不生效。
- 事务里做远程调用,导致锁持有时间不可控。
- 大事务包含批量处理,影响数据库吞吐。
金融微服务建议:
- 本地事务只包住本地数据变更。
- 远程调用放在事务外,或用 Outbox/事务消息。
- 明确幂等键,避免重试导致重复写。
- 每个交易状态转换写业务流水。
7. Actuator 与可观测性
Spring Boot Actuator 提供健康检查、指标等运维入口。生产微服务至少要关注:
/actuator/health:是否可接流量。- readiness/liveness:在 Kubernetes 中区分“能启动”和“能接请求”。
- metrics:请求耗时、错误数、线程池、连接池、JVM。
- info:版本、commit、构建时间。
不要把 Actuator 全量暴露到公网。建议:
- 运维端点走内网。
- 敏感端点加鉴权。
- 自定义健康检查要区分关键依赖和非关键依赖。
- DB 不可用时,支付写接口应拒绝;短信服务不可用时,部分查询接口可能仍可服务。
8. 学习练习
练习 1:解释一个接口从请求到响应的路径
写一个 POST /transfers 接口,然后画出:
- Servlet 容器接收请求。
- Filter 处理 traceId。
- Spring MVC 找到 Controller。
- 参数绑定和校验。
- Service 执行业务。
- Repository 写数据库。
- 事务提交。
- Controller 返回响应。
- 统一异常处理。
- Actuator/Micrometer 记录指标。
练习 2:做一个内部 starter
目标:封装统一响应和 traceId 日志。
要求:
- starter 自动注册一个
OncePerRequestFilter。 - 从请求头读取
X-Request-Id,没有则生成。 - 日志 MDC 写入 requestId。
- 响应头带回
X-Request-Id。 - 提供配置
company.trace.enabled=false可关闭。
练习 3:事务失效实验
构造同类内部调用、异常被吞、checked exception 三种事务失效情况,写测试证明差异。
9. 本章检查清单
- 能解释 IoC 和 DI,不只会说“控制反转”四个字。
- 能解释 Bean 生命周期中配置、实例化、注入、初始化的大致顺序。
- 能说明 Spring Boot starter 和自动配置的关系。
- 能用
@ConfigurationProperties管理一组配置。 - 能解释
@Transactional的代理限制。 - 能解释 Actuator health、metrics 与 Kubernetes probe 的关系。