应用 @Alternative Bean 和生命周期标注

撰稿人:Andy Gibson

此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0

JSR-299 指定的上下文和依赖关系注入 (CDI) 是 Java EE 6 的一个组成部分,提供了一个体系结构,以允许 Java EE 组件(例如 Servlet、企业 Bean 和 JavaBeans)在具有明确定义范围的应用程序生命周期内存在。此外,CDI 服务允许 Java EE 组件(例如 EJB 会话 Bean 和 JavaServer Faces (JSF) 受管 Bean)注入并通过触发和观察事件以松散耦合的方式进行交互。

本教程基于 Andy Gibson 发布的博客,标题为:CDI 入门指南,第 2 部分 - 注入。本教程演示如何使用 @Alternative 标注配置应用程序用于不同的部署,还将演示如何使用受管 Bean 生命周期标注(例如,@PostConstruct@PreDestroy)将 CDI 注入和 Java EE 6 受管 Bean 规范提供的功能结合起来。

NetBeans IDE 为上下文和依赖关系注入提供了内置支持,包括在项目创建时构建 beans.xml CDI 配置文件的选项,为标注提供的编辑器和导航支持,以及用于创建常用 CDI 工件的各种向导。


要学完本教程,您需要具备以下软件和资源。

软件或资源 要求的版本
NetBeans IDE 7.2、7.3、7.4、8.0、Java EE 版本
Java 开发工具包 (JDK) 版本 7 或 8
GlassFish Server Open Source Edition 3.x 或 4.x
cdiDemo2.zip N/A

注:

  • NetBeans IDE Java 包中还含 GlassFish Server Open Source Edition,后者是与 Java EE 兼容的容器。
  • 可以下载本教程的解决方案样例项目:cdiDemo3.zip

处理多个部署

CDI 提供 @Alternative 标注,通过该标注可以包含与注入点匹配的多个 Bean 而不会出现多义性错误。换句话说,可以将 @Alternative 标注应用至两个或多个 Bean,然后,根据部署指定要在 CDI 的 beans.xml 配置文件中使用的 Bean。

为演示此情况,请参见下面的方案。我们将 ItemValidator 注入 ItemProcessor 主类。ItemValidatorDefaultItemValidatorRelaxedItemValidator 实现。根据部署要求,在大多数情况下我们会使用 DefaultItemValidator,但在特定部署中,还需要 RelaxedItemValidator。为解决此问题,我们对两个 Bean 添加标注,然后通过向应用程序的 beans.xml 文件添加条目,指定给定部署要使用的 Bean。

