IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source | Java technology  >

Apache Geronimo 中的依赖注入,第 2 部分: 下一代

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论


级别: 初级

Neal Ford (nford@thoughtworks.com), 应用程序架构师, ThoughtWorks Inc.

2006 年 4 月 24 日

这是两部分的文章系列的第 2 部分,继续研究从 第 1 部分 开始的 Apache Geronimo 中的依赖注入。本文将概述 Geronimo 的架构,研究 DI 对 Geronimo 的冲击,学习如何使用 Geronimo 中的 DI 特性改变编写代码的方式。本文还介绍了 GBeans 的工作方式和 Geronimo 处理构造函数注入和 setter 注入的方式。

本系列的第一篇文章独立于 DI 在 Geronimo 中的具体实现,讨论了 DI 在 Geronimo 核心层的工作方式。那篇文章演示了为什么 DI 是处理包含相互引用的类之间耦合点的有效方式。本文将在这些概念上进行扩展,把它们放在 Geronimo 的上下文环境中。通过这种方式,将学习如何使用 DI 并理解 Geronimo 如何实现 DI (构造函数和 setter 方式)。

DI 处理代码解耦有现实的好处。这里是几个例子:假设有一个人力资源部门,要根据员工服务的客户提升基本工资。要处理这个问题,要把客户实现成一个抽象基类,拥有许多不同的子类(例如,财务、制造等等)。在编写 Human Resources 类时,不知道给定员工要处理哪种类型的客户。所以,Human Resources 类要依赖于客户类的特定实现。使用 DI,可以通过配置容器或在代码中实现注入,让容器注入适合的客户类。

第二个,也是更典型的例子是持久性框架的使用。容器知道它要使用持久性框架(例如 Hibernate、Java 数据对象 [JDO] 或 iBATIS),而且每个框架都提供公共函数。使用 DI,容器可以利用容器的配置来注入特定持久性框架的代码。让持久性的耦合这么松散,就能够更容易地从一个框架切换到另一个框架。

Geronimo 的架构

Geronimo 的构建,与多数传统的 Java™ 2 Platform, Enterprise Edition (J2EE) 应用服务器不同,在传统的应用服务器中,构成应用服务器的类紧密地绑定到应用服务器的代码基。Geronimo 定义了一个基本内核,可以向它注入需要的行为。实际上,如果没有向 Geronimo 注入定义 J2EE 行为的组件,那么没有什么东西能让它成为 J2EE 应用服务器。Geronimo 是组件的松散组合,这些组件叫做 GBeans,它们在部署的时候注册到基内核,从而定义了应用服务器的功能。然后,GBeans 可能彼此相互依赖。例如,一个持久性框架 bean 可能依赖一些由文件管理 bean 定义的功能。图 1 显示了 Geronimo 的这个概念性定义。


图 1. Geronimo 的架构
架构图

图 1 中的箭头代表到其他 bean 的引用。Geronimo 中的每一样东西都是 GBean:容器、连接器、适配器、应用程序,等等。即使您创建和部署的应用程序也在部署到 Geronimo 的时候被转化成 GBeans。Geronimo 对 GBean 组件的使用带来以下效果:

  • 容器的所有部件(不论是核心的 J2EE 行为或是应用程序)的行为都是一致的。
  • 通过部署新 GBean,易于扩展 Geronimo。
  • 可以控制组件间的依赖项,因为它们的行为相同。
  • GBean 在容器中包含状态(或者通过序列化 GBean 进行持久存储,或者不持久存储)。
  • GBean 包含定义对事件响应方式的逻辑。

最终,从简单组件之间的简单交互这个角度对 Geronimo 的定义,让容器既可以更强壮,也更易于修改。要理解 Geronimo,必须理解 GBean。





回页首


GBean

GBean 组件的生命周期是:内核创建它们、在它们上面执行 DI、调用它们的方法、向它们发送事件。本文要研究的是生命周期事件的创建、销毁和 DI。

GBean 生命周期

为了把自己注册成要参与正常的生命周期操作,GBean 实现了 GBeanLifecycle 接口。这个接口定义了一个有三个方法的合约,如 清单 1 所示。


清单 1. GBeanLifecycle 方法
public class Customer implements GBeanLifecycle {
    public void doStart() throws Exception {
    }
    public void doStop() throws Exception {
    }
    public void doFail() {
    }
}

顾名思义, 清单 1 中的方法是回调方法,容器会在 GBean 的生命周期中调用它们。这些方法允许把 GBean 注入内核并接收感兴趣事件的通知。

GBean 信息

要使用 GBean,内核必须能够询问 GBean 它能做什么。这条信息位于 BeanInfo 类中,该类通常在 bean 的类通过静态初始化器装入时,由 bean 提供。清单 2 显示了典型的静态初始化器。


