10

首先我要澄清一下,这篇文章并不是要批评 CDI,而是要发现 CDI 设计背后的思想和假设,这将对设计任何使用 CDI 的 Web 应用程序产生明显的影响。

CDI(Java EE 6)最显着的特性之一是类型安全。Jboss Seam 的字体不安全。它使用名称来限定要注入的任何实例。像下面这样:

   @Name("myBean")
   public class MyBean implements Bean {
     ...
   }

   @Name("yourBean")
   public class YourBean implements Bean {
     ...
   }

在注入 MyBean 时,可以这样做:

   @In
   private Bean myBean; //myBean is injected

   @In
   private Bean yourBean; //yourBean is injected  

和 Spring 的早期版本(3.0 之前),这种类型的注入发生如下:

只需在 bean 配置文件中定义 bean:

   <bean id="myBean" class="com.example.common.MyBean">
       ...
   </bean>

   <bean id="yourBean" class="com.example.common.YourBean">
       ...
   </bean>

并使用命名限定符,决定使用哪一个:

   @Autowired
   @Qualifier("myBean")
   private Bean bean;

   @Autowired
   @Qualifier("yourBean")
   private Bean bean; 

但现在在 CDI 中,首先您需要Qualifier为任何特定类型的对象定义自定义注释。然后使用该注释来限定该对象。归根结底,当您查看源代码时,您会发现,您浪费了大量时间来编写大量用于依赖注入的自定义注释。Java 社区正在转向注解,而将基于 XML 的配置(详细的 XML)抛在后面。有没有什么可以说服任何人认为这(自定义注释的类型安全)不是冗长的注释,而是 CDI 的一个优秀和杰出的特性?

编辑:

点,推送以突出显示

  1. 如果我为每个服务或 dao(每个接口)的类型安全使用自定义限定符,那么对于大型应用程序,例如拥有 1000 个或更多服务或具有多个实现的 dao 类,它会很混乱。那么对于大型应用程序,使用类型安全注入是否可行?
  2. 如果上述问题的答案是“否”,那么使用类型安全有什么意义呢?
  3. 即使为类型安全编写注释是可行的,但在大型应用程序中,是否真的值得为避免冗长的 xml配置而付出努力?
  4. 我什么时候需要类型安全而不是 bean 名称限定符?

对以上几点的简短讨论

  1. 真正需要类型安全注入的情况并不多,特别是当你有一个接口的实现时,你应该使用@Name它作为限定符。所以是的,在大型应用程序中,在实际需要时使用类型安全是可行的。
  2. 当然,类型安全是 CDI 的显着特征之一,在公认的答案中,有一个非详尽的清单列出了您可能选择使用类型安全的原因。
  3. 由于您是一名聪明的程序员,并且您确切地知道何时使用类型安全,因此在真正需要时绝对值得付出努力。
  4. 接受的答案的大部分部分都在真正讨论,我们什么时候需要类型安全,这篇文章也非常有助于理解。

感谢和快乐的编码!

4

3 回答 3

18

CDI 冗长吗?需要限定符吗?

  1. 首先,当您只有一个接口实现时,您不需要限定符。
  2. 如果您有多个接口的实现,那么问问自己——部署后是否需要区分它们?

    • 如果答案是否定的,那么考虑使用替代方案。
    • 如果答案是肯定的,那么您仍然不需要限定符。原因如下:

      要使用您自己的示例:

      public class MyBean implements Bean {
          ...
      }
      
      public class YourBean implements Bean {
          ...
      }
      

      然后,您只需执行以下操作:

      @Inject MyBean bean;
      

      或者

      @Inject YourBean bean;
      

      如果你不喜欢你的实例变量是一个具体的类型并且宁愿看到一个接口,而不是这样做:

      private Bean bean;
      
      @Inject
      public void setBean(MyBean bean) {
          this.bean = bean;
      }
      

      或者

      private Bean bean;
      
      @Inject
      public void setBean(YourBean bean) {
          this.bean = bean;
      }
      

      在上述所有情况下,它完全没有限定符,绝对类型安全,而且绝对不冗长。

  3. 然后,详细说明最后一点 - 开发人员是否需要选择适当的实现,或者选择是否有问题?

    • 如果开发人员将选择,则按照上面 2 中的说明进行操作。
    • 如果选择有问题,则使用生产者:

      @Produces
      public Bean obtainTheAppropriateBean(InjectionPoint ip) {
          if (meetsConditionA(ip)) {
              return getBeanImplA();
          } else if (meetsConditionB(ip)) {
              return getBeanImplB();
          } else if (...) {
              ...
          } else {
              return getDefaultBeanImpl();
          }
      }
      

      仍然没有限定符,类型安全,并且可能仍然不冗长(自动化的交易选择)。

    有关如何使用InjectionPoint API的观点和想法,请参阅这篇文章。