此 CDI 图显示了在本练习中创建的对象
  1. 首先,从 cdiDemo2.zip 文件提取样例启动项目(请参见上面的所需资源列表)。在 IDE 中打开项目,方法是选择 "File"(文件)> "Open Project"(打开项目)(Ctrl-Shift-O 组合键;在 Mac 上为 ⌘-Shift-O 组合键),然后从计算机上的相应位置选择该项目。
  2. 右键单击“项目”窗口中的项目节点,然后选择“属性”。
  3. 选择 "Run"(运行)类别,并确认在 "Server"(服务器)下拉列表中选定 GlassFish 实例。
  4. 创建 ItemValidator 接口。

    单击 "New File"(新建文件)("New File"(新建文件)按钮) 按钮,或者按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)以打开文件向导。
  5. 选择 "Java" 类别,然后选择 "Java Interface"(Java 接口)。单击 "Next"(下一步)。
  6. 键入 ItemValidator 作为类名,然后输入 exercise3 作为包。
  7. 单击 "Finish"(完成)。将会生成新接口并在编辑器中将其打开。
  8. 添加名为 isValid() 的方法,以提取 Item 对象并返回 boolean 值。
    public interface ItemValidator {
        boolean isValid(Item item);
    }
    (使用编辑器的提示为 exercise2.Item 添加 import 语句。)
  9. 扩展 ItemProcessor 类以包含新功能。在编辑器中打开 ItemProcessor,并进行以下更改。
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject @Demo
        private ItemDao itemDao;
    
        @Inject
        private ItemValidator itemValidator;
    
        public void execute() {
          List<Item>  items = itemDao.fetchItems();
          for (Item item : items) {
              System.out.println("Item = " + item + " valid = " + itemValidator.isValid(item));
          }
        }
    }

    使用编辑器的提示为 exercise3.ItemValidator 添加 import 语句。

  10. 创建一个 ItemValidator 实现,名为 DefaultItemValidator,该实现根据值简单地对 limit 进行测试。

    在 "Projects"(项目)窗口中,右键单击 exercise3 包,并选择 "New"(新建)> "Java Class"(Java 类)。将该类命名为 DefaultItemValidator,然后单击 "Finish"(完成)。

  11. DefaultItemValidator 实现 ItemValidator 并覆盖 isValid() 方法,如下所示。
    public class DefaultItemValidator implements ItemValidator {
    
        @Override
        public boolean isValid(Item item) {
            return item.getValue() < item.getLimit();
        }
    }

    (使用编辑器的提示为 exercise2.Item 添加 import 语句。)

  12. 在 IDE 的主工具栏中单击 "Run Project"(运行项目)("Run Project"(运行项目)按钮) 按钮。编译该项目并将其部署到 GlassFish,然后在浏览器中打开应用程序的欢迎页 (process.xhtml)。
  13. 单击页面上显示的 Execute 按钮。切换回 IDE 并检查 GlassFish Server 日志。服务器日志会显示在 "Output"(输出)窗口(Ctrl-4 组合键;在 Mac 上为 ⌘-4 组合键)中 "GlassFish" 标签的下方。然后会看到验证项,并列出唯一一个值小于 limit 的有效项。
    INFO: Item =  [Value=34, Limit=7] valid = false
    INFO: Item =  [Value=4, Limit=37] valid = true
    INFO: Item =  [Value=24, Limit=19] valid = false
    INFO: Item =  [Value=89, Limit=32] valid = false
    "Output"(输出)窗口 - GlassFish Server 日志
  14. 现在,请考虑以下情况,假定您必须部署到另一个更松散的站点,且仅当项值大于 limit 的两倍时,才将该项视为无效。您可能需要使用另一个 Bean 为该逻辑实现 ItemValidator 接口。

    创建一个新的 ItemValidator 实现,名为 RelaxedItemValidator。在 "Projects"(项目)窗口中,右键单击 exercise3 包,并选择 "New"(新建)> "Java Class"(Java 类)。将该类命名为 RelaxedItemValidator,然后单击 "Finish"(完成)。

  15. RelaxedItemValidator 实现 ItemValidator 并覆盖 isValid() 方法,如下所示。
    public class RelaxedItemValidator implements ItemValidator {
    
        @Override
        public boolean isValid(Item item) {
            return item.getValue() < (item.getLimit() * 2);
        }
    }

    (使用编辑器的提示为 exercise2.Item 添加 import 语句。)

  16. 单击 "Run Project"(运行项目)("Run Project"(运行项目)按钮) 按钮以运行项目。请注意,项目现在无法部署。
  17. 在 "Output"(输出)窗口(Ctrl-4 组合键;在 Mac 上为 ⌘-4 组合键)中查看服务器日志。您会看到一则错误消息,报告 "ambiguous dependency"(依赖关系不明确)问题。出现此错误的原因是,您现在有两个实现同一接口的类。
    org.glassfish.deployment.common.DeploymentException: Injection point has ambiguous dependencies.
    Injection point: field exercise2.ItemProcessor.itemValidator;
    Qualifiers: [@javax.enterprise.inject.Default()];
    Possible dependencies: [exercise3.RelaxedItemValidator, exercise3.DefaultItemValidator]

    Weld(CDI 实现)无法确定对给定注入点使用 RelaxedItemValidator 还是使用 DefaultItemValidator

    如前面所述,唯一的区别在于部署。对于大多数部署,需要使用默认验证器,但对于一个部署,需要使用 "relaxed" 实现。CDI 提供 @Alternative 标注,通过该标注可以包含与一个注入点匹配的多个 Bean 而不会出现多义性错误,且要使用的 Bean 在 beans.xml 中定义。这允许您在同一模块中部署这两个实现,唯一的差别在于 beans.xml 的定义,该定义可以根据不同的部署进行更改。

  18. @Alternative 标注和相应的 import 语句添加至 RelaxedItemValidatorDefaultItemValidator

    在编辑器中打开 RelaxedItemValidator 并进行以下更改。
    import javax.enterprise.inject.Alternative;
    ...
    
    @Alternative
    public class RelaxedItemValidator implements ItemValidator {
    
        public boolean isValid(Item item) {
            return item.getValue() < (item.getLimit() * 2);
        }
    }

    键入 Al,然后按 Ctrl-空格组合键以调用代码完成。因为仅过滤了一个选项,将完成 @Alternative 标注,javax.enterprise.inject.Alternative 相应的 import 语句会自动添加到文件顶部。通常情况下,在标注上按 Ctrl-空格组合键还会提供 Javadoc 文档弹出窗口。

    编辑器中的 Javadoc 文档弹出式窗口

    切换至 DefaultItemValidator(按 Ctrl-Tab 组合键)并进行以下更改。

    import javax.enterprise.inject.Alternative;
    ...
    
    @Alternative
    public class DefaultItemValidator implements ItemValidator {
    
        public boolean isValid(Item item) {
            return item.getValue() < item.getLimit();
        }
    }

    如果现在部署了应用程序,则会收到 "unsatisfied dependency"(不符合要求的依赖关系)错误,因为您定义了两个匹配的 Bean 作为替代项,但是没有在 beans.xml 文件中启用这两个文件中的任何一个。

  19. 使用 IDE 的 "Go to File"(转至文件)对话框快速打开 beans.xml 文件。从 IDE 的主菜单(Alt-Shift-O;在 Mac 上为 Ctrl-Shift-O)选择 "Navigate"(导航)> "Go to File"(转至文件),然后键入 "beans"。单击 "OK"(确定)。"Go to File"(转至文件)对话框
  20. beans.xml 文件进行如下更改。
    <beans xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
    
        <alternatives>
            <class>exercise3.RelaxedItemValidator</class>
        </alternatives>
    
    </beans>

    这会通知 CDI 使用 RelaxedItemValidator 进行此部署。可以认为 @Alternative 标注有效禁用了 Bean,使其不能用于注入,但是允许该实现与其他 Bean 一起打包。在 beans.xml 文件中将其添加为替代项可有效地重新启用该 Bean,使其可用于注入。通过将此类型的元数据移动至 beans.xml 文件,可以将不同版本的文件与不同部署一起打包。

  21. 单击 "Run Project"(运行项目)("Run Project"(运行项目)按钮) 按钮以运行项目(或者,按 F6 键;在 Mac 上按 fn-F6 组合键)。在浏览器中,单击页面上显示的 "Execute" 按钮。切换回 IDE 并查看 "Output"(输出)窗口(Ctrl-4 组合键,在 Mac 上为 ⌘-4 组合键)中显示的 GlassFish Server 日志。
    INFO: Item =  [Value=34, Limit=7] valid = false
    INFO: Item =  [Value=4, Limit=37] valid = true
    INFO: Item =  [Value=24, Limit=19] valid = true
    INFO: Item =  [Value=89, Limit=32] valid = false

    您会看到,当第三项显示为有效而提供的值 (24) 大于给定 limit (19) 时,会使用 RelaxedItemValidator 实现。


