如何处理Spring/EJB/Mockito上的内部调用代理?

2022-09-03 13:06:35

正如您许多人在代理对象时所知道的那样,例如当您为Spring / EJB创建具有事务属性的Bean时,甚至当您使用某些框架创建部分模拟时,代理对象也不知道这一点,并且内部调用不会重定向,然后也不会被拦截......

这就是为什么如果你在春天做这样的事情:

@Transactionnal
public void doSomething() {
    doSomethingInNewTransaction();
    doSomethingInNewTransaction();
    doSomethingInNewTransaction();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomethingInNewTransaction() {
    ...
}

当您调用doSomething时,除了主要事务之外,您还期望有3个新事务,但实际上,由于此问题,您只能获得一个...


所以我想知道你如何处理这些问题...

实际上,我处于必须处理复杂事务系统的情况下,我没有看到比将我的服务拆分为许多小服务更好的方法,这样我就可以确保通过所有代理...

这让我很困扰,因为所有的代码都属于同一个功能领域,不应该被拆分......

我发现这个相关的问题有有趣的答案:春天 - @Transactional - 背景中会发生什么?

Rob H说我们可以在服务中注入spring代理,并调用proxy.doSomethingInNewTransaction();相反。这很容易做到,而且它有效,但我真的不喜欢它...

侯云峰是这样说的:

所以我编写了我自己的 CglibSubclassingInstantiationStrategy 和代理创建者版本,以便它将使用 CGLIB 生成一个真正的子类,该子类将调用委托给其超级而不是另一个实例,Spring 现在正在这样做。因此,我可以自由地注释任何方法(只要它不是私有的),并且无论我在哪里调用这些方法,它们都会得到照顾。好吧,我仍然要付出代价:1.我必须列出我想要启用新的CGLIB子类创建的所有注释。2. 我无法对最终方法进行注释,因为我现在正在生成子类,因此最终方法无法被截获。

他所说的“哪个春天现在在做什么”是什么意思?这是否意味着内部事务调用现在被截获?


您认为什么更好?

当你需要一些事务粒度时,你会拆分你的类吗?还是使用如上所述的解决方法?(请分享)


答案 1

我将讨论Spring和@Transactional但该建议也适用于许多其他框架。

这是基于代理的方面的固有问题。在春季文档中对此进行了讨论:

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

有许多可能的解决方案。

重构类以避免绕过代理的自调用调用。

Spring文档将其描述为“最佳方法(术语”最佳“在这里被松散地使用)”。

这种方法的优点是它的简单性,并且与任何框架都没有联系。但是,它可能不适合非常繁重的事务性代码库,因为您最终会得到许多微不足道的小类。

在类内部获取对代理的引用。

这可以通过注入代理或使用硬编码的“ AopContext.currentProxy()”调用来完成(请参阅上面的Spring文档)。

此方法允许您避免拆分类,但在很多方面否定了使用事务性批注的优点。我个人的观点是,这是有点丑陋的事情之一,但丑陋是自成一体的,如果使用大量交易,可能是务实的方法。

切换到使用 AspectJ

由于AspectJ不使用代理,因此自调用不是问题

这是一个非常干净的方法 - 它是以引入另一个框架为代价的。我曾参与过一个大型项目,正是出于这个原因引入了AspectJ。

不要使用@Transactional

重构代码以使用手动事务划分 - 可能使用装饰器模式。

一个选项 -但是需要适度重构,引入额外的框架联系并增加复杂性 - 因此可能不是首选选项

我的建议

通常拆分代码是最好的答案,对于分离关注点也是一件好事。但是,如果我有一个严重依赖嵌套事务的框架/应用程序,我会考虑使用AspectJ来允许自调用。


答案 2

在建模和设计复杂的用例时,一如既往 - 专注于可理解和可维护的设计和代码。如果您更喜欢某个模式或设计,但它与基础框架冲突,请考虑是否值得采用复杂的解决方法来将设计硬塞入框架中,或者是否应该妥协并在必要时使您的设计符合框架。不要与框架作斗争,除非你绝对必须这样做。

我的建议 - 如果你能通过如此简单的妥协来实现你的目标,以至于分成几个额外的服务类 - 那就去做吧。在时间,测试和痛苦方面,这听起来比替代方案便宜得多。这听起来确实更容易维护,并且对于下一个家伙来说,接管工作也不那么头疼。


推荐