清单 2. GBean 初始化使用的静态初始化器
private static final GBeanInfo GBEAN_INFO;
    static {
        GBeanInfoBuilder infoFactory = new GBeanInfoBuilder(
                Customer.class.getName(),
                        Customer.class);
        // attributes
        infoFactory.addAttribute("name", String.class, true);
        infoFactory.addAttribute("salary", double.class, true);
        infoFactory.addOperation("setName",
                new Class[]{String.class});
        infoFactory.addOperation("getName");
        infoFactory.addOperation("setSalary", 
                new Class[] {double.class});
        infoFactory.addOperation("getSalary");
        GBEAN_INFO = infoFactory.getBeanInfo();
    }
    public static GBeanInfo getGBeanInfo() {
        return GBEAN_INFO;
    }

清单 2 中的静态初始化器显示了 Customer 类的注册过程,这个类包含两个属性:name 和 salary。infoFactory 拥有的方法允许类来注册感兴趣的属性和操作。注意,不必要注册类的全部公共属性和方法 —— 可以从中选择想向容器公开的那些公共属性和方法。类包含一个静态方法 getGBeanInfo(),后者返回静态初始化器创建的 GBean 信息。

清单 2 中的初始化代码是一个类的一部分,它使用 setter 注入进行 DI。在 本系列的第一篇文章 中,我把 setter 注入定义为容器(在这个示例中,是 Geronimo),在代码上调用 setXXX() 方法注入一些依赖属性值或类。为了方便这个 setter 注入,我为两个属性都注册了 set 方法。清单 3 显示了 Customer 类的完整清单。