将生命周期标注应用于受管 Bean

在此示例中,将 ItemErrorHandler 注入到主 ItemProcessor 类。因为 FileErrorReporterItemErrorHandler 接口的唯一实现,所以会选中它作为接口。要为此类设置生命周期特定的操作,请从受管 Bean 规范(包含在 JSR 316:Java Platform, Enterprise Edition 6 规范)使用 @PostConstruct@PreDestroy 标注。

此 CDI 图显示了在本练习中创建的对象

继续执行本示例,创建 ItemErrorHandler 接口以在发现无效的项时对其进行处理。

  1. 在 "Projects"(项目)窗口中,右键单击 exercise3 包,然后选择 "New"(新建)> "Java Interface"(Java 接口)。
  2. 在 Java 接口向导中,键入 ItemErrorHandler 作为类名,然后输入 exercise3 作为包。单击 "Finish"(完成)。

    将会生成新接口并在编辑器中将其打开。

  3. 添加名为 handleItem() 的方法,该方法将 Item 对象作为参数。
    public interface ItemErrorHandler {
        void handleItem(Item item);
    }

    (使用编辑器的提示为 exercise2.Item 添加 import 语句。)

  4. 首先,使用名为 FileErrorReporter 的伪处理程序(该程序将项详细信息保存至文件)实现 ItemErrorHandler

    在 "Projects"(项目)窗口中,右键单击 exercise3 包,并选择 "New"(新建)> "Java Class"(Java 类)。将该类命名为 FileErrorReporter,然后单击 "Finish"(完成)。

  5. FileErrorReporter 实现 ItemErrorHandler 并覆盖 handleItem() 方法,如下所示。
    public class FileErrorReporter implements ItemErrorHandler {
    
        @Override
        public void handleItem(Item item) {
            System.out.println("Saving " + item + " to file");
        }
    }

    (使用编辑器的提示为 exercise2.Item 添加 import 语句。)

    您需要在开始处理项之前打开文件,并在向文件添加内容的过程中使其保持打开状态,然后在完成处理时关闭文件。您可以手动将 initProcess()finishProcess() 方法添加到错误报告程序 Bean,但之后无法向接口添加代码,因为调用程序需要知道这些类特定的方法。您可以将以上这些方法添加到 ItemErrorReporter 接口,但之后必须在实现该接口的每个类中实现这些方法,这就产生了不必要的操作。不过,您可以使用受管 Bean 规范(JSR 316:Java 平台 Enterprise Edition 6 规范中包含)中的一些生命周期标注,在 Bean 生命周期中的某些时点对 Bean 调用方法。当已经构造了 Bean 且 Bean 的任何依赖关系都已注入时,则调用 @PostConstruct 标注的方法。同样,容器会在处理 Bean 之前调用 @PreDestroy 标注的方法。

  6. 添加以下带有相应 @PostConstruct@PreDestroy 标注的 init()release() 方法。
    public class FileErrorReporter implements ItemErrorHandler {
    
        @PostConstruct
        public void init() {
            System.out.println("Creating file error reporter");
        }
    
        @PreDestroy
        public void release() {
            System.out.println("Closing file error reporter");
        }
    
        @Override
        public void handleItem(Item item) {
            System.out.println("Saving " + item + " to file");
        }
    }
  7. 修复导入。在编辑器中右键单击并选择 "Fix Imports"(修复导入),或者按 Ctrl-Shift-I 组合键(在 Mac 上按 ⌘-Shift-I 组合键)。javax.annotation.PostConstructjavax.annotation.PreDestroy 的 Import 语句将添加到文件顶部。
  8. 最后,向 ItemProcessor 添加 ItemErrorHandler Bean。
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject @Demo
        private ItemDao itemDao;
    
        @Inject
        private ItemValidator itemValidator;
    
        @Inject
        private ItemErrorHandler itemErrorHandler;
    
        public void execute() {
            List<Item>  items = itemDao.fetchItems();
            for (Item item : items) {
                if (!itemValidator.isValid(item)) {
                    itemErrorHandler.handleItem(item);
                }
            }
        }
    }

    (使用编辑器的提示为 exercise3.ItemErrorHandler 添加 import 语句。)

  9. 单击 "Run Project"(运行项目)("Run Project"(运行项目)按钮) 按钮以运行项目(或者,按 F6 键;在 Mac 上按 fn-F6 组合键)。在浏览器中,单击页面上显示的 "Execute" 按钮。切换回 IDE 并查看 "Output"(输出)窗口(Ctrl-4 组合键,在 Mac 上为 ⌘-4 组合键)中显示的 GlassFish Server 日志。
    INFO: Creating file error reporter
    INFO: Saving  [Value=34, Limit=7] to file
    INFO: Saving  [Value=89, Limit=32] to file
    INFO: Closing file error reporter

另请参见

不同的应用程序部署会使用不同的规则来处理无效项,例如拒绝项、向个人发送通知、为其添加标记、或者仅在输出文件中列出它们。此外,我们可能需要结合使用这些项(例如,拒绝订单、向销售代表发送电子邮件以及在文件中列出订单)。处理此类多方面问题的一个最佳方式是使用事件。本系列最后一部分的主题是 CDI 事件:

有关 CDI 和 Java EE 的详细信息,请参见以下资源。

get support for the NetBeans

Support


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo