Java 行为附加到注释的设计问题

Java design issue where behavior is attached to annotations

假设我通过使用 @transactions 注释来使用 JPA

因此,为了在事务下使用任何方法 运行,我在事务下添加了一个 @transaction 注释和 BINGO 我的方法 运行。

为了实现上述目标,我们需要 class 有一个 interface 并且该实例由某个容器管理。
此外,我应该始终从接口引用中调用该方法,以便代理对象可以启动事务。 所以我的代码看起来像:

class Bar {
   @Inject
   private FooI foo;
   ...
   void doWork() {
      foo.methodThatRunUnderTx();
   }
}
class FooImpl implements FooI {
   @Override
   @Transaction
   public void methodThatRunUnderTx() {
       // code run with jpa context and transaction open
   }
}
interface FooI {
    void methodThatRunUnderTx();
}

很好很好

现在假设methodThatRunUnderTx做两个逻辑运算

[1] 调用一些服务(长 request/response 周期假设 5 秒)并获取结果

[2] 执行一些 jpa 实体修改

现在由于这个方法调用很长而且我们不想长时间保持交易打开,所以我们更改代码以便 [2] 发生在单独的 tx 而 methodThatRunUnderTx 不 运行 在交易中

所以我们将从 methodThatRunUnderTx 中删除 @Transaction 并在 class 中添加另一个方法 @transaction 假设新方法是 methodThatRunUnderTx2,现在要从 methodThatRunUnderTx 调用此方法,我们必须将其注入自身并向接口添加一个方法,以便通过代理对象进行调用。

所以现在我们的代码将如下所示:

class Bar {
   @Inject
   private FooI foo;
   ...
   void doWork() {
      foo.methodThatRunUnderTx();
   }
}
class FooImpl implements FooI {
   @Inject
   private FooI self;
   @Override
   //@Transaction -- remove transaction from here
   public void methodThatRunUnderTx() {
      ...
     self.methodThatRunUnderTx2();// call through proxy object
   }
   @Override
   @Transaction //add transaction from here
   public void methodThatRunUnderTx2() {
       // code run with jpa context and transaction open
   }
}
interface FooI {
    void methodThatRunUnderTx();
    void methodThatRunUnderTx2();
}

现在的问题

我们通过interface.[=27将methodThatRunUnderTx2()变成了public =]

但这不是我们想要公开的 FooI 的 api 并且不打算从外部调用..

有什么解决的建议吗?

正如您所说,如果您在同一个 bean 上调用一个方法,它将不会被代理,因此不会发生事务管理,要解决这个问题,您可以在 Bean Managed Transaction 中手动启动和停止事务:

class FooImpl implements FooI {

   @Resource
   private UserTransaction userTransaction;

   @Override
   //@Transaction -- remove transaction from here
   public void methodThatRunUnderTx() {
      ...
     self.methodThatRunUnderTx2();// call through proxy object
   }
   @Override
   //@Transaction -- remove transaction from here too, because now you'll manage the transaction
   public void methodThatRunUnderTx2() {
       userTransaction.start();
       // code run with jpa context and transaction open
       userTransaction.commit(); // Commit or rollback do all the handling, i'm not writing it because its just an example
   }
}

这样你就不会向 public api 暴露任何额外的东西,但你会有一些额外的代码来管理交易。

遵循接口隔离原则,将两个逻辑操作分离到两个接口:一个fetcher和一个modifier。将两者都注入 class Bar。这允许两个逻辑实现相互独立地改变,例如允许一个是事务性的而另一个不是。第二个接口不必是 public class.

这个问题是关于处理交易部分的一个非常有效的问题。但是,如果您试图隐藏一项功能而不是其他功能,则需要考虑这些:

选项 1:

  1. 考虑 - 您需要公开执行调用者所需的全部功能的方法

  2. 在这种交易处理的情况下,建议您暂时保持交易开启,直到交易完成

选项 2:

  1. 正在考虑 - 您需要有效地管理交易
  2. 根据功能 IModifyFooISelectFoo 分别修改和 select 拆分接口的方法,并实现这些方法并在所需方法上用 @Transactional 注释
  3. 接口设计为 public,这意味着您需要了解需要向外部世界公开什么。在这种情况下,您将选择原则而不是技术挑战。

我只能想到这些选项,我们正在努力解决您在此处遇到的基于 java 基础知识的技术挑战。好好想想。

如果您希望该方法 ThatRunUnderTx2 不会成为 public 将其设为私有方法并删除 @Override 注释并将其从接口中删除。

这就是现代容器不需要实现任何接口的原因 - 然后通过动态子classing 或使用字节码工具创建代理。

因此,您的设计问题的解决方案很简单:实现一个包含事务方法的助手 class,并将其注入实现接口的 class(以及任何其他 class 可以从中受益)。

您必须接受基于事务的注释不适用于私有方法。因此,您根本无法隐藏(设为私有)应该是此类注释主题的方法。

您可以摆脱接口(即 EJB 世界中的@LocalBean),但是您仍然不能使用私有方法...

可以肯定的是,这个问题的解决方案是正确的。他们将允许从 public void methodThatRunUnderTx() 的主体中删除 self.methodThatRunUnderTx2() 方法调用。这个问题的答案很可能对您有所帮助:Aspectj and catching private or inner methods

但是我不确定方面对于这个问题是否不是太大的问题,因为它们增加了代码的复杂性和可读性。我宁愿考虑以这样一种方式更改代码的体系结构,这样您的问题就无关紧要了。