清单 3. 可以进行 setter 注入的 Customer 类
package com.nealford.articles.di_geronimo;
import org.apache.geronimo.gbean.GBeanLifecycle;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Customer implements GBeanLifecycle {
    private static Log log = LogFactory.getLog(Customer.class);
    private String name;
    private double salary;
    private static final GBeanInfo GBEAN_INFO;
    static {
        GBeanInfoBuilder infoFactory = new GBeanInfoBuilder(
                Customer.class.getName(),
                        Customer.class);
        // attributes
        infoFactory.addAttribute("name", String.class, true);
        infoFactory.addAttribute("salary", double.class, true);
        infoFactory.addOperation("setName",
                new Class[]{String.class});
        infoFactory.addOperation("getName");
        infoFactory.addOperation("setSalary", 
                new Class[] {double.class});
        infoFactory.addOperation("getSalary");
        GBEAN_INFO = infoFactory.getBeanInfo();
    }
    public void doStart() throws Exception {
        log.info("Starting Customer GBean");
    }
    public void doStop() throws Exception {
        log.info("Stopping Customer GBean");
    }
    public void doFail() {
        log.info("customer GBean failure!");
    }
    public static GBeanInfo getGBeanInfo() {
        return GBEAN_INFO;
    }
    public String getName() {
        return name;
    }
    public void setName(final String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(final double salary) {
        this.salary = salary;
    }
}

启动和停止 GBean

在进一步深入讨论 DI 之前,还有一个值得注意的生命周期主题:开始和启动 bean。如果想以编程方式装入 GBean,是可行的。清单 4 显示了如何以编程方式装入 GBean,这个 GBean 的名称是通过 Java Management Extension (JMX) 接口定义的。


清单 4. 手动处理 GBean 的生命周期
GBeanMBean gmb = new GBeanMBean(Customer.getGBeanInfo());
gmb.setAttribute("name","Homer");
gmb.setAttribute("salary", 2500.00);
ObjectName myGbeanName = ObjectName.newInstance(
        "Geronimo.my:Customer=customer1");
kernel.loadGBean(myGbeanName, gmb);
kernel.startGBean(myGbeanName);
//do some work with Customers
kernel.stopGBean(myGbeanName);
kernel.unloadGBean(myGbeanName);

当然,对于您所创建的大多数 GBean 来说并不需要做这点 —— 容器会根据所提供的部署计划装入它们。可以看到,没有什么会妨碍您在代码中装入、启动和停止自己的 GBean 引用。

GBean 状态

与其他对象引用一样,GBean 也以属性或者对其他 GBean 引用的形式包含状态信息。GBean 的属性是由 GBean 对象的一个属性所包含的值。换句话说,属性对应着 GBean 类中的一对访问器/修改器方法(例如 gettersetter)。引用则类似于普通 JavaBean 中的对象引用,只是这时是显式地引用到另一个 GBean。稍后会详细介绍引用。

属性既可以是持久性的也可以是非持久性的。持久性的属性在 GBean 的实例之间保持它们的值。通过对 GBean 进行序列化或者其他机制(例如保存在数据库中),持久性属性被永久保存。非持久性属性只在 GBean 实例的生存期间保持它们的值,在实例消失后也消失。

魔法属性

Geronimo 架构定义了特殊的属性类型,叫做魔法属性。魔法属性装入的值取决于装入 GBean 的环境。例如,魔法属性 kernel 指向内核,它自动被框架注入到类中。类似地,ClassLoader 属性注入当前类装入器,ObjectName 属性把当前名称注入 GBean。 清单 4 显示了 kernel 属性的用法,它被自动注入到定义生命周期的类中。所有魔法属性的完整列表位于内核的 GBeanMBean 类 中。魔法属性不是持久性的,因为它们与启动环境绑定在一起。





回页首


GBean 中的 DI

清单 3 所示的 customer 类是使用 setter 注入的类的良好示例。接下来,将看到构造函数注入的示例,以及 Geronimo 的部署计划定义容器注入代码的方式。正如在本系列的 第一篇文章 中解释过的,构造函数注入表明框架会提供类在初始化时需要的构造函数参数。

单一引用

许多 DI 的支持者愿意采用构造函数注入,因为这意味着注入的类永远不会出现依赖项不存在的情况。清单 5 显示的 GBean 被定义成使用构造函数注入来提供 Customer 引用。


清单 5. 使用构造函数注入的 HumanResources 类
package com.nealford.articles.di_geronimo;
import org.apache.geronimo.gbean.GBeanLifecycle;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HumanResources implements GBeanLifecycle {
    private static Log log = LogFactory.getLog(HumanResources.class);
    private Customer customer1;
    private static final GBeanInfo GBEAN_INFO;
    static {
        GBeanInfoBuilder infoFactory = new GBeanInfoBuilder(
                "HumanResources",
                 HumanResources.class);
        // attributes
        infoFactory.addReference("Customer", Customer.class);
        // operations
        infoFactory.setConstructor(new String[]{"Customer1"});
        GBEAN_INFO = infoFactory.getBeanInfo();
    }
    public HumanResources(Customer customer1) {
        this.customer1 = customer1;
    }
    public void doStart() throws Exception {
        log.info("Starting HR GBean");
    }
    public void doStop() throws Exception {
        log.info("Stopping HR GBean");
    }
    public void doFail() {
        log.info("HR GBean failure!");
    }
}

清单 5 中的 GBean 从格式上看起来像 清单 3 中定义的 Customer GBean。但是请注意,这个类没有任何 getset 方法。相反,惟一的构造函数被设计成接受 Customer 对象。而且,在 attributes 部分,我定义了一个到 Customer 类的引用。在 清单 5 中建立的引用是 图 1 中的箭头表示的 GBean 间引用的一个示例。

这个 GBean 的创建者(也就是触发这个类中的构造函数的人)必须提供一个到 Customer 对象的引用。在多数情况下,构造实体是 Geronimo 容器本身。下一节将介绍构造实体如何能知道要提供客户以及要提供哪个客户。

部署计划

Geronimo 中关于引用和依赖项的信息位于部署计划中。部署计划 是 XML 文档,它们定义了要构造哪个 GBean,并解析它们的依赖项。CustomerHuman Resources 类的部署计划如 清单 6 所示。


清单 6. 部署计划
<?xml version="1.0" encoding="UTF-8"?>
<configuration
    xmlns="http://geronimo.apache.org/xml/ns/deployment"
    configId="test/HRPlan">
    
    
    <gbean name="test:Customer=customer1" class="Customer">
        <attribute name="name">Homer</attribute>
        <attribute name="salary">2500.00</attribute>
        
    </gbean>
    
    <gbean name="test:HR=HumanResources" class="HumanResources">
        <reference name="customer1">
            test:Customer=customer1
        </reference> 
        </gbean>
</configuration>

部署计划定义了两种类型的 GBean。对于 Customer GBean,它定义了要注入 Customer 实例的属性中的值。对于 HumanResources 类,它创建了在 HumanResources 类初始化时,Geronimo 向其中注入 Customer 实例所需要的引用。

这个示例显然是个简单的示例 —— 只有两个 GBean,每一个都使用了一种不同类型的 DI。通常,设置依赖项之间的一对一映射要比这复杂得多。例如,如果有各种不同的 Customer 子类且环境需要判断在指定情况下要注入哪个子类,该怎么办?

引用模式

Geronimo 利用引擎模式 来处理多依赖项的情况。使用引用模式,可以指定一组合法的可以注入的依赖项类型。创建引用模式的方式是在注入的类名称中使用通配符,引用模式可以在代码中使用也可以在部署计划中使用。以下代码显示了如何创建一个允许注入任何 customer 子类的引用:

bean.setReferencePattern("Geronimo.my:*");




回页首


结束语

本系列演示了 DI 是多么强大的代码重用机制。Geronimo 是第一个完全作为 DI 容器编写的 J2EE 服务器。在构建为 Geronimo 设计的组件时,这会带来巨大的灵活性。DI 是一种已经存在多年的代码重用技术,但是只有现在程序员才开始享受它所带来的极度松散耦合的真正好处。



参考资料

学习

获得产品和技术

讨论


关于作者

Neal Ford

Neal Ford 是 ThoughtWorks 的应用程序架构师,这是一家全球性的 IT 咨询公司。他还是应用程序、指导材料、杂志文章、课件、视频/DVD 演示的设计师和开发人员,还是 Developing with Delphi: Object-Oriented TechniquesJBuilder 3 UnleashedArt of Java Web Development 这些图书的作者。他的主要咨询重点是构建大规模企业应用程序。他还是国际知名的演讲者,曾经在世界各地许多开发人员会议上发言。请访问他的 Web 站点 http://www.nealford.com。他欢迎反馈,您可以通过他的邮件 nford@thoughtworks.com 与他联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

将您的建议发给我们或者通过参加讨论与其他人分享您的想法.







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款