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

抽象类实现中Autowired对象NullPointerException及事务冲突问题

问题原因分析与解决方案

一、矛盾现象的核心原因

1. protected修饰updateRdi时事务失效的原因

Spring的@Transactional注解默认仅对public方法生效:

  • 若使用JDK动态代理,仅能代理public方法;
  • 即便用CGLIB代理(支持protected方法),Spring的事务切面默认也只匹配public方法的注解。
    因此protected方法上的@Transactional不会被切面拦截,事务自然失效。

2. public修饰updateRdi时出现NullPointerException的原因

这是Spring CGLIB代理机制与错误的Bean配置共同导致的:

  • 错误的Bean标注:你的抽象类ExcelRdi_Iupr_Sl被标注了@Service,但抽象类无法被实例化,Spring不会为其创建Bean,反而会干扰实现类的Bean管理逻辑。
  • CGLIB代理的特性:当子类的updateRdi设为public并添加@Transactional后,Spring会为实现类生成CGLIB代理对象(因为是继承抽象类而非实现接口)。CGLIB代理是通过创建目标类的子类实现的,代理对象会继承目标类的所有成员变量,但这些变量不会被Spring注入(Spring仅初始化目标对象的成员变量)。
  • 成员变量访问冲突:当你调用代理对象的模板方法(如retrieve/update)时,模板方法属于父类,代理类直接继承了该方法,方法中访问的是代理对象自身的成员变量(如父类的env、子类的sheet),而这些变量未被注入/初始化,因此抛出空指针异常。

二、具体解决方案

1. 移除抽象类上的@Service注解

抽象类不需要被Spring标记为Bean,仅需在实现类上标注@Component或@Service,让Spring只管理实现类的Bean,避免配置混淆。

2. 调整事务注解的位置(推荐方案)

将@Transactional移到父类的模板方法上,而非子类的updateRdi方法:

  • 模板方法设为public,确保事务注解生效;
  • 模板方法调用子类的protected方法时,访问的是目标对象的成员变量(子类方法未被代理,this指向目标对象),避免空指针。

示例代码:

// 抽象类:移除@Service,仅保留核心逻辑
public abstract class ExcelRdi_Iupr_Sl {
    @Autowired
    protected Environment env;

    // 模板方法添加事务注解
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        firstTwoRow();
        elaboration();
        updateRdi();
    }

    public abstract void retrieve();

    // 子类实现的方法设为protected即可
    protected abstract void firstTwoRow();
    protected abstract void elaboration();
    protected abstract void updateRdi();
}

// 实现类:仅保留@Component/@Service标注
@Service
public class ExcelRdi extends ExcelRdi_Iupr_Sl {
    @Autowired
    private RdiServices rdiServices;

    @Override
    public void retrieve() {
        // 实现模板方法逻辑
    }

    @Override
    protected void firstTwoRow() {
        // 初始化sheet等操作,此时this指向目标对象,成员变量正常
    }

    @Override
    protected void elaboration() {
        // 业务逻辑实现
    }

    @Override
    protected void updateRdi() {
        // 数据库操作,事务由父类模板方法管控
    }
}

3. 改用构造注入(可选,增强代码安全性)

构造注入是Spring推荐的依赖注入方式,可从根源避免代理对象成员变量未注入的问题:

  • 构造注入在对象创建时完成初始化,代理对象会调用目标对象的构造方法,确保所有成员变量被正确赋值;
  • 同时提升代码的可测试性与可读性。

示例代码:

// 抽象类:构造注入Environment
public abstract class ExcelRdi_Iupr_Sl {
    protected final Environment env;

    public ExcelRdi_Iupr_Sl(Environment env) {
        this.env = env;
    }

    @Transactional(rollbackFor = Exception.class)
    public void update() {
        firstTwoRow();
        elaboration();
        updateRdi();
    }

    public abstract void retrieve();
    protected abstract void firstTwoRow();
    protected abstract void elaboration();
    protected abstract void updateRdi();
}

// 实现类:构造注入依赖,调用父类构造方法
@Service
public class ExcelRdi extends ExcelRdi_Iupr_Sl {
    private final RdiServices rdiServices;

    public ExcelRdi(Environment env, RdiServices rdiServices) {
        super(env);
        this.rdiServices = rdiServices;
    }

    // 重写方法...
}

内容的提问来源于stack exchange,提问作者luca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:52:05