在目标而不是代理实例上调用 @Transactional 的方法
Method with @Transactional called on target not on proxy instance
我目前正在将我的一个项目从 "self configured spring" 迁移到 spring 引导。虽然大多数东西已经在工作了,但我遇到了一个 @Transactional
方法的问题,当它被调用时,由于调用 "target" 实例而不是 "proxy"实例(我将在下面详细说明)。
首先是我的 class 层次结构的精简视图:
@实体
public class 配置{
// 字段和东西
}
public接口导出器{
int startExport() 抛出 ExporterException;
void setConfig(配置配置);
}
public 摘要 class ExporterImpl 实现 Exporter {
受保护的配置配置;
@覆盖
public final void setConfig(Config 配置) {
this.config = 配置;
// this.config 是一个有效的配置实例
}
@覆盖
@Transactional(readOnly = true)
public int startExport() 抛出 ExporterException {
// this.config 在这里是 NULL
}
// 其他方法,包括 subclass 的抽象方法
}
@Scope("prototype")
@Service("cars2Exporter")
public class Cars2ExporterImpl 扩展 ExporterImpl {
// 覆盖抽象方法和一些其他的
// 不接触 startExport()
}
// 还有其他 ExporterImpl 的实现
// 在所有实现中都会出现问题
调用代码是这样的:
@注入
私人供应商<出口商> cars2Exporter;
public void scheduleExport(配置配置){
出口商出口商 = cars2Exporter.get();
exporter.setConfig(配置);
exporter.startExport();
// 实际上我把它包装在一个 class 实现可运行
// 并将其放入 `TaskExecutor` 的队列中,但问题发生了
// 也可以直接调用。 :(
}
到底是什么问题?
在对 startExport()
的调用中,ExporterImpl
的字段 config
为空,尽管它之前已被设置。
到目前为止我发现了什么:
在 exporter.startExport();
处设置断点,我检查了 eclipse 调试器显示的导出器实例的 ID。在编写此 post 的调试轮中,它是 16585
。继续执行到 startExport()
的 call/first 行,在那里我再次检查了 id(这次是 this
的)期望它是相同的但意识到它不是。这里是 16606
...所以对 startExport()
的调用是在 class 的另一个实例上完成的...在上一轮调试中我检查了 instance/id调用 setConfig()
将...转到第一个(在本例中为 16585)。这解释了为什么配置字段在 16606 实例中为空。
为了了解我调用 exporter.startExport();
的行和 startExport()
的实际第一行之间发生了什么,我在 eclipse 调试器中单击了这两行之间的步骤。
我来到 line 655 in CglibAopProxy 看起来像这样:
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
检查这里的参数,我发现 proxy
是 ID 为 16585 的实例,target
是 ID 为 16606 的实例。
不幸的是,我对 spring 的 aop 内容了解不深,不知道它是否应该如此...
我只是想知道为什么有两个实例在不同的方法上被调用。对 setConfig()
的调用转到代理实例,对 startExport()
的调用到达目标实例,因此无法访问先前设置的配置...
如前所述,该项目已迁移到 spring 引导,但我们之前已经使用 Athens-RELEASE
版本的 spring 平台 bom。据我所知,迁移前没有特殊的 AOP 配置,迁移后也没有明确设置值。
为了解决这个问题(或至少以某种方式起作用)我已经尝试了多种方法:
- 从子class
中删除@Scope
- 将@Transactional 从方法级别移动到class
- 覆盖 subclass 中的 startExport() 并将 @Transactional 放在这里
- 将@EnableAspectJAutoProxy 添加到应用程序class(我什至无法登录 - 没有错误消息)
- 将spring.aop.proxy-目标-class设置为真
- 以上的不同组合...
目前我不知道如何让它恢复工作...
提前致谢
*希望有人能帮忙*
Spring Boot 尝试创建一个 cglib 代理,这是一个基于 class 的代理,在您可能拥有基于接口的代理(JDK 动态代理)之前。
因此,您的 Cars2ExporterImpl
的一个子 class 被创建,所有方法都被覆盖,建议将被应用。但是,由于您的 setConfig
方法是 final
,因此无法覆盖该方法,因此实际上将在代理而不是代理实例上调用该方法。
因此,要么删除 final
关键字以便可以创建 CgLib 代理,要么明确禁用基于 class 的交易代理。添加 @EnableTransationManagement(proxy-target-class=false)
也应该可以解决问题。除非有其他东西触发基于 class 的代理。
我目前正在将我的一个项目从 "self configured spring" 迁移到 spring 引导。虽然大多数东西已经在工作了,但我遇到了一个 @Transactional
方法的问题,当它被调用时,由于调用 "target" 实例而不是 "proxy"实例(我将在下面详细说明)。
首先是我的 class 层次结构的精简视图:
@实体 public class 配置{ // 字段和东西 } public接口导出器{ int startExport() 抛出 ExporterException; void setConfig(配置配置); } public 摘要 class ExporterImpl 实现 Exporter { 受保护的配置配置; @覆盖 public final void setConfig(Config 配置) { this.config = 配置; // this.config 是一个有效的配置实例 } @覆盖 @Transactional(readOnly = true) public int startExport() 抛出 ExporterException { // this.config 在这里是 NULL } // 其他方法,包括 subclass 的抽象方法 } @Scope("prototype") @Service("cars2Exporter") public class Cars2ExporterImpl 扩展 ExporterImpl { // 覆盖抽象方法和一些其他的 // 不接触 startExport() } // 还有其他 ExporterImpl 的实现 // 在所有实现中都会出现问题
调用代码是这样的:
@注入 私人供应商<出口商> cars2Exporter; public void scheduleExport(配置配置){ 出口商出口商 = cars2Exporter.get(); exporter.setConfig(配置); exporter.startExport(); // 实际上我把它包装在一个 class 实现可运行 // 并将其放入 `TaskExecutor` 的队列中,但问题发生了 // 也可以直接调用。 :( }
到底是什么问题?
在对 startExport()
的调用中,ExporterImpl
的字段 config
为空,尽管它之前已被设置。
到目前为止我发现了什么:
在 exporter.startExport();
处设置断点,我检查了 eclipse 调试器显示的导出器实例的 ID。在编写此 post 的调试轮中,它是 16585
。继续执行到 startExport()
的 call/first 行,在那里我再次检查了 id(这次是 this
的)期望它是相同的但意识到它不是。这里是 16606
...所以对 startExport()
的调用是在 class 的另一个实例上完成的...在上一轮调试中我检查了 instance/id调用 setConfig()
将...转到第一个(在本例中为 16585)。这解释了为什么配置字段在 16606 实例中为空。
为了了解我调用 exporter.startExport();
的行和 startExport()
的实际第一行之间发生了什么,我在 eclipse 调试器中单击了这两行之间的步骤。
我来到 line 655 in CglibAopProxy 看起来像这样:
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
检查这里的参数,我发现 proxy
是 ID 为 16585 的实例,target
是 ID 为 16606 的实例。
不幸的是,我对 spring 的 aop 内容了解不深,不知道它是否应该如此...
我只是想知道为什么有两个实例在不同的方法上被调用。对 setConfig()
的调用转到代理实例,对 startExport()
的调用到达目标实例,因此无法访问先前设置的配置...
如前所述,该项目已迁移到 spring 引导,但我们之前已经使用 Athens-RELEASE
版本的 spring 平台 bom。据我所知,迁移前没有特殊的 AOP 配置,迁移后也没有明确设置值。
为了解决这个问题(或至少以某种方式起作用)我已经尝试了多种方法:
- 从子class 中删除@Scope
- 将@Transactional 从方法级别移动到class
- 覆盖 subclass 中的 startExport() 并将 @Transactional 放在这里
- 将@EnableAspectJAutoProxy 添加到应用程序class(我什至无法登录 - 没有错误消息)
- 将spring.aop.proxy-目标-class设置为真
- 以上的不同组合...
目前我不知道如何让它恢复工作...
提前致谢
*希望有人能帮忙*
Spring Boot 尝试创建一个 cglib 代理,这是一个基于 class 的代理,在您可能拥有基于接口的代理(JDK 动态代理)之前。
因此,您的 Cars2ExporterImpl
的一个子 class 被创建,所有方法都被覆盖,建议将被应用。但是,由于您的 setConfig
方法是 final
,因此无法覆盖该方法,因此实际上将在代理而不是代理实例上调用该方法。
因此,要么删除 final
关键字以便可以创建 CgLib 代理,要么明确禁用基于 class 的交易代理。添加 @EnableTransationManagement(proxy-target-class=false)
也应该可以解决问题。除非有其他东西触发基于 class 的代理。