Matt Corey 在 博客 中讨论了CDI拦截器绑定,展示了一个简单的示例,说明如何实现自己的@RequiredTx注解。他还轻微批评了使用beans.xml来启用拦截器的做法。(这在Weld论坛中也被讨论过。)我认为我们在这里的设计恰到好处,我将解释原因。但首先让我回忆一下为什么CDI拦截器绑定比EJB 3.0中的@Interceptors注解要好得多。@Interceptors注解。
在EJB 3.0中,您可以通过在bean类上直接指定来声明bean的拦截器。使用Matt的示例
public class UserGetterer {
@Interceptors({RequiredTxInterceptor.class, SecurityInterceptor.class})
public User loadUser() { ... }
}
现在,我认为这里有三个问题
- 将bean类直接依赖于拦截器实现类非常不美观。
- 拦截器应该能够根据部署环境而变化 - 我不需要在测试中使用事务拦截器,我绝对不希望我的安全拦截器!
- 拦截器顺序是由拦截器列表的顺序定义的 - 如果拦截器确实是正交的,这可能还不错,但这很少是情况。拦截器顺序应该更集中地定义,以减少微妙的错误的可能性。
(EJB 3.0还定义了一种基于XML的方法来将拦截器绑定到bean上,这不太有问题,但我更喜欢在代码中看到这样的信息。)
因此,在CDI中,我们引入了一个间接的方法,称为拦截器绑定。拦截器绑定是一个注解 - 您可以在Matt的帖子中看到一个示例 - 我们用它来代替拦截器类
public class UserGetterer {
@RequiredTx @Secure
public User loadUser() { ... }
}
现在,我们的bean类不直接依赖于拦截器实现类,也没有指定拦截器调用的顺序。为了指定bean与拦截器类之间的连接,我们需要做两件事:
- 将拦截器绑定应用到拦截器类中,并且
- 通过在bean模块的beans.xml文件中列出拦截器来启用拦截器。
马特反对第二个要求
是的,我知道——这感觉有点像是通过让用户启用拦截器而不是使用注解来暴露你的实现细节……有点烦人,但我可以忍受……
实际上,beans.xml文件包含部署特定的元数据——只是那些依赖于你的部署的东西。所以在你的测试环境中,你不需要在beans.xml中指定任何拦截器。在你的生产部署中,你可以启用RequiredTransactionInterceptor。在某个其他部署中,你可以启用DummyRequiredTransactionInterceptor,这是@RequiredTx 语义
或方面
的不同实现。甚至可以绑定多个拦截器到同一个拦截器绑定!所以,我们真正地将语义注解与其实现@RequiredTx解耦了,RequiredTransactionInterceptor.
因此,我们在这里获得了一个额外的间接层次,如果我们像马特建议的那样指定注解,就不会有这个层次。beans.xml实际上,
在这里扮演两个不同的角色。它让你可以指定特定部署的拦截器绑定的任何、无或多个实现,以及beans.xml拦截器的顺序。
- 顺序很重要。你很可能会在你的应用程序中使用由各种不同的可移植扩展定义的拦截器。这些可移植扩展之间互不认识,所以它们不知道应该按什么顺序调用。我们
- 不能在定义拦截器的可移植扩展中指定拦截器顺序。
马特说
另一件事要注意的是,如果你将拦截器打包到一个共享库文件中,即使你在你的.jar文件中启用了它,应用程序中的其他所有库文件仍然需要这样做——这是因为CDI中没有全局拦截器的概念,但为了安全起见,每个库都被迫启用所有使用的拦截器,这样它们就可以以非常直观的方式强制执行拦截器的顺序……我之所以接受这种推理,仅仅是因为他们已经定义了它,而不是让它成为一个未定义的混乱,所以,这对我来说更多的是一个烦恼——希望将来会有其他选项……
好吧,让我们退一步。如果我的应用程序作为一个单一模块部署,那么
确实有一个全局顺序,由该模块的beans.xml文件定义的顺序。
现在,对于需要真正部署的更复杂的应用程序案例,例如,作为一个包含多个war和EJB jar模块的ear,或者作为一个包含多个jar模块的war,各个模块很可能将定义不同的拦截器、装饰器和替代列表。我的意思是,模块化的整个
目的就是为了解耦模块。beans.xml文件,该文件定义了全局或默认的拦截器、装饰器和替代项列表。这很容易添加到CDI规范中,但我认为这是一个相当高级的情况。从历史上看,Java EE不允许你在ear中放置模块级元数据。