是否需要限定符?

在上述示例之后,我可以看到这个问题。答案是肯定的,以下是您可能选择使用它们的非详尽原因列表:

  • 在上面的一个示例中,我提到了注入接口的特定实现以避免使用限定符。当代码是内部的并且内部开发人员会知道哪个是哪个时,这完全没问题。但是,如果代码是一个库或框架,并且您不想在公共 API 中公开任何特定的实现,该怎么办?然后定义一些限定词并很好地记录它们。这与详细的 XML 有何不同?即使您作为图书馆作者可能正在做同样多的冗长工作,您的用户也不必这样做。相反,他们只会在注入点上方写一个单词,并且很高兴您没有让他们编写任何 XML - 我个人会非常非常非常高兴。:)
  • 在上面的 producer 示例中,您可以使用 producer 方法中的逻辑覆盖大多数情况,但不能涵盖所有情况。或者,也许您只是希望能够在任何特定注入点覆盖该逻辑。然后,保留生产者,制作一个限定符并用它注释一些特定的实现。然后在您不希望生产者逻辑运行时使用限定符。
  • 想象一下您有多个接口和多个实现的情况。特定的实现可能具有共同的可区分特征,并且这些特征对您的所有接口都是通用的。例如,让我们以 Java 集合框架为例,特别是 List、Set 和 Map 接口。它们中的每一个都有多个实现,但是所有或某些接口都有共同的特征。例如链接节点(快速迭代)——想想 LinkedList、LinkedHashSet、LinkedHashMap;sorted(排序)——想想 TreeSet、TreeMap;基于哈希表(快速插入/删除/包含)——想想 HashSet、LinkedHashSet、HashMap、LinkedHashMap;一致的;随机访问; 等等。现在,您可以定义@Linked@Sorted@Hash注释。然后注入:

    @Inject @Linked @Hash private Map map;
    @Inject @Sorted private Map map;
    @Inject @Hash private Set set;
    @Inject @Hash private Set set;
    

    现在,是否值得为集合框架这样做?我不会这样做,但我有一个类似于我在当前工作项目中描述的案例(抱歉,无法讨论)。

  • 最后,您可以使用限定符将参数与@Nonbinding. 继续上面的集合框架,定义:

    @Qualifier
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hash {
        @Nonbinding int capacity() default 16;
        @Nonbinding float loadFactor() default 0.75f;
    }
    

    这样,您可以将所需的容量和负载因子传递给生产者,返回任何基于如下的哈希表:

    @Inject @Hash(capacity = 256, loadFactor = 0.85f) private Set set;
    @Inject @Hash private Set set;
    @Inject @Hash(capacity = 8, loadFactor = 0.65f) private Map map;
    

我希望这回答了你的问题。这肯定是我喜欢 CDI 的部分原因。

于 2013-03-09T16:35:32.337 回答
4

什么时候需要使用自定义限定符?

只有在以下情况下才需要使用 CDI 自定义限定符: (a) 您的(非注释)代码故意在要使用的类型中引入歧义;(b) 您希望注入比代码指示的更具体类型的对象;(c) 您希望拥有比预定义限定符@Named 提供的更灵活的类型选择和可配置性。

限定符消除了 CDI 创建/注入的类型的歧义。
(a) & (b) 当你在代码中使用高级类型来给出一个强大的通用算法,可以重用和灵活调整时发生。例如,通常可以并鼓励针对接口而不是特定类编写整个算法 - 例如针对 List、Map 或 Queue 的算法。但通常这些算法只有在我们承诺接口的特定实现时才能很好地工作,例如 SortedList、TreeMap 或 PriorityQueue。如果 CDI 要充当对象工厂,执行对象生命周期管理,初始化正确的对象以进行注入和依赖管理,那么它需要了解全部细节。

自定义限定符在消除 CDI 创建/注入的类型歧义方面是智能且可配置的。@Named 是一个更直接的工具。对于 (c),当我提到“类型选择”时,我的意思是开发人员可以将多个限定符组合在一起以“逻辑选择”最合适的类型,而无需命名精确的类。@Named 实际上需要指定精确的类 - 多个限定符将允许 CDI 为您解决。可配置性是指在部署时通过修改 beans.xml 来改变 CDI 行为的能力(无需修改代码/注释)。即可以在逻辑上调整 CDI 正在做什么,并且可以一直执行到部署时间 - 不触及代码或注释。

什么时候需要声明自定义限定符?

不需要为每个可以注入的单独类型创建 CDI 自定义限定符。这是最重要的。

