| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
| <html> |
| <head> |
| <title>使用 CDI 中的注入和限定符 - NetBeans IDE 教程</title> |
| <meta http-equiv="content-type" content="text/html; charset=UTF-8"> |
| <meta name="description" content="A demonstration of how to use CDI injection and apply qualifiers to Java EE projects using NetBeans IDE 7.0"> |
| <meta name="keywords" content="NetBeans, IDE, integrated development environment, |
| Contexts and Dependency Injection, CDI, Web Beans, injection, qualifier"> |
| |
| <link rel="stylesheet" type="text/css" href="../../../netbeans.css"> |
| </head> |
| <body> |
| |
| <!-- |
| Copyright (c) 2009, 2010, 2011, Oracle and/or its affiliates. All rights reserved. |
| --> |
| |
| <h1>使用 CDI 中的注入和限定符</h1> |
| |
| <p><em>撰稿人:Andy Gibson</em></p> |
| |
| <div class="margin-around"> |
| <div class="feedback-box margin-around float-left" style="margin-right:15px"> |
| |
| <h3>上下文和依赖关系注入</h3> |
| |
| <ol> |
| <li><a href="cdi-intro.html">CDI 和 JSF 2.0 入门指南</a></li> |
| <li><strong>使用 CDI 中的注入和限定符</strong> |
| |
| <ul style="margin: 5px 0 0 -2em"> |
| <li><a href="#inject">注入:CDI 中的 "I"</a></li> |
| <li><a href="#qualifier">使用限定符</a></li> |
| <li><a href="#alternative">其他注入方法</a></li> |
| <li><a href="#seealso">另请参见</a></li> |
| </ul></li> |
| |
| <li><a href="cdi-validate.html">应用 @Alternative Bean 和生命周期标注</a></li> |
| <li><a href="cdi-events.html">使用 CDI 中的事件</a></li> |
| </ol> |
| </div> |
| </div> |
| |
| <img alt="此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0" class="stamp" src="../../../images_www/articles/73/netbeans-stamp-80-74-73.png" title="此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0"> |
| |
| <p><a href="http://jcp.org/en/jsr/detail?id=299">JSR-299</a> 指定的上下文和依赖关系注入 (CDI) 是 Java EE 6 的一个组成部分,提供了一个体系结构,以允许 Java EE 组件(例如 Servlet、企业 Bean 和 JavaBeans)在具有明确定义范围的应用程序生命周期内存在。此外,CDI 服务允许 Java EE 组件(例如 EJB 会话 Bean 和 JavaServer Faces (JSF) 受管 Bean)注入并通过触发和观察事件以松散耦合的方式进行交互。</p> |
| |
| <p>本教程基于 Andy Gibson 发布的博客,标题为:<a href="http://www.andygibson.net/blog/index.php/2009/12/22/getting-started-with-cdi-part-2-injection/">CDI 入门指南,第 2 部分 - 注入</a>。它演示了如何使用 CDI 注入来将类或接口<em>注入</em>至其他类。还显示了如何将 CDI <em>限定符</em>应用到代码,以便可以指定应该在给定注入点注入的类类型。</p> |
| |
| <p>NetBeans IDE 为上下文和依赖关系注入提供了内置支持,包括在项目创建时构建 <code>beans.xml</code> CDI 配置文件的选项,为标注提供的编辑器和导航支持,以及用于创建常用 CDI 工件的各种向导。</p> |
| |
| <br style="clear:left;"> |
| |
| <div class="indent"> |
| <p>要学完本教程,您需要具备以下软件和资源。</p> |
| |
| <table id="requiredSoftware"> |
| <tbody> |
| <tr> |
| <th class="tblheader" scope="col">软件或资源</th> |
| <th class="tblheader" scope="col">要求的版本</th> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td> |
| <td class="tbltd1">7.2、7.3、7.4、8.0、Java EE 版本</td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">Java 开发工具包 (JDK)</a></td> |
| <td class="tbltd1">版本 7 或 8</td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://glassfish.dev.java.net/">GlassFish Server</a></td> |
| <td class="tbltd1">Open Source Edition 3.x 或 4.x</td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FcdiDemo.zip">cdiDemo.zip</a></td> |
| <td class="tbltd1">N/A</td> |
| </tr> |
| </tbody> |
| </table> |
| |
| <p class="notes"><strong>注:</strong></p> |
| </div> |
| |
| <ul> |
| <li>NetBeans IDE Java 包中还含 GlassFish Server Open Source Edition,后者是与 Java EE 兼容的容器。</li> |
| |
| <li>可以下载本教程的解决方案样例项目:<a href="https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FcdiDemo2.zip">cdiDemo2.zip</a></li> |
| </ul> |
| |
| |
| <br> |
| <h2 id="inject">注入:CDI 中的 "I"</h2> |
| |
| <p>CDI 是一个用于注入上下文和依赖关系的 API。在 Seam 和 Spring 中,依赖关系主要通过命名 Bean 并根据名称将它们绑定到注入点以发挥作用。如果是在完成<a href="cdi-intro.html">上下文和依赖关系注入以及 JSF 2.0 入门指南</a>以后开始学习本教程,则使用 <code>@Named</code> 标注定义 Bean 名称时,仅限从 JSF 页按名称引用受管 Bean。<code>@Named</code> 标注的主要作用是定义 Bean,以在应用程序内解析 EL 语句(通常是通过 JSF EL 解析器完成)。<em>可以</em>通过使用名称执行注入,但 CDI 注入并非按照此方式工作,因为 CDI 表示注入点和要注入的 Bean 的方式更为丰富。</p> |
| |
| <p>在以下示例中,将创建 <code>ItemProcessor</code>,该程序从实现 <code>ItemDao</code> 接口的类获得项列表。使用 CDI 的 <code>@Inject</code> 标注演示如何将 Bean <em>注入</em>另一个类。下图描绘您在本练习中构建的方案。</p> |
| |
| <div class="indent"> |
| <img alt="此 CDI 图显示了在本练习中创建的对象" class="margin-around" src="../../../images_www/articles/73/javaee/cdi-inject/cdi-diagram-inject.png" title="在应用程序中使用 CDI 注入对类进行松散耦合"> |
| </div> |
| |
| <p class="tips">DAO 表示<em>数据访问对象</em>。</p> |
| |
| <ol> |
| <li>首先,从 <code>cdiDemo.zip</code> 文件提取样例启动项目(请参见上面的<a href="#requiredSoftware">所需资源列表</a>)。在 IDE 中打开项目,方法是选择 "File"(文件)> "Open Project"(打开项目)(Ctrl-Shift-O 组合键;在 Mac 上为 ⌘-Shift-O 组合键),然后从计算机上的相应位置选择该项目。</li> |
| <li>右键单击“项目”窗口中的项目节点,然后选择“属性”。</li> |
| <li>选择 "Run"(运行)类别,并确认在 "Server"(服务器)下拉列表中选定 GlassFish 实例。 </li> |
| |
| <li>创建新 <code>Item</code> 类,并将其存储在名为 <code>exercise2</code> 的新包中。单击 "New File"(新建文件)(<img alt=""New File"(新建文件)按钮" src="../../../images_www/articles/73/javaee/cdi-common/new-file-btn.png">) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。</li> |
| |
| <li>选择 "Java" 类别,然后选择 "Java Class"(Java 类)。单击 "Next"(下一步)。</li> |
| |
| <li>输入 <strong>Item</strong> 作为类名,然后键入 <strong>exercise2</strong> 作为包。(新包在完成向导时创建。) <br> <img alt="Java 类向导" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/java-class-wizard.png" title="使用文件向导创建新 Java 类"></li> |
| |
| <li>单击 "Finish"(完成)。新类和包生成,并在编辑器中打开 <code>Item</code> 类。</li> |
| |
| <li>为 <code>Item</code> POJO 创建 <code>value</code> 和 <code>limit</code> 属性,并实现 <code>toString()</code> 方法。在该类中添加以下内容。 |
| |
| <pre class="examplecode"> |
| public class Item { |
| |
| <strong>private int value; |
| private int limit; |
| |
| @Override |
| public String toString() { |
| return super.toString() + String.format(" [Value=%d, Limit=%d]", value,limit); |
| }</strong> |
| }</pre></li> |
| |
| <li>在该类中添加 getter 和 setter 方法。要执行此操作,请确保将光标置于类定义之间(例如,该类的花括号之间),然后右键单击编辑器并选择 "Insert Code"(插入代码)(Alt-Insert;在 Mac 上为 Ctrl-I)。选择 "Getter and Setter"(Getter 和 Setter)。 <br> <img alt=""Insert Code"(插入代码)弹出式窗口" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/insert-code.png" title="使用 "Insert Code"(插入代码)弹出式窗口创建 getter 和 setter"></li> |
| |
| <li>选中 <code>Item</code> 复选框(执行此操作可选择该类中包含的所有属性)。 <br> <img alt=""Generate Getters and Setters"(生成 getter 和 setter)对话框" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/generate-getters-setters.png" title="选中类复选框可选择该类中包含的所有属性"></li> |
| |
| <li>单击 "Generate"(生成)。将为该类生成 getter 和 setter 方法。 |
| |
| <pre class="examplecode"> |
| public class Item { |
| |
| private int value; |
| private int limit; |
| |
| <strong>public int getLimit() { |
| return limit; |
| } |
| |
| public void setLimit(int limit) { |
| this.limit = limit; |
| } |
| |
| public int getValue() { |
| return value; |
| } |
| |
| public void setValue(int value) { |
| this.value = value; |
| }</strong> |
| |
| @Override |
| public String toString() { |
| return super.toString() + String.format(" [Value=%d, Limit=%d]", value, limit); |
| } |
| }</pre></li> |
| |
| <li>创建同时具有 <code>value</code> 和 <code>limit</code> 参数的构造函数。同样,IDE 可以帮助完成此操作。在类定义内按 Ctrl-空格键,并选择 <code>Item(int value, int limit) - generate</code> 选项。<br> <img alt="显示在编辑器中的代码完成列表" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/generate-constructor.png" title="按 Ctrl-空格键可利用编辑器的代码完成功能"> <br> 下列构造函数将添加到类中。 |
| |
| <pre class="examplecode"> |
| public class Item { |
| |
| <strong>public Item(int value, int limit) { |
| this.value = value; |
| this.limit = limit; |
| }</strong> |
| |
| private int value; |
| private int limit; |
| |
| ...</pre></li> |
| |
| <li>创建 <code>ItemDao</code> 接口以定义获取 <code>Item</code> 对象列表的方式。在此测试应用程序中,预期将使用多个实现,因此将编写多个接口的代码。 |
| |
| <p> |
| 单击 "New File"(新建文件)(<img alt=""New File"(新建文件)按钮" src="../../../images_www/articles/73/javaee/cdi-common/new-file-btn.png">) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。</p></li> |
| |
| <li>选择 "Java" 类别,然后选择 "Java Interface"(Java 接口)。单击 "Next"(下一步)。</li> |
| |
| <li>键入 <strong>ItemDao</strong> 作为类名,然后输入 <strong>exercise2</strong> 作为包。</li> |
| |
| <li>单击 "Finish"(完成)。将会生成新接口并在编辑器中将其打开。</li> |
| |
| <li>添加名为 <code>fetchItems()</code> 的方法,它将返回 <code>Item</code> 对象的 <code>List</code>。 |
| |
| <pre class="examplecode"> |
| public interface ItemDao { |
| |
| <strong>List<Item> fetchItems();</strong> |
| |
| }</pre> |
| (使用编辑器的提示为 <code>java.util.List</code> 添加 import 语句。)</li> |
| |
| <li>创建 <code>ItemProcessor</code> 类。这是要向其中注入 Bean 并从中执行进程的主类。目前,您将从 DAO 入手,了解如何将其注入我们的处理器 Bean。 |
| |
| <p> |
| 单击 "New File"(新建文件)(<img alt=""New File"(新建文件)按钮" src="../../../images_www/articles/73/javaee/cdi-common/new-file-btn.png">) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。</p></li> |
| |
| <li>选择 "Java" 类别,然后选择 "Java Class"(Java 类)。单击 "Next"(下一步)。</li> |
| |
| <li>键入 <strong>ItemProcessor</strong> 作为类名,然后输入 <strong>exercise2</strong> 作为包。单击 "Finish"(完成)。 |
| <p>将会生成新类并在编辑器中将其打开。</p></li> |
| |
| <li>修改该类,如下所示: |
| |
| <pre class="examplecode"> |
| @Named |
| @RequestScoped |
| public class ItemProcessor { |
| |
| private ItemDao itemDao; |
| |
| public void execute() { |
| List<Item> items = itemDao.fetchItems(); |
| for (Item item : items) { |
| System.out.println("Found item " + item); |
| } |
| } |
| }</pre></li> |
| |
| <li>修复导入。在编辑器中右键单击并选择 "Fix Imports"(修复导入),或者按 Ctrl-Shift-I 组合键(在 Mac 上按 ⌘-Shift-I 组合键)。 <br> <img alt=""Fix Imports"(修复导入)对话框" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/fix-imports.png" title="右键单击编辑器,然后选择 "Fix Imports"(修复导入)以将 import 语句添加到类中"></li> |
| |
| <li>单击 "OK"(确定)。需要以下类的 import 语句: |
| |
| <ul> |
| <li><code>java.util.List</code></li> |
| <li><code>javax.inject.Named</code></li> |
| <li><code>javax.enterprise.context.RequestScoped</code></li> |
| </ul></li> |
| |
| <li>首先是一个简单的 DAO,仅用于创建项列表并返回项的固定列表。<br><br> 在 "Projects"(项目)窗口中,右键单击 <code>exercise2</code> 包节点并选择 "New"(新建)> "Java Class"(Java 类)。在 Java 类向导中,将类命名为 <code>DefaultItemDao</code>。单击 "Finish"(完成)。<img alt="Java 类向导" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/java-class-wizard2.png" title="使用 Java 类向导创建新 Java 类"></li> |
| |
| <li>在编辑器中,让 <code>DefaultItemDao</code> 实现 <code>ItemDao</code> 接口,然后提供 <code>fetchItems()</code> 实现。 |
| |
| <pre class="examplecode"> |
| public class DefaultItemDao <strong>implements ItemDao</strong> { |
| |
| <strong>@Override |
| public List<Item> fetchItems() { |
| List<Item> results = new ArrayList<Item>(); |
| results.add(new Item(34, 7)); |
| results.add(new Item(4, 37)); |
| results.add(new Item(24, 19)); |
| results.add(new Item(89, 32)); |
| return results; |
| }</strong> |
| }</pre> |
| (按 Ctrl-Shift-I 组合键(在 Mac 上按 ⌘-Shift-I 组合键)为 <code>java.util.List</code> 和 <code>java.util.ArrayList</code> 添加 import 语句。)</li> |
| |
| <li>切换到 <code>ItemProcessor</code> 类(按 Ctrl-Tab 组合键)。为了将 <code>DefaultItemDao</code> 注入到 <code>ItemProcessor</code>,我们向 <code>ItemDao</code> 字段添加 <code>javax.inject.Inject</code> 标注以表示该字段为注入点。 |
| |
| <pre class="examplecode"> |
| <strong>import javax.inject.Inject;</strong> |
| ... |
| |
| @Named |
| @RequestScoped |
| public class ItemProcessor { |
| |
| <strong>@Inject</strong> |
| private ItemDao itemDao; |
| |
| ... |
| }</pre> |
| <span class="tips">使用编辑器的代码完成支持向类中添加 <code>@Inject</code> 标注和 import 语句。例如,键入 <code>@Inj</code>,按后按 Ctrl-空格组合键。</span></li> |
| |
| <li>最后,需要采用一些方式来调用 <code>ItemProcessor</code> 上的 <code>execute()</code> 方法。此方法可以在 SE 环境中运行,但现在会将其保留在 JSF 页。创建名为 <code>process.xhtml</code> 的新页,并包含用于调用 <code>execute()</code> 方法的按钮。<br><br> 单击 "New File"(新建文件)(<img alt=""New File"(新建文件)按钮" src="../../../images_www/articles/73/javaee/cdi-common/new-file-btn.png">) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。</li> |
| |
| <li>选择 "JavaServer Faces" 类别,然后选择 "JSF Page"(JSF 页)。单击 "Next"(下一步)。</li> |
| |
| <li>键入 <strong>process</strong> 作为文件名,然后单击 "Finish"(完成)。 <br> <img alt="JSF 页向导" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/new-jsf-page.png" title="使用 JSF 文件向导创建新 Facelets 页"></li> |
| |
| <li>在新的 <code>process.xhtml</code> 文件中,添加连接到 <code>ItemProcessor.execute()</code> 方法的按钮。使用 EL 时,受管 Bean 的默认名称与类名称相同,但是第一个字母用小写(例如,<code>itemProcessor</code>)。 |
| |
| <pre class="examplecode"> |
| <h:body> |
| <strong><h:form> |
| <h:commandButton action="#{itemProcessor.execute}" value="Execute"/> |
| </h:form></strong> |
| </h:body></pre></li> |
| |
| <li>运行此项目之前,在项目的 Web 部署描述符中将 <code>process.xhtml</code> 文件设置为新的欢迎页面。<br><br> 使用 IDE 的 "Go to File"(转至文件)对话框快速打开 <code>web.xml</code> 文件。从 IDE 的主菜单中选择 "Navigate"(导航)> "Go to File"(转至文件)(Alt-Shift-O;在 Mac 上为 Ctrl-Shift-O),然后键入 "<code>web</code>"。 <br> <img alt=""Go to File"(转至文件)对话框" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/go-to-file.png" title="使用 "Go to File"(转至文件)对话框可快速找到项目文件"></li> |
| |
| <li>单击 "OK"(确定)。在 <code>web.xml</code> 文件的 XML 视图中,进行以下更改。 |
| |
| <pre class="examplecode"> |
| <welcome-file-list> |
| <welcome-file>faces/<strong>process.xhtml</strong></welcome-file> |
| </welcome-file-list></pre></li> |
| |
| <li>在 IDE 的主工具栏中单击 "Run Project"(运行项目)(<img alt=""Run Project"(运行项目)按钮" src="../../../images_www/articles/73/javaee/cdi-common/run-project-btn.png">) 按钮。编译该项目并将其部署到 GlassFish,然后在浏览器中打开 <code>process.xhtml</code> 文件。</li> |
| |
| <li>单击页面上显示的 <code>Execute</code> 按钮。切换回 IDE 并检查 GlassFish Server 日志。服务器日志会显示在 "Output"(输出)窗口(Ctrl-4 组合键;在 Mac 上为 ⌘-4 组合键)中 "GlassFish Server" 标签的下方。单击该按钮时,日志将列出默认 DAO 实现的项。<br> <img alt=""Output"(输出)窗口中的 GlassFish Server 日志" class="margin-around b-all" src="../../../images_www/articles/73/javaee/cdi-inject/output-window.png" title="在 IDE 的 "Output"(输出)窗口中查看服务器日志"> <br> <span class="tips">在 "Output"(输出)窗口中右键单击,然后选择 "Clear"(清除)(Ctrl-L 组合键;在 Mac 上为 ⌘-L 组合键)以清除日志。在上图中,仅在单击 <code>Execute</code> 按钮前清除日志。</span></li> |
| </ol> |
| |
| <p>我们创建了一个实现 <code>ItemDao</code> 接口的类,然后在部署应用程序时,由 CDI 实现来处理模块中的受管 Bean(由于模块中的 <code>beans.xml</code> 文件)。<code>@Inject</code> 标注指定要将受管 Bean 注入该字段,而我们只知道可注入 Bean 必须实现 <code>ItemDao</code> 或该接口的一些子类型。在这种情况下,<code>DefaultItemDao</code> 类非常符合要求。</p> |
| |
| <p>如果注入了多个 <code>ItemDao</code> 实现,会怎么样?CDI 可能不知道应该选择哪个实现,会标记部署时错误。要解决此问题,需要使用 CDI 限定符。限定符将在以下部分进行探讨。</p> |
| |
| <br> |
| <h2 id="qualifier">使用限定符</h2> |
| |
| <p>CDI 限定符是一个标注,可在类级别应用以表示该类所属的 Bean 类型,还可以在字段级别(在其他位置)应用以表示该点需要注入哪种类型的 Bean。</p> |
| |
| <p>为了演示在我们构建的应用程序中需要限定符,我们向还会实现 <code>ItemDao</code> 接口的应用程序中添加另一个 DAO 类。下图描述了本练习中将要构建的方案。CDI 必须能够确定在注入点应该使用哪个 Bean 实现。因为有两个 <code>ItemDao</code> 实现,我们可以通过创建名为 <code>Demo</code> 的限定符来解决此问题。然后,使用 <code>@Demo</code> 标注对要使用的 Bean 以及 <code>ItemProcessor</code> 中的注入点添加“标记”。</p> |
| |
| <div class="indent"> |
| <img alt="此 CDI 图显示在本教程中创建的对象" class="margin-around" src="../../../images_www/articles/73/javaee/cdi-inject/cdi-diagram-qualify.png" title="在应用程序中使用 CDI 注入和限定符对类进行松散耦合"> |
| </div> |
| |
| <p>请执行以下步骤。</p> |
| |
| <ol> |
| <li>在 "Projects"(项目)窗口中,右键单击 <code>exercise2</code> 包,并选择 "New"(新建)> "Java Class"(Java 类)。</li> |
| |
| <li>在新建 Java 类向导中,将新类命名为 <strong>AnotherItemDao</strong>,然后单击 "Finish"(完成)。将会生成新类并在编辑器中将其打开。</li> |
| |
| <li>按如下方式修改类,以实现 <code>ItemDao</code> 接口,并定义该接口的 <code>fetchItems()</code> 方法。 |
| |
| <pre class="examplecode"> |
| public class AnotherItemDao <strong>implements ItemDao</strong> { |
| |
| <strong>@Override |
| public List<Item> fetchItems() { |
| List<Item> results = new ArrayList<Item>(); |
| results.add(new Item(99, 9)); |
| return results; |
| }</strong> |
| }</pre> |
| <p>请务必为 <code>java.util.List</code> 和 <code>java.util.ArrayList</code> 添加 import 语句。要执行此操作,请在编辑器中右键单击,然后选择 "Fix Imports"(修复导入),或者按 Ctrl-Shift-I 组合键(在 Mac 上按 ⌘-Shift-I 组合键)。</p> |
| |
| <p> |
| 现在有两个实现 <code>ItemDao</code> 的类,因此无法确定要注入哪个 Bean。</p></li> |
| |
| <li>单击 "Run Project"(运行项目)(<img alt=""Run Project"(运行项目)按钮" src="../../../images_www/articles/73/javaee/cdi-common/run-project-btn.png">) 按钮以运行项目。请注意,项目现在无法部署。 |
| <p class="tips">您可能只需要保存文件,因为 "Deploy on Save"(在保存时部署)默认为启用状态,IDE 将自动部署项目。</p></li> |
| |
| <li>在 "Output"(输出)窗口(Ctrl-4 组合键;在 Mac 上为 ⌘-4 组合键)中查看服务器日志。将会显示类似如下的错误消息。 |
| |
| <pre class="examplecode">Caused by: org.jboss.weld.DeploymentException: Injection point has ambiguous dependencies. |
| Injection point: field exercise2.ItemProcessor.itemDao; |
| Qualifiers: [@javax.enterprise.inject.Default()]; |
| Possible dependencies: [exercise2.DefaultItemDao, exercise2.AnotherItemDao]</pre> |
| |
| <p class="tips">要在 "Output"(输出)窗口中将文本调整为多行,请右键单击并选择 "Wrap text"(自动换行)。此操作不需要水平滚动。</p> |
| |
| <p> |
| Weld(CDI 实现)提供了一个不明确的依赖关系错误含义,它不能确定为给定注入点使用哪个 Bean。在 Weld 中,CDI 注入可能发生的绝大多数(如果不是所有)错误会在部署时报告,甚至包含钝化 Bean 是否会丢失 <code>Serializable</code> 实现。</p> |
| |
| <p> |
| 可以指定 <code>ItemProcessor</code> 中的 <code>itemDao</code> 字段作为一个特定类型与其中一个实现类型(<code>AnotherItemDao</code> 或 <code>DefaultItemDao</code>)匹配,因为它只会与一个类类型匹配。但是,以后将不能对接口进行编码,也很难在不更改字段类型的情况下更改实现。查看 CDI 限定符是更好的解决方法。</p> |
| |
| <p> |
| 当 CDI 检查注入点以找到合适的注入 Bean 时,它会同时考虑类类型和任何限定符。在不知道的情况下,我们已经使用了一个名为 <code>@Any</code> 的默认限定符。现在需要创建一个 <code>@Demo</code> 限定符以应用于 <code>DefaultItemDao</code> 实现,以及 <code>ItemProcessor</code> 中的注入点。</p> |
| |
| <p> |
| IDE 提供可用于生成 CDI 限定符的向导。</p></li> |
| |
| <li>单击 "New File"(新建文件)(<img alt=""New File"(新建文件)按钮" src="../../../images_www/articles/73/javaee/cdi-common/new-file-btn.png">) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。</li> |
| |
| <li>选择 "Context and Dependency Injection"(上下文和依赖关系注入)类别,然后选择 "Qualifier Type"(限定符类型)。单击 "Next"(下一步)。</li> |
| |
| <li>输入 <strong>Demo</strong> 作为类名,然后输入 <strong>exercise2</strong> 作为包名。</li> |
| |
| <li>单击 "Finish"(完成)。新 <code>Demo</code> 限定符在编辑器中打开。 |
| |
| <pre class="examplecode"> |
| package exercise2; |
| |
| import static java.lang.annotation.ElementType.TYPE; |
| import static java.lang.annotation.ElementType.FIELD; |
| import static java.lang.annotation.ElementType.PARAMETER; |
| import static java.lang.annotation.ElementType.METHOD; |
| import static java.lang.annotation.RetentionPolicy.RUNTIME; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.Target; |
| import javax.inject.Qualifier; |
| |
| /** |
| * |
| * @author nbuser |
| */ |
| @Qualifier |
| @Retention(RUNTIME) |
| @Target({METHOD, FIELD, PARAMETER, TYPE}) |
| public @interface Demo { |
| }</pre> |
| |
| <p>接下来,在类级别将此限定符添加到默认 DAO 实现。</p> |
| </li> |
| <li>在编辑器中切换到 <code>DefaultItemDao</code>(按 Ctrl-Tab 组合键),然后在类定义上方键入 <code>@Demo</code>。 |
| |
| <pre class="examplecode"> |
| <strong>@Demo</strong> |
| public class DefaultItemDao implements ItemDao { |
| |
| @Override |
| public List<Item> fetchItems() { |
| List<Item> results = new ArrayList<Item>(); |
| results.add(new Item(34, 7)); |
| results.add(new Item(4, 37)); |
| results.add(new Item(24, 19)); |
| results.add(new Item(89, 32)); |
| return results; |
| } |
| }</pre> |
| |
| <span class="tips">键入 <code>@</code> 后,按 Ctrl-空格键以调用代码完成建议。编辑器识别 <code>Demo</code> 限定符并列出 <code>@Demo</code> 作为代码完成选项。</span></li> |
| |
| <li>单击 "Run Project"(运行项目)(<img alt=""Run Project"(运行项目)按钮" src="../../../images_www/articles/73/javaee/cdi-common/run-project-btn.png">) 按钮以运行项目。该项目将构建和部署,且不出现错误。 |
| <p class="notes"><strong>注:</strong>对于此项修改,可能需要显式运行项目以重新部署应用程序而不是增量部署所做的更改。</p></li> |
| |
| <li>在浏览器中,单击 <code>Execute</code> 按钮,然后返回至 IDE 并检查 "Output"(输出)窗口中的服务器日志。将看到以下输出结果。 |
| |
| <pre class="examplecode">INFO: Found item exercise2.Item@1ef62a93 [Value=99, Limit=9]</pre> |
| |
| <p>输出列出了 <code>AnotherItemDao</code> 类中的项。请回想一下,我们对 <code>DefaultItemDao</code> 实现进行了标注,但没有对 <code>ItemProcessor</code> 中的注入点进行标注。通过向默认 DAO 实现添加 <code>@Demo</code> 限定符,使另一个实现更加匹配注入点,因为该实现同时与类型和限定符匹配。当前,<code>DefaultItemDao</code> 有 <code>Demo</code> 限定符,而注入点上没有,因此降低了适用性。</p> |
| |
| <p>接下来,将向 <code>ItemProcessor</code> 中的注入点添加 <code>@Demo</code> 标注。</p></li> |
| <li>在编辑器中切换到 <code>ItemProcessor</code>(按 Ctrl-Tab 组合键),然后进行以下更改。 |
| |
| <pre class="examplecode"> |
| @Named |
| @RequestScoped |
| public class ItemProcessor { |
| |
| @Inject <strong>@Demo</strong> |
| private ItemDao itemDao; |
| |
| public void execute() { |
| List<Item> items = itemDao.fetchItems(); |
| for (Item item : items) { |
| System.out.println("Found item " + item); |
| } |
| } |
| }</pre></li> |
| |
| <li>在浏览器中,单击 <code>Execute</code> 按钮,然后返回至 IDE 并检查 "Output"(输出)窗口中的服务器日志。您会再次看到默认实现 (<code>DefaultItemDao</code>) 的输出。 |
| |
| <pre class="examplecode"> |
| INFO: Found item exercise2.Item@7b3640f1 [Value=34, Limit=7] |
| INFO: Found item exercise2.Item@26e1cd69 [Value=4, Limit=37] |
| INFO: Found item exercise2.Item@3274bc70 [Value=24, Limit=19] |
| INFO: Found item exercise2.Item@dff76f1 [Value=89, Limit=32]</pre> |
| |
| <p>这是因为现在是根据类型<em>和</em>限定符进行匹配,且 <code>DefaultItemDao</code> 是唯一同时具有正确类型和 <code>@Demo</code> 标注的 Bean。</p></li> |
| </ol> |
| |
| |
| <br> |
| <h2 id="alternative">其他注入方法</h2> |
| |
| <p>有多种方式可以在注入的类上定义注入点。到目前为止,您对引用注入对象的字段添加了标注。不需要为字段注入提供 getter 和 setter。如果要使用最终字段创建不可变受管 Bean,可以通过使用 <code>@Inject</code> 标注对构造函数进行标注,在构造函数中使用注入。然后,可以对构造函数参数应用任何标注以限定要注入的 Bean。(当然,每个参数都有一个类型可帮助限定要注入的 Bean。)Bean 可能只有一个定义了注入点的构造函数,但是可以实现多个构造函数。</p> |
| |
| <div class="indent"> |
| <pre class="examplecode"> |
| @Named |
| @RequestScoped |
| public class ItemProcessor { |
| |
| private final ItemDao itemDao; |
| |
| @Inject |
| public ItemProcessor(@Demo ItemDao itemDao) { |
| this.itemDao = itemDao; |
| } |
| }</pre> |
| </div> |
| |
| <p>此外,还可以调用初始化方法,为该方法传递要注入的 Bean。</p> |
| |
| <div class="indent"> |
| <pre class="examplecode"> |
| @Named |
| @RequestScoped |
| public class ItemProcessor { |
| |
| private ItemDao itemDao; |
| |
| @Inject |
| public void setItemDao(@Demo ItemDao itemDao) { |
| this.itemDao = itemDao; |
| } |
| }</pre> |
| </div> |
| |
| <p>虽然在上例中使用了 setter 方法进行初始化,但您可以创建任何方法,并使用它对方法调用中任意数量的 Bean 进行初始化。您还可以在一个 Bean 中使用多个初始化方法。</p> |
| |
| <div class="indent"> |
| <pre class="examplecode"> |
| @Inject |
| public void initBeans(@Demo ItemDao itemDao, @SomeQualifier SomeType someBean) { |
| this.itemDao = itemDao; |
| this.bean = someBean; |
| }</pre> |
| </div> |
| |
| <p>无论注入点是如何定义的,都可以将同样的规则应用于匹配的 Bean。CDI 会尝试根据类型和限定符查找最佳匹配,并且会在注入点有多个匹配 Bean 或没有匹配 Bean 时部署失败。</p> |
| |
| |
| <div class="feedback-box"> |
| <a href="/about/contact_form.html?to=3&subject=Feedback:%20Working%20with%20Injection%20and%20Qualifiers%20in%20CDI">发送有关此教程的反馈意见</a> |
| </div> |
| |
| <br style="clear:both;"> |
| |
| |
| <h2 id="seealso">另请参见</h2> |
| |
| <p>请继续完成本系列中关于上下文和依赖关系注入的下一个部分:</p> |
| |
| <ul> |
| <li><a href="cdi-validate.html">应用 @Alternative Bean 和生命周期标注</a></li> |
| </ul> |
| |
| <p>有关 CDI 和 Java EE 的详细信息,请参见以下资源。</p> |
| |
| <ul> |
| <li><a href="cdi-intro.html">上下文和依赖关系注入以及 JSF 2.0 入门指南</a></li> |
| <li><a href="javaee-gettingstarted.html">Java EE 应用程序入门指南</a></li> |
| <li><a href="http://blogs.oracle.com/enterprisetechtips/entry/using_cdi_and_dependency_injection">企业技术提示:在 JSF 2.0 应用程序中使用面向 Java 的 CDI 和依赖关系注入</a></li> |
| <li><a href="http://download.oracle.com/javaee/6/tutorial/doc/gjbnr.html">Java EE 6 教程第五部分:面向 Java EE 平台的上下文和依赖关系注入</a></li> |
| <li><a href="http://jcp.org/en/jsr/detail?id=299">JSR 299:上下文和依赖关系注入规范</a></li> |
| </ul> |
| |
| </body> |
| </html> |