在目标上调用@Transactional的方法不在代理实例

时间:2016-11-17 13:50:07

标签: java spring spring-boot spring-transactions

我正在将我的一个项目从“自配置弹簧”迁移到弹簧启动。虽然大多数东西已经工作了但我遇到了@Transactional方法的问题,当调用它时,由于调用“目标”实例而不是“代理”实例,因此上下文不存在。 (我将在下面详细说明。)

首先是我的类层次结构的精简视图:

@Entity
public class Config {
    // fields and stuff
}

public interface Exporter {

    int startExport() throws ExporterException;

    void setConfig(Config config);
}


public abstract class ExporterImpl implements Exporter {
    protected Config config;

    @Override
    public final void setConfig(Config config) {
        this.config = config;
        // this.config is a valid config instance here
    }

    @Override
    @Transactional(readOnly = true)
    public int startExport() throws ExporterException {
        // this.config is NULL here
    }

    // other methods including abstract one for subclass
}

@Scope("prototype")
@Service("cars2Exporter")
public class Cars2ExporterImpl extends ExporterImpl {

    // override abstract methods and some other
    // not touching startExport()
}

// there are other implementations of ExporterImpl too 
// in all implementations the problem occurs

调用代码是这样的:

@Inject
private Provider<Exporter> cars2Exporter;

public void scheduleExport(Config config) {
    Exporter exporter = cars2Exporter.get();
    exporter.setConfig(config);
    exporter.startExport();
    // actually I'm wrapping it here in a class implementing runnable
    // and put it in the queue of a `TaskExecutor` but the issue happens
    // on direct call too. :(
}

究竟是什么问题?

startExport()的来电中,config的字段ExporterImpl为空,尽管它已在之前设置。

到目前为止我发现了什么: 在exporter.startExport();处有一个断点,我检查了eclipse调试器显示的导出器实例的id。在撰写这篇文章时,在debbug回合中它是16585。继续执行startExport()的调用/第一行,我再次检查了id(这次是this),期望它是相同的但是意识到它不是。这里是16606 ...所以对startExport()的调用是在类的另一个实例上完成的...在之前的调试回合中我检查了实例/ id对{{1}的调用正在......到第一个(在这种情况下是16585)。这解释了为什么16606实例中的配置字段为空。

要理解我调用setConfig()的行与exporter.startExport();的实际第一行之间发生了什么,我点击了eclipse调试器中这两行之间的步骤。

我来到line 655 in CglibAopProxy看起来像这样:

retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

检查这里的参数我发现startExport()是id为16585的实例,proxy是16606的实例。

不幸的是,我并没有那么深入到泉水里去了解它是不是应该如何......

我只是想知道为什么有两个实例可以调用不同的方法。对target的调用转到代理实例,调用do setConfig()到达目标实例,因此无法访问先前设置的配置...

如前所述,该项目已迁移到spring boot,但我们之前已经使用了startExport()版本的spring platform bom。据我所知,迁移前没有特殊的AOP配置,迁移后没有明确设置值。

为了解决这个问题(或者至少在某种程度上工作),我已经尝试过多种方法:

  • 从子类中删除@Scope
  • 将@Transactional从方法级别移动到类
  • 在子类中覆盖startExport()并将@Transactional放在此处
  • 将@EnableAspectJAutoProxy添加到应用程序类(我甚至无法登录 - 没有错误消息)
  • 将spring.aop.proxy-target-class设置为true
  • 以上不同的组合...

目前,我已经找不到如何恢复工作的线索......

提前致谢

*希望有人可以提供帮助*

1 个答案:

答案 0 :(得分:3)

Spring Boot尝试创建一个cglib代理,它是一个基于类的代理,然后你可能有一个基于接口的(JDK动态代理)。

由于这个原因,您的Cars2ExporterImpl的子类被创建,并且所有方法都被覆盖,并且将应用建议。但是,由于您的setConfig方法final无法被覆盖,因此该方法将在代理上实际调用,而不是在代理实例上调用。

因此,请删除final关键字,以便可以创建CgLib代理或显式禁用事务的基于类的代理。添加@EnableTransationManagement(proxy-target-class=false)也应该做到这一点。除非有其他东西触发基于类的代理。