多个限定符可用于注入点和类型声明。在确定注入什么类型时,CDI 匹配 bean 类型加上 SET OF 限定符。为了减少要声明的限定符的数量,将类型分解为描述它们的许多属性/关注点是一个好主意。然后,每个类型“属性”可以有一个限定符,而不是每个类型一个限定符 - 即使这样的属性是多值的,例如哈希/树/枚举(请参阅下一段)。

其次,限定符应该智能地使用参数来进一步减少所需的声明数量。与其创建 10 个限定符,不如使用一个限定符,它的参数是一个字符串,或者最好是一个可以接受 10 个值的枚举。

将这两个想法(限定符=类型属性加上使用限定符参数)结合到一个集合库的示例中:可以用代表排序/非排序的属性来描述类型?哈希/树/数组?链接/未链接?如果我指定一个变量是

@Inject @Sorted @Organisation("hash") @Navigation("linked") Map myMap;

然后我有很强的类型选择,因为保证满足这些方面中的每一个,我有一个排序的链接哈希映射,但不知道这需要特定包中的一个非常特殊的类。

尚未解决的要点:

如果我使用带有参数化 bean 名称的一些注释,那么它实际上是如何类型安全的?

保证强类型,因为必须完全匹配:

  • 豆类
  • 限定词集
  • 参数值集

没有注入类型可以违反任何这些指定的事情。与其将类型安全视为单个字符串(packagename.classname)的匹配,不如将其视为完全由开发人员控制的多个字符串/值的匹配——这些相同的字符串/值用于声明和注入端,提供安全匹配。此外,不要认为您必须匹配最低级别的具体类 - 请记住您可以匹配继承层次结构中的更高级别的类并在您的类型上智能使用 @Default 限定符(请记住 Java EE 6“智能默认配置” ?) 将带您选择首选的具体类型。

如果我为每个服务或 dao(每个接口)的类型安全使用自定义限定符,那么对于大型应用程序,例如拥有 1000 个或更多服务或具有多个实现的 dao 类,它会很混乱。那么对于大型应用程序,使用类型安全注入是否可行?

  • 1000或更多的服务或道课程?真的??!这通常会立即标记出一个大的设计问题。除非这是针对税务部门或美国宇航局等复杂企业的超大型吉尼斯记录尝试申请。即便如此,分解成更小的 Java EE 模块/SOA 服务以及更少的类也是正常的。如果这是您所拥有的,您可能想要简化您的设计 - 您可能会遇到比过去限定符定义更大的问题......

  • 只有在类型不明确的情况下才需要限定符 - 即同一类型层次结构中有许多祖先/后代类。通常情况下,相对较少的服务或 DAO 类是模棱两可的。

  • 如上所述,不要使用每个实现类一个限定符模式:而是重构以使用每个类型方面的限定符,并且还使用每个限定符实现模式的描述性参数

  • 在大型应用程序中使用类型安全注入是可行的。

如果上述问题的答案是“否”,那么使用类型安全有什么意义呢?

  • 答案是“是”
  • 提议的场景(1000 多个服务/DAO 类)非常罕见

即使为类型安全编写注释是可行的,但在大型应用程序中,是否真的值得为避免冗长的 xml 配置而付出努力?

CDI 的目的不仅仅是“避免冗长的 xml 配置” CDI 有以下目标:

  • 通过对象的生命周期管理支持范围(包括新的 Web 对话范围)
  • 类型安全的依赖注入机制,包括在开发或部署时选择依赖项的能力,无需详细配置
  • 支持 Java EE 模块化和 Java EE 组件架构——在解决 Java EE 组件之间的依赖关系时,会考虑 Java EE 应用程序的模块化结构
  • 与统一表达式语言 (EL) 集成,允许在 JSF 或 JSP 页面中直接使用任何上下文对象
  • 装饰注入对象的能力
  • 通过类型安全的拦截器绑定将拦截器与对象关联的能力
  • 事件通知模型
  • 除了 Java Servlets 规范定义的三个标准 Web 上下文之外,还有一个 Web 会话上下文
  • 一个 SPI 允许便携式扩展与容器干净地集成

这与将基本 XML 配置文件转换为注释的方式不同且更有用——即使是早期的基本 Java EE 5 资源注入注释也与 XML 配置转换为注释不同且更有用。

于 2013-03-11T05:31:34.900 回答
2

不需要@Qualifier每次都创建自定义。只用一些参数创建一个就足够了。CDI 将@CustomQualifier("myBean")@CustomQualifier("yourBean")视为不同的限定符。

此外,这样的限定符已经在 CDI 包中 - @Named

但有时静态限定符非常有用,例如(参见@Alternative):

@Alternative
@Qualifier
@Documented
@Retention(value=RUNTIME)
public @interface Mock

并且您可以将一些 bean 标记@Mock为仅用于测试(不要忘记在测试类路径中启用alternative部分。beans.xml

于 2013-03-05T21:23:19.523 回答