泛型Bean的@PostConstruct初始化异常问题排查求助
我定义了泛型接口PostgresDAO<T>及其实现类PostgresDAOImpl<T>,将该实现类注入到3个Service中,并在@PostConstruct方法内设置typeParameterClass。其中2个Service能返回正确的typeParameterClass,但ClassThreeServiceImpl返回的却是ClassOne的类型而非自身设置的ParameterThree.class。
已添加初始化日志,显示setTypeParameterClass方法中设置的类型是正确的。本地JDK-17 Temurin环境下程序运行正常,但在OpenJDK 11环境下出现该问题。
泛型DAO代码
public interface PostgresDAO<T> { void setTypeParameterClass(Class<T> clazz); Entity findEntityById(Long id); } @Repository @NoArgsConstructor @Getter public class PostgresDAOImpl<T> implements PostgresDAO<T> { private Class<T> typeParameterClass; @PersistenceContext private EntityManager em; @Override public void setTypeParameterClass(Class<T> clazz) { this.typeParameterClass = clazz; } // 其他使用EntityManager的代码 }
三个Service代码
@Service @RequiredArgsConstructor public class ClassOneServiceImpl implements ClassOneService { private final PostgresDAO<ParameterOne> dao; @PostConstruct public void enrichDao() { dao.setTypeParameterClass(ParameterOne.class); } public Class getParameter() { return dao.getTypeParameterClass(); } // 其他代码 } @Service @RequiredArgsConstructor public class ClassTwoServiceImpl implements ClassTwoService { private final PostgresDAO<ParameterTwo> dao; @PostConstruct public void enrichDao() { dao.setTypeParameterClass(ParameterTwo.class); } public Class getParameter() { return dao.getTypeParameterClass(); } // 其他代码 } @Service @RequiredArgsConstructor public class ClassThreeServiceImpl implements ClassThreeService { private final PostgresDAO<ParameterThree> dao; @PostConstruct public void enrichDao() { dao.setTypeParameterClass(ParameterThree.class); } public Class getParameter() { return dao.getTypeParameterClass(); } // 其他代码 }
依赖配置
<!-- Java 11下支持@PostConstruct注解的依赖 --> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
环境信息
- Lombok 1.18.22
- Spring Boot 2.7.0
- OpenJDK 版本 "11.0.4" 2019-07-16 LTS
- OpenJDK Runtime Environment 18.9 (build 11.0.4+11-LTS)
- OpenJDK 64-Bit Server VM 18.9 (build 11.0.4+11-LTS, mixed mode, sharing)
参考配置代码:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class PostgresDAOConfig { @Bean public PostgresDAO<ParameterOne> deceptionDAO() { PostgresDAOImpl<ParameterOne> result = new PostgresDAOImpl<>(); result.setParameterClass(ParameterOne.class); return result; } @Bean public PostgresDAO<ParameterTwo> deceptionDAO() { PostgresDAOImpl<ParameterTwo> result = new PostgresDAOImpl<>(); result.setParameterClass(ParameterTwo.class); return result; } @Bean public PostgresDAO<ParameterThree> deceptionDAO() { PostgresDAOImpl<ParameterThree> result = new PostgresDAOImpl<>(); result.setParameterClass(ParameterThree.class); return result; } }
核心原因:Spring泛型Bean的实例化策略差异
问题本质不是@PostConstruct不工作,而是Spring在不同JDK版本下对泛型DAO实例的复用逻辑不同:
PostgresDAOImpl<T>标注了@Repository,Spring默认将其作为单例Bean管理。在JDK 11环境下,Spring无法通过泛型参数区分不同的Bean实例,导致三个Service复用了同一个PostgresDAOImpl实例。- 虽然日志显示
setTypeParameterClass被正确调用,但单例实例的属性会被后续调用覆盖——如果ClassOneServiceImpl的@PostConstruct最后执行,就会把typeParameterClass覆盖为ParameterOne.class,导致ClassThreeServiceImpl读取到错误值。 - JDK 17环境下Spring的泛型解析逻辑更完善,能区分不同泛型参数的Bean,因此每个Service都拿到了独立实例,没有覆盖问题。
- 参考配置代码存在错误:三个
@Bean方法名称均为deceptionDAO,Spring会认为是同一个Bean,后面的定义会覆盖前面的,同样会导致实例复用问题。
解决方案
方案1:为每个泛型参数创建独立的DAO Bean(推荐)
修改配置类,给每个Bean设置唯一名称,确保Spring能区分不同泛型实例:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class PostgresDAOConfig { @Bean(name = "parameterOneDAO") public PostgresDAO<ParameterOne> parameterOneDAO() { PostgresDAOImpl<ParameterOne> result = new PostgresDAOImpl<>(); result.setTypeParameterClass(ParameterOne.class); return result; } @Bean(name = "parameterTwoDAO") public PostgresDAO<ParameterTwo> parameterTwoDAO() { PostgresDAOImpl<ParameterTwo> result = new PostgresDAOImpl<>(); result.setTypeParameterClass(ParameterTwo.class); return result; } @Bean(name = "parameterThreeDAO") public PostgresDAO<ParameterThree> parameterThreeDAO() { PostgresDAOImpl<ParameterThree> result = new PostgresDAOImpl<>(); result.setTypeParameterClass(ParameterThree.class); return result; } }
在Service中通过@Qualifier指定要注入的Bean:
@Service public class ClassOneServiceImpl implements ClassOneService { private final PostgresDAO<ParameterOne> dao; public ClassOneServiceImpl(@Qualifier("parameterOneDAO") PostgresDAO<ParameterOne> dao) { this.dao = dao; } // 移除@PostConstruct方法,配置类已初始化typeParameterClass public Class getParameter() { return dao.getTypeParameterClass(); } }
方案2:将PostgresDAOImpl改为原型Bean
让每个Service注入时获取新的实例,避免属性覆盖:
@Repository @NoArgsConstructor @Getter @Scope("prototype") // 添加原型作用域 public class PostgresDAOImpl<T> implements PostgresDAO<T> { // 原有代码不变 }
此方式下每个Service的@PostConstruct设置的typeParameterClass不会互相覆盖,但需注意原型Bean的生命周期由调用方管理,EntityManager注入需确保正常。
方案3:自动解析泛型参数
利用Spring的GenericTypeResolver自动解析泛型类型,无需手动设置:
@Repository @Getter public class PostgresDAOImpl<T> implements PostgresDAO<T> { private Class<T> typeParameterClass; @PersistenceContext private EntityManager em; @PostConstruct public void init() { // 自动解析当前类的泛型参数 this.typeParameterClass = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), PostgresDAO.class); } // 移除setTypeParameterClass方法,Service中无需@PostConstruct设置 }
此方式适合直接继承PostgresDAOImpl的场景,比如创建ParameterOneDAOImpl extends PostgresDAOImpl<ParameterOne>并标注@Repository。
内容的提问来源于stack exchange,提问作者Alex A

