抽象类实现中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
相关产品推荐
相关产品推荐

