Spring Boot 无法启动 Spring State Machine
结论
orderStateMachine 重复定义,是因为两个 Bean 使用了同一个名称:
OrderStateMachine类标注了@Configuration,默认注册为名为orderStateMachine的配置 Bean。@EnableStateMachineFactory(name = "orderStateMachine")又使用相同名称注册StateMachineFactory。
解决方法是分别命名配置类 Bean 和工厂 Bean,并为两个状态机补全泛型参数。不要开启 Bean 覆盖来回避命名冲突。
示例中 initial() 和 end() 的参数也不正确。这两个方法接收 state,而不是 event。例如,ITEM_PICKED 属于 OrderEvent,不能作为 OrderState 传入。
重复定义的原因
Spring 通常根据类名生成默认 Bean 名称,并将类名首字母改为小写:
OrderStateMachine -> orderStateMachine
PaymentStateMachine -> paymentStateMachine
所以下面的配置会让 Spring 注册两个同名 Bean:
@Configuration
@EnableStateMachineFactory(name = "orderStateMachine")
public class OrderStateMachine {
}
这两个 Bean 分别是:
@Configuration 配置类 Bean:orderStateMachine
StateMachineFactory Bean:orderStateMachine
Spring Boot 默认不允许覆盖 Bean 定义,因此应用会在创建上下文时失败。
不建议使用 spring.main.allow-bean-definition-overriding=true。覆盖后,配置类或状态机工厂可能无法使用,也很难直观看出最终注入的是哪个 Bean。
正确配置方式
可以从两处调整:
- 将配置类改名为
OrderStateMachineConfig和PaymentStateMachineConfig。 - 将工厂 Bean 明确命名为
orderStateMachineFactory和paymentStateMachineFactory。
同时补全泛型参数,避免使用原始类型而失去类型检查。
订单状态机
@Configuration
@EnableStateMachineFactory(name = "orderStateMachineFactory")
public class OrderStateMachineConfig
extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(
StateMachineStateConfigurer<OrderState, OrderEvent> states)
throws Exception {
states
.withStates()
.initial(OrderState.ORDER_INITIATED)
.end(OrderState.ORDER_CONFIRMED)
.end(OrderState.ORDER_CANCELLED)
.states(EnumSet.allOf(OrderState.class));
}
@Override
public void configure(
StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(OrderState.ORDER_INITIATED)
.target(OrderState.ORDER_CONFIRMED)
.event(OrderEvent.ITEM_CONFIRMED)
.and()
.withExternal()
.source(OrderState.ORDER_INITIATED)
.target(OrderState.ORDER_CANCELLED)
.event(OrderEvent.ITEM_RETURNED);
}
}
支付状态机
@Configuration
@EnableStateMachineFactory(name = "paymentStateMachineFactory")
public class PaymentStateMachineConfig
extends EnumStateMachineConfigurerAdapter<PaymentState, PaymentEvent> {
@Override
public void configure(
StateMachineStateConfigurer<PaymentState, PaymentEvent> states)
throws Exception {
states
.withStates()
.initial(PaymentState.PAYMENT_INITIATED)
.end(PaymentState.PAYMENT_COLLECTED)
.end(PaymentState.PAYMENT_CANCELLED)
.states(EnumSet.allOf(PaymentState.class));
}
@Override
public void configure(
StateMachineTransitionConfigurer<PaymentState, PaymentEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(PaymentState.PAYMENT_INITIATED)
.target(PaymentState.PAYMENT_COLLECTED)
.event(PaymentEvent.PAYMENT_ID_GENERATED)
.and()
.withExternal()
.source(PaymentState.PAYMENT_INITIATED)
.target(PaymentState.PAYMENT_CANCELLED)
.event(PaymentEvent.PAYMENT_FAILED);
}
}
这里的 transition 只是根据现有枚举整理出的示例映射,实际流转关系还要按照订单和支付流程调整。
在 Service 中注入对应工厂
建议使用构造器注入。@Qualifier 中填写 @EnableStateMachineFactory 声明的工厂 Bean 名称:
@Service
public class StateMachineService {
private final StateMachineFactory<OrderState, OrderEvent>
orderStateMachineFactory;
private final StateMachineFactory<PaymentState, PaymentEvent>
paymentStateMachineFactory;
public StateMachineService(
@Qualifier("orderStateMachineFactory")
StateMachineFactory<OrderState, OrderEvent> orderStateMachineFactory,
@Qualifier("paymentStateMachineFactory")
StateMachineFactory<PaymentState, PaymentEvent> paymentStateMachineFactory) {
this.orderStateMachineFactory = orderStateMachineFactory;
this.paymentStateMachineFactory = paymentStateMachineFactory;
}
}
需要状态机实例时,分别通过对应的工厂创建:
StateMachine<OrderState, OrderEvent> orderMachine =
orderStateMachineFactory.getStateMachine();
StateMachine<PaymentState, PaymentEvent> paymentMachine =
paymentStateMachineFactory.getStateMachine();
如果要按业务对象区分实例,也可以传入机器 ID:
StateMachine<OrderState, OrderEvent> orderMachine =
orderStateMachineFactory.getStateMachine(orderId);
StateMachine<PaymentState, PaymentEvent> paymentMachine =
paymentStateMachineFactory.getStateMachine(paymentId);
注意事项
initial()、state() 和 end() 接收的都是状态类型:
.initial(OrderState.ORDER_INITIATED)
.end(OrderState.ORDER_CONFIRMED)
只有 event() 接收事件类型:
.event(OrderEvent.ITEM_CONFIRMED)
因此,下面这类调用在类型正确的代码中无法通过编译:
.initial(OrderState.ITEM_PICKED)
.end(OrderEvent.ORDER_CONFIRMED)
ITEM_PICKED 属于 OrderEvent,ORDER_CONFIRMED 属于 OrderState。补全泛型后,编译器可以直接发现 state 和 event 混用的问题。
最终应确保这四个名称含义明确:
配置类 Bean:orderStateMachineConfig
工厂 Bean:orderStateMachineFactory
配置类 Bean:paymentStateMachineConfig
工厂 Bean:paymentStateMachineFactory
这样,两个 StateMachineFactory 就能同时存在,@Qualifier 也可以准确选择订单或支付状态机。
备注:内容仅供参考。
