You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

泛型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实例的复用逻辑不同:

  1. PostgresDAOImpl<T>标注了@Repository,Spring默认将其作为单例Bean管理。在JDK 11环境下,Spring无法通过泛型参数区分不同的Bean实例,导致三个Service复用了同一个PostgresDAOImpl实例。
  2. 虽然日志显示setTypeParameterClass被正确调用,但单例实例的属性会被后续调用覆盖——如果ClassOneServiceImpl的@PostConstruct最后执行,就会把typeParameterClass覆盖为ParameterOne.class,导致ClassThreeServiceImpl读取到错误值。
  3. JDK 17环境下Spring的泛型解析逻辑更完善,能区分不同泛型参数的Bean,因此每个Service都拿到了独立实例,没有覆盖问题。
  4. 参考配置代码存在错误:三个@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 13:07:46