升级Spring Boot 3后使用DefaultLockRepository遇空指针错误
问题背景
将遗留代码从Spring Boot v2.7.1升级到v3.1.0后,使用JdbcLockRegistry时出现以下错误:
org.springframework.dao.CannotAcquireLockException: Failed to lock mutex at b02643f1-227e-4893-9260-cdbi18x6f377 ... Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.transaction.support.TransactionTemplate.execute(org.springframework.transaction.support.TransactionCallback)" because "this.serializableTransactionTemplate" is null at org.springframework.integration.jdbc.lock.DefaultLockRepository.acquire(DefaultLockRepository.java:343) at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.doLock(JdbcLockRegistry.java:272) at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.tryLock(JdbcLockRegistry.java:253) ... 73 more
原代码中JdbcLockRegistry的初始化方式:
private JdbcLockRegistry getJdbcLockRegistry() { final var dataSource = getDatasource(); final var defaultLockRepository = new DefaultLockRepository(dataSource); defaultLockRepository.setPrefix(getSchema()); defaultLockRepository.afterPropertiesSet(); return new JdbcLockRegistry(defaultLockRepository); }
升级后DefaultLockRepository的变化:
- 不再标注
@Repository注解 - 新增多个需通过setter配置的字段:
private ApplicationContext applicationContext;private PlatformTransactionManager transactionManager;private TransactionTemplate defaultTransactionTemplate;private TransactionTemplate readOnlyTransactionTemplate;private TransactionTemplate serializableTransactionTemplate;
尝试的新配置:
@Autowired ApplicationContext context; private JdbcLockRegistry getJdbcLockRegistry() { final var dataSource = getDatasource(); final var defaultLockRepository = new DefaultLockRepository(dataSource); defaultLockRepository.setApplicationContext(context); defaultLockRepository.setPrefix(getSchema()); defaultLockRepository.afterSingletonsInstantiated(); defaultLockRepository.afterPropertiesSet(); return new JdbcLockRegistry(defaultLockRepository); }
请问这种配置是否有效?为何需要手动注入ApplicationContext?
解答
1. 当前配置的有效性
你的配置是有效的,测试运行正常已经验证了这一点。手动注入ApplicationContext后,DefaultLockRepository会在afterSingletonsInstantiated()方法中自动从容器中获取PlatformTransactionManager,并基于它初始化所有需要的TransactionTemplate(包括报错中缺失的serializableTransactionTemplate),从而解决了空指针问题。
2. 为何需要手动注入ApplicationContext
Spring Integration在升级后对DefaultLockRepository做了设计调整:
- 移除
@Repository注解意味着它不再是Spring自动管理的Bean,Spring不会自动为它注入依赖。 - 该类需要
PlatformTransactionManager来创建事务模板,而通过注入ApplicationContext,它可以自行从容器中查找并获取这个Bean,无需你手动传入事务管理器。
这种设计的目的是提升灵活性:你可以在非Spring容器环境中使用DefaultLockRepository,只要手动传入必要的依赖;但在Spring环境中,就需要你主动注入ApplicationContext或直接传入事务管理器。
3. 更规范的配置方式
更符合Spring Bean管理规范的做法是将DefaultLockRepository和JdbcLockRegistry声明为Spring Bean,让容器处理它们的生命周期和依赖注入:
方式一:通过ApplicationContext注入
@Bean public DefaultLockRepository defaultLockRepository(DataSource dataSource, ApplicationContext context) { DefaultLockRepository repository = new DefaultLockRepository(dataSource); repository.setPrefix(getSchema()); repository.setApplicationContext(context); return repository; } @Bean public JdbcLockRegistry jdbcLockRegistry(DefaultLockRepository defaultLockRepository) { return new JdbcLockRegistry(defaultLockRepository); }
Spring会自动调用afterPropertiesSet()和afterSingletonsInstantiated()方法(因为DefaultLockRepository实现了InitializingBean和SmartInitializingSingleton接口),无需手动调用。
方式二:直接注入PlatformTransactionManager
如果你容器中存在默认的PlatformTransactionManager Bean,可以直接注入它,避免依赖ApplicationContext:
@Bean public DefaultLockRepository defaultLockRepository(DataSource dataSource, PlatformTransactionManager transactionManager) { DefaultLockRepository repository = new DefaultLockRepository(dataSource); repository.setPrefix(getSchema()); repository.setTransactionManager(transactionManager); return repository; } @Bean public JdbcLockRegistry jdbcLockRegistry(DefaultLockRepository defaultLockRepository) { return new JdbcLockRegistry(defaultLockRepository); }
这种方式更清晰,直接依赖所需的事务管理器,减少对容器上下文的依赖。
内容的提问来源于stack exchange,提问作者Wumba

