Spring Roo 简介,第 5 部分: 编写 Spring Roo 的高级附加组件和包装器附加组件

提高 Spring Roo 的功能

Spring Roo 高级附加组件提供了在应用程序中添加 Java™ 代码的机制(例如,构建一个能够为您的域对象编写 equals 方法和 hashcode 方法的附加组件)。利用 addon create 命令,您可以创建一个高级附加组件模板。然后可以扩展该模板,以满足开发人员的需求。本文将遍历创建高级附加组件的步骤。

Shekhar Gulati, 高级顾问, Xebia

Shekar Gulati 的照片Shekhar Gulati 是效力于 Xebia India 的一名 Java 顾问。他拥有 6 年的企业 Java 经验。他在 Spring 产品组合项目(比如 Spring、Spring-WS、Spring Roo 等)方面有丰富的经验。他主要关注 Spring、NoSQL 数据库、Hadoop、SRAD 框架(如 Sring Roo)、云计算(主要是 Google App Engine、CloudFoundry、OpenShift 等 PaaS 服务)和 Hadoop。他是一位活跃的作家,为 JavaLobby、Developer.com 和 IBM developerWorks 撰稿,他在 http://whyjava.wordpress.com/ 上拥有自己的博客。您可以在 twitter @ http://twitter.com/#!/shekhargulati 上关注他。



2012 年 5 月 14 日

本系列 “Spring Roo 简介” 的 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件 讨论了 Spring Roo 附加架构,以及如何使用 addon create 命令创建国际化的、简单的附加组件。本文主要关注 Spring Roo 支持的其余两种类型的附加组件,即高级附加组件和包装器附加组件。建议您在开始阅读本文之前先阅读第 3 部分的文章。

高级附加组件的简介

高级附加组件允许 Spring Roo 执行简单附加组件所能执行的一切操作,例如,利用依赖关系或插件更新 Maven POM 文件,更新或添加配置文件,增强现有的 Java 类型,并使用 AspectJ ITD 引入新的 Java 类型。添加源代码的功能使高级附加组件比所有其他附加组件都更强大。在创建一个 Spring Roo 高级附加组件之前,请先研究一下 Spring Roo 提供的现有高级附加组件。


使用中的高级附加组件

目前使用的一个高级附加组件是 JPA,它能执行与持久性相关的工作,即为数据库添加支持并创建新的实体。要查看此组件的运行情况,请打开 Roo shell,并在 清单 1 中执行此命令。在本文中,我使用的是 Spring Roo V1.2.0.M1。

清单 1. JPA 示例
project --topLevelPackage com.dw.demo --projectName entity-demo 
jpa setup --database FIREBIRD --provider HIBERNATE 
entity --class ~.domain.Book

jpa setupentity 命令均等同于一个名叫 org.springframework.roo.addon.jpa 的高级附加组件。Roo shell 上的 jpa setupentity 命令的输出允许明确地对简单附加组件和高级附加组件进行划分。清单 2 显示了 JPA setup 命令的输出。

清单 2. JPA setup 命令的输出
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties 
Updated ROOT/pom.xml [added dependencies ...] 
Updated SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml 
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml

jpa setup 命令的输出显示,它正在执行配置功能,比如,在 pom.xml 中添加依赖关系、更新 Spring applicationContext.xml,以及创建特定于持久性的 persistence.xml 文件。假设 JPA setup 命令相当于一个简单的附加组件,因为它不创建或更新 Java 源代码。在与上面显示的设置相似的场景中使用一个简单的附加组件。

清单 3 显示了 entity 命令的输出。

清单 3. entity 命令的输出
Created SRC_MAIN_JAVA/com/dw/demo/domain 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book.java 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Configurable.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Jpa_Entity.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Entity.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_ToString.aj

该输出显示创建了一个名为 Book.java 的 Java 文件以及四个 *.aj 文件。识别高级附加组件的黄金法则是生成 Java 文件和/或 *.aj 文件,如同 entity 命令一样。这些 *Roo_*.aj 文件被称为类型间声明 (Inter-type Declarations, ITD)。ITD 允许一个类型(一个方面)声明另一个类型,也就是说,您可以通过添加方法和字段或者更改它们的类型层次来修改任何类型的静态结构。Roo 使用 ITD 作为代码生成构件,并管理其整个生命周期。ITD 允许 Roo 在单独的编译单元中生成代码,但是无法将他们组合到相同的编译类中。

查看 entity 命令的输出后,请考虑一下如何通过 Spring Roo 生成这些构件(.java 和.aj 文件)。参见 清单 4 中的一个 Book.java 文件样例。

清单 4. Book.java 文件
package com.dw.demo.domain; 

import org.springframework.roo.addon.entity.RooEntity; 
import org.springframework.roo.addon.javabean.RooJavaBean; 
import org.springframework.roo.addon.tostring.RooToString; 

@RooJavaBean 
@RooToString 
@RooEntity 
public class Book { 
}

除了类中的注释以外,Java 文件看起来很普通。看一下注释和 .aj 文件的名称,显然,一些注释相当于 .aj 文件添加的函数。例如,RooToString 相当于 Book_Roo_ToString.aj 文件并添加了 toString() 方法。RooEntity 相当于 Book_Roo_Entity .aj、Book_Roo_Jpa_Entity 以及与持久性相关的一些方法。我们暂时将其余的内容先放一放。要了解如何利用注释生成 ITD,请先了解 Spring Roo 如何提供高级附加组件功能。

  1. Roo shell 启动后,会扫描所有的类,并注册所有实现 CommandMarker 接口的类。CommandMarker 接口会告知 Roo,这些类将定义该附加组件能执行的命令。
  2. 所有的这些高级附加组件会向 Spring Roo 提供的 OSGi 运行时注册其服务。这些服务指定了触发代码生成的条件。对于所有的高级附加组件,触发点就是一个注释。例如,如果 Java 类型拥有 RooToString 注释,则只会触发针对 toString() 方法生成的高级附加组件。这种情况也适用于其他注释。
  3. 一旦使用了 entity --class ~.domain.Book,附加组件就会创建一个带注释的名为 Book.java 的 Java 文件。其他的附加组件会在 Java 类拥有这些注释时或拥有为它们编写的 .aj 文件时触发。

在创建自己的高级附加组件时,您会看见更多的相关说明。

org.springframework.roo.addon.jpa 附加组件只是 Spring Roo 所提供的高级附加组件的一个示例。其他的高级附加组件还包括 GWT、控制器、JSON 等。Spring Roo 1.2.0 发行版本还包含两个更高级的附加组件,即 addon-equals 和 addon-jsf。addon-equals 附加组件提供了一个实体的 equals 和 hashcode 方法的实现,addon-jsf 则在 Spring Roo 应用程序中提供 JSF 支持。要玩转最新的 Spring Roo 快照,请构建 Spring Roo 代码或从 Spring Roo 存储库 中下载每日快照。


在 My Entity Class 中包含 compareTo() 方法

值对象或实体通常是实现 java.lang.Comparable 接口所必需的,它们还提供了 compareTo() 方法的实现。Comparable 接口在实现它的每一个类的对象上进行完全排序。当您实现 Comparable 时,可以执行以下操作:

  1. 调用 java.util.Collections.sortjava.util.Collections.binarySearch
  2. 调用 java.util.Arrays.sortjava.util.Arrays.binarySearch
  3. 将对象用作 java.util.TreeMap 中的键
  4. 将对象用作 java.util.TreeSet 中的元素

在本文中,您将构建一个高级的附加组件,该组件将为您在应用程序中创建的实体提供了 compareTo() 的实现。因为您想在自己的应用程序中添加 Java 代码,所以必须创建一个高级附加组件。


项目的创建

Spring Roo 文档 详细地说明了如何在 Google 代码之上创建一个项目和 Maven 存储库,所以有必要在此重复一下。请注意,我将使用 "spring-dw-roo-compareto-addon" 作为项目名称。

如果您正在使用的不是最新版本的 Spring Roo(1.2.0.RC1),请从 项目网站 下载此版本。请解压缩此版本并安装它,如 Spring Roo 简介,第 1 部分:从源代码构建 所述。

Spring Roo 摈弃或移除了早期版本中使用的一些类。


创建一个高级附加组件

创建项目后,您会看到一个名为 spring-dw-roo-compareto-addonAfter 的目录,目录中只有一个 .svn 文件夹。从命令行中导航至 spring-dw-roo-compareto-addon 目录,并启动 Roo shell。然后键入以下命令:

addon create advanced --topLevelPackage org.xebia.roo.addon.compareto --projectName spring-dw-roo-compareto-addon

就这样!您就创建了一个高级附加组件。

接下来,在 Roo shell 上,运行 perform package 命令以创建一个附加组件 jar。清单 5 显示了 addon create advanced 命令生成的文件。

清单 5. addon create advanced 命令生成的文件
Created ROOT/pom.xml 
Created SRC_MAIN_JAVA 
Created SRC_MAIN_RESOURCES 
Created SRC_TEST_JAVA 
Created SRC_TEST_RESOURCES 
Created SPRING_CONFIG_ROOT 
Created ROOT/readme.txt 
Created ROOT/legal 
Created ROOT/legal/LICENSE.TXT 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoCommands.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperations.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperationsImpl.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadata.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadataProvider.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/RooCompareto.java 
Created ROOT/src/main/assembly 
Created ROOT/src/main/assembly/assembly.xml 
Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto 
Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto/configuration.xml

一些已生成的文件,如 pom.xml、readme.txt 和 license.txt 不需要 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件 所讨论的任何简介,也不需要加以说明。更多有趣的构件包括:

  • ComparetoCommands.java
  • ComparetoOperations.java
  • ComparetoOperationsImpl.java
  • ComparetoMetadata.java
  • ComparetoMetadataProvider.java
  • RooCompareto.java

现在,依次查看每个生成的构件。

  • ComparetoCommands.java:该类实现了 CommandMarker 接口并展示了两种方法(一个带有 CliAvailablityIndicator 注释,另一个带有 CliCommand 注释)。CliAvailablityIndicator 注释告知 Spring Roo 可以看见该命令的时间。例如,只有用户在 Roo shell 中或直接在项目中定义持久性设置之后,才能使用 'entity' 命令。使用 @CliCommand 注释的方法会使用 Roo shell 注册该命令。@CliCommand 注释拥有两个属性:一个是定义命令名称的 value 属性;另一个是 help 属性,它会在键入帮助命令时定义所显示的帮助消息。要获得 *Commands 类的详细说明,请参阅 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件
  • ComparetoOperationsImpl.java:该 ComparetoCommands 类将所有工作都委托给 ComparetoOperationsImpl 类。在此类中生成的四个方法是:
    • isCommandAvailable():此方法由 ComparetoCommands 类中带 CliAvailabilityIndicator ComparetoCommands 注释的方法进行调用,以查看该命令是否应该可见。这样做是为了确保命令是上下文感知的。此方法可执行各种检验。例如,如果已经创建了项目,则应该只能看见该命令,或者,如果已经设置了持久性,则应该只能看见该命令。并不强制一定要提供命令可见条件。只需返回 true 来确保该命令总是可见。
    • setup():此方法由ComparetoCommands 类中使用 @CliCommand 注释的 setup() 方法进行调用。此代码清楚地表明,该类负责执行与设置相关的任务,比如,添加 Maven 依赖关系,添加 Maven 存储库,或者创建或更新 Spring 上下文文件(正如 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件中对 Jamon Roo 附加组件所做的操作一样)。
    • annotateType():此方法与 annotateAll() 方法是简单附加组件中没有的两个新方法。该方法的功能是在特定的 Java 类型上添加一个注释 (RooCompareto)。该方法使用了一些 Spring Roo 提供的服务来获取给定 Java 类的类详细资料,并将 RooJCompareto 注释附加到其中。
    • annotateAll():此方法会查找所有使用 RooJavaBean 注释的类型,并在所有那些类型上调用 annotateType() 方法。当所有实体都应该拥有 RooCompareto 注释时使用此方法。
  • RooCompareto.java:此注释的存在会导致附加组件生成代码。
  • ComparetoMetadataProvider.java:该类是一个 Spring Roo 服务,由 Roo 调用,用于为附加组件检索元数据。该类注册了添加和移除元数据的触发器。无需在此类进行任何修改,但是请记住,该类拥有一个名为 getMetadata() 的方法,只要存在任何带有 RooCompareto 注释的 Java 类型,就会通过 Spring Roo 调用该方法。
  • ComparetoMetadata.java:该类是负责生成与附加组件相对应的 ITD。在该生成的代码中,使用了一个名为 ItdTypeDetailsBuilder 的类来创建一个带有一个字段和方法的 ITD。在本文后面部分,您需要修改默认的生成代码,以满足添加一个 compareTo 方法和实现 Comparable 接口的需求。

修改附加组件以满足需求

您可能想要创建一个将 compareTo 方法添加到实体类的附加组件。您应该执行以下操作:

  • commons-lang V3.1 的 Maven 依赖关系添加到目标项目中。这是必须的,因为 commons-lang 提供了一个名为 CompareToBuilder 的构建器类,可以使用它来构建 compareTo 方法。
  • 使得实体类实现 Comparable 接口。
  • compareTo 方法创建一个 ITD。

添加 Maven 依赖关系

要满足这些需求,则需要更改 ComparetoOperationsImplComparetoMetadata 类。依次完成这些更改。

  1. 首先,在目标项目中添加 Maven commons-lang 依赖关系。更新 configuration.xml 文件,使之拥有 commons-lang 依赖关系,而不是默认提供的 Spring batch 依赖关系,正如 清单 6 中所示。
    清单 6. 更新 configuration.xml 文件
    <?xml version="1.0" encoding="UTF-8" standalone="no"?> 
    <configuration>
       <dependencies>
          <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <version>3.1</version>
          </dependency>
       </dependencies>
    </configuration>
  2. 接下来,修改 ComparetoOperationsImpl 类中的 setup() 方法的实现,以便读取 commons-lang Maven 依赖关系,而不是 Spring batch Maven 依赖关系,正如 清单 7 中所示。这里未显示 annotateTypeannotateAll() 方法,因为没有对它们进行任何更改。
    清单 7. 修改 setup() 方法的实现
    @Component
    @Service
    public class ComparetoOperationsImpl implements ComparetoOperations {
    
        @Reference
        private ProjectOperations projectOperations;
    
        @Reference
        private TypeLocationService typeLocationService;
    
        @Reference
        private TypeManagementService typeManagementService;
    
        /** {@inheritDoc} */
            public void setup() {
         // Install the add-on Google code repository needed to get the annotation
            projectOperations.addRepository("",
               new Repository("Compareto Roo add-on repository",
               "Compareto Roo add-on repository",
               "https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo"));
            List<Dependency> dependencies = new ArrayList<Dependency>();
         // Install the dependency on the add-on jar (
            dependencies.add(new Dependency("org.xebia.roo.addon.compareto",
               "org.xebia.roo.addon.compareto", "0.1.0.BUILD-SNAPSHOT",
               DependencyType.JAR, DependencyScope.PROVIDED));
            Element configuration = XmlUtils.getConfiguration(getClass());
            for (Element dependencyElement : XmlUtils.findElements(
                    "/configuration/dependencies/dependency",
                    configuration)) {
                       dependencies.add(new Dependency(dependencyElement));
                    }
            projectOperations.addDependencies("", dependencies);
        }
    }

到目前为止所做的更改与 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件 中用来创建 Jamon 简单附加组件的更改类似。

让实体类实现 Comparable 接口

在代码中添加 Maven 依赖关系后,您需要确定您的实体类已经实现了 java.lang.Comparable 接口。为此,请修改由 ComparetoMetadata 类生成的 ITD。元数据类使用 ItdTypeDetailsBuilder 类生成 ITD,该类提供了向 ITD 添加方法、字段、注释、接口等元素的各种添加方法。要让 Java 类型实现一个接口,请使用 ItdTypeDetailsBuilder 类中的 addImplementsType 方法,如 清单 8 中所示。我只展示了 ComparetoMetadata 构造函数,因为 ITD 的构造是在构造函数中完成的。

清单 8. 实现 java.lang.Comparable 接口
public ComparetoMetadata(String identifier, JavaType aspect Name,
    PhysicalTypeMetadata  governorPhysicalTypeMetadata) {
        super(identifier, aspect Name, governorPhysicalTypeMetadata);
        Assert.isTrue(isValid(identifier), "Metadata identification string '" + 
            identifier + "' does not appear to be a valid");
        JavaType comparableInterface = new JavaType("java.lang.Comparable");
        builder.addImplementsType(comparableInterface);
        itdTypeDetails = builder.build();
    }

为 compareTo 方法创建一个 ITD

让 Java 类型实现 Comparable 接口后,您还必须提供 compareTo 方法的实现。CompareToBuilder 类为创建 compareTo 方法提供了一个流畅接口。Spring Roo equals 附加组件使用 EqualsBuilder 和 HashcodeBuilder 来提供 equals 和 hashcode 方法的实现。让我们举一个例子,您一定要清楚 CompareToBuilder 是如何帮助创建 compareTo 方法。假设您拥有一个名叫 Book 的实体,并且您想要使用 CompareToBuilder 为它提供 compareTo 实现。清单 9 显示了 Book 类和 compareTo 方法

清单 9. Book 类和 compareTo 方法
import org.apache.commons.lang3.builder.CompareToBuilder;

public class Book implements Comparable {
    private String title;
    private String author;
    private double price;
    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    // getters and setters

    public int compareTo(Book o) {
	if(!(o instanceof Book)){
	  return -1;
	}
	Book book = (Book)o
        return new CompareToBuilder().append(this.title, book.title).append(this.author, 
            book.author).append(this.price, book.price).toComparison();
    }

    @Override
    public String toString() {
        return "Book [title=" + title + ", author=" + author + ", price=" + price + "]";
    }

}

清单 9 中的 compareTo 方法执行下列操作:

  • 如果 o 不是 instanceOfBook,则返回 -1
  • 如果 o 是 instanceOfBook,则将 o 的类型强制转换为 Book
  • 创建一个 CompareToBuilder 类的对象,然后在字段上调用 append 方法

循序渐进地使用以下这些步骤构建 compareTo 方法:

  1. 如果 o 不是 instanceOf Book,则返回 -1

    在添加 instanceOf 检查之前,创建 compareTo 方法。请参见 清单 10

    清单 10. 创建 compareTo 方法
    public ComparetoMetadata(String identifier, JavaType aspect Name, 
        PhysicalTypeMetadata governorPhysicalTypeMetadata) { 
            super(identifier, aspect Name, governorPhysicalTypeMetadata); 
            Assert.isTrue(isValid(identifier), 
                "Metadata identification string '" + identifier + 
                "' does not appear to be a valid"); 
    
        JavaType comparableInterface = new JavaType("java.lang.Comparable"); 
        builder.addImplementsType(comparableInterface); 
        builder.addMethod(getCompareToMethod()); 
    
        itdTypeDetails = builder.build(); 
    } 
    
    private MethodMetadata getCompareToMethod() { 
        final JavaType parameterType = JavaType.OBJECT; 
        final List<JavaSymbolName> parameterNames = 
            Arrays.asList(new JavaSymbolName("obj")); 
        final InvocableMemberBodyBuilder bodyBuilder = 
            new InvocableMemberBodyBuilder(); 
        bodyBuilder.appendFormalLine("return -1;"); 
        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), 
            Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
            JavaType.INT_PRIMITIVE, 
                AnnotatedJavaType.convertFromJavaTypes(parameterType), 
            parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
    }

    getCompareToMethod() 使用 MethodMetadataBuilder 类生成 compareTo 方法元数据。MethodMetadataBuilder 是一个 Spring Roo 提供的 Builder 类,用于构建方法元数据提。要构建方法元数据,首先要构造一个 MethodMetadataBuilder 对象,传递参数(比如访问修饰符、方法名称、返回类型、参数列表或方法主体构建器)为 compareTo 方法创建元数据,如 清单 11 中所示。

    清单 11. instanceOf 检查
    private MethodMetadata getCompareToMethod() { 
            final JavaType parameterType = JavaType.OBJECT; 
            String parameterName = "obj"; 
            final List<JavaSymbolName> parameterNames = 
                Arrays.asList(new JavaSymbolName(parameterName)); 
            final InvocableMemberBodyBuilder bodyBuilder = 
                new InvocableMemberBodyBuilder(); 
            final String typeName = destination.getSimpleTypeName(); 
            bodyBuilder.appendFormalLine("if (!(" + parameterName + 
                " instanceof " + typeName + ")) {"); 
            bodyBuilder.indent(); 
            bodyBuilder.appendFormalLine("return -1;"); 
            bodyBuilder.indentRemove(); 
            bodyBuilder.appendFormalLine("}"); 
    
            bodyBuilder.appendFormalLine("return -1;"); 
            final MethodMetadataBuilder methodBuilder = 
                new MethodMetadataBuilder(getId(), 
                Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
                JavaType.INT_PRIMITIVE, 
                AnnotatedJavaType.convertFromJavaTypes(parameterType), 
                parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
        }
  2. 如果 o 是 instanceOfBook,则将 o 的类型强制转换为 Book

    下一个步骤是一个强制转换,因此您能够构建 compareTo 方法。为此,请将此行追加到 instanceOf 检查的后面:

    bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + 
        OBJECT_NAME + ";");
  3. 创建 CompareToBuilder 类的对象,然后再在字段上调用 append 方法

    要构建 compareTo 方法,则需要访问一个类中的所有字段。ComparetoMetadata 类不包含任何有关类型的信息,所以它不能获取该类的字段。此信息可由 ComparetoMetadataProvider 提供,如 清单 12 中所示。

    清单 12. ComparetoMetadataProvider
    protected ItdTypeDetailsProvidingMetadataItem getMetadata(String metadataId,
        JavaType aspect Name, PhysicalTypeMetadata governorPhysicalTypeMetadata, 
        String itdFilename) { 
    
            final String[] excludeFields = {};
    
            final MemberDetails memberDetails = 
                getMemberDetails(governorPhysicalTypeMetadata); 
            if (memberDetails == null) { 
                return null; 
            } 
    
            final JavaType javaType = 
                governorPhysicalTypeMetadata.getMemberHoldingTypeDetails().getName();
    
            final List<FieldMetadata> compareToFields = 
                locateFields(javaType, excludeFields, memberDetails, metadataId); 
    
            return new ComparetoMetadata(metadataId, aspect Name,
            governorPhysicalTypeMetadata, compareToFields);
        } 
    
    private List<FieldMetadata> locateFields(final JavaType javaType, final String[]
            excludeFields, final MemberDetails memberDetails, final String
            metadataIdentificationString) { 
    
    	final SortedSet<FieldMetadata> locatedFields = new TreeSet<FieldMetadata>(new
        	  Comparator<FieldMetadata>() { 
                public int compare(final FieldMetadata l, final FieldMetadata r) { 
                    return l.getFieldName().compareTo(r.getFieldName()); 
                } 
            }); 
    
            final List<?> excludeFieldsList = 
                CollectionUtils.arrayToList(excludeFields); 
            final FieldMetadata versionField = 
                persistenceMemberLocator.getVersionField(javaType); 
    
            for (final FieldMetadata field : memberDetails.getFields()) { 
                if (excludeFieldsList.contains(field.getFieldName().getSymbolName())) { 
                    continue; 
                } 
                if (Modifier.isStatic(field.getModifier()) ||
            Modifier.isTransient(field.getModifier()) ||
          		field.getFieldType().isCommonCollectionType() 
                        || field.getFieldType().isArray()) { 
                    continue; 
                } 
                if (versionField != null && 
                    field.getFieldName().equals(versionField.getFieldName())) { 
                        continue; 
                    } 
    
                locatedFields.add(field); 
    
                metadataDependencyRegistry.registerDependency(
                    field.getDeclaredByMetadataId(), 
                    metadataIdentificationString
                ); 
            } 
    
            return new ArrayList<FieldMetadata>(locatedFields); 
        }

    拥有这些字段后,将它们传送到 ComparetoMetadata,以便能够构建 compareTo 方法,如 清单 13 中所示。

    清单 13. 传递元数据以构建 compareTo 方法
    private List<FieldMetadata> compareToFields; 
    
    public ComparetoMetadata(String identifier, JavaType aspectName, 
        PhysicalTypeMetadata governorPhysicalTypeMetadata, 
        List<FieldMetadata> compareToFields) { 
    
            super(identifier, aspectName, governorPhysicalTypeMetadata); 
            Assert.isTrue(isValid(identifier), 
                "Metadata identification string '" + identifier + 
                    "' does not appear to be a valid"); 
    
            this.compareToFields = compareToFields; 
            if (!CollectionUtils.isEmpty(compareToFields)) { 
                JavaType comparableInterface = new JavaType("java.lang.Comparable"); 
                builder.addImplementsType(comparableInterface); 
                builder.addMethod(getCompareToMethod()); 
            } 
            itdTypeDetails = builder.build(); 
    
        } 
    
        private MethodMetadata getCompareToMethod() { 
            final JavaType parameterType = JavaType.OBJECT; 
            String parameterName = "obj"; 
            final List<JavaSymbolName> parameterNames = 
                Arrays.asList(new JavaSymbolName(parameterName)); 
            final InvocableMemberBodyBuilder bodyBuilder = 
                new InvocableMemberBodyBuilder();
            final ImportRegistrationResolver imports = 
                builder.getImportRegistrationResolver(); 
                imports.addImport(
                    newJavaType("org.apache.commons.lang3.builder.CompareToBuilder")
                );
    
            final String typeName = destination.getSimpleTypeName(); 
            bodyBuilder.appendFormalLine("if (!(" + parameterName + " instanceof " + 
                typeName + ")) {"); 
            bodyBuilder.indent(); 
            bodyBuilder.appendFormalLine("return -1;"); 
            bodyBuilder.indentRemove(); 
            bodyBuilder.appendFormalLine("}"); 
    
            bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + 
                parameterName + ";"); 
            final StringBuilder builder = new StringBuilder(); 
            builder.append("return new CompareToBuilder()"); 
    
            for (final FieldMetadata field : compareToFields) { 
                builder.append(".append(" + field.getFieldName() + ", rhs." + 
                    field.getFieldName() + ")"); 
            } 
            builder.append(".toComparison();"); 
    
            bodyBuilder.appendFormalLine(builder.toString()); 
    
            final MethodMetadataBuilder methodBuilder = 
                    new MethodMetadataBuilder(getId(), 
                        Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
                        JavaType.INT_PRIMITIVE, 
                        AnnotatedJavaType.convertFromJavaTypes(parameterType), 
                        parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
        }

测试

这完成了 compareTo 附加组件的实现。您可以从 Google 代码存储库 中下载此附加组件的完整源代码。现在,您可以测试您刚创建的 compareTo

  1. 退出 roo shell 并运行 mvn clean install 命令。在构建流程中,系统会要求您键入 GPG 通行码。
  2. 构建 Roo 附加组件后,打开一个新的命令行并创建一个名叫 bookshop 的目录。
  3. 导航至 bookshop 目录并键入 roo 命令来打开一个 Roo shell。
  4. 在 Roo shell 中执行来自 清单 14 的命令。
    清单 14. 创建附加组件
    project --topLevelPackage com.xebia.roo.bookshop --projectName bookshop  
    jpa setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE  
    entity jpa --class ~.domain.Book  
    field string --fieldName title --notNull  
    field string --fieldName author --notNull  
    field number --fieldName price --type double --notNull
  5. 要安装并启动该附加组件,请在 Roo shell 上键入以下内容:
    osgi start --url file://<location to compareTo addon jar>

    这会安装并激活您的 compareTo 附加组件。您可以使用 OSGi ps 命令查看附加组件的状态。

  6. 键入 compareto 并按下选项卡,查看 清单 15 中的三个 compareto addon 命令。
    清单 15. 查看 compareto addon 命令
    roo> compareto
    
    compareto add      compareto all      compareto setup
  7. 清单 15 中陈述的步骤会确认此 compareto 附加组件是否正确安装。下一个步骤是运行 setup 命令,该命令将配置这些必要的依赖关系。请参见 清单 16
    清单 16. 运行 setup 命令
    roo> compareto setup 
    
    Updated ROOT/pom.xml [added repository 
        https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo; 
        added dependencies org.xebia.roo.addon.compareto:org.xebia.roo.addon.compareto:
            0.1.0.BUILD,
        org.apache.commons:commons-lang3:3.1;
        removed dependency org.apache.commons:commons-lang3:3.0.1]
  8. 运行 compareto setup 命令后,下一个合理的步骤就是向实体类添加 compareTo 方法。您可以通过 compareto add 或 compareto all 来实现此操作,具体操作取决于您是想仅为一个实体类生成 compareTo 方法,还是想为所有实体类生成 compareTo 方法。让我们为样例 bookshop 应用程序(参见 下载)中的所有实体类添加 compareTo 方法。请参见 清单 17
    清单 17. 为所有的实体类添加 compareTo 方法
    roo> compareto all 
    Updated SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book.java 
    Created SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book_Roo_Compareto.aj

    正如您在上面 compareto all 命令的输出中所看到的,该命令会生成一个名为 Book_Roo_Compareto.aj 的 ITD。此文件将包含 compareTo 方法。清单 18 显示了 Book_Roo_Compareto.aj。

    清单 18. Book_Roo_Compareto.aj
    import org.apache.commons.lang.builder.CompareToBuilder; 
    
    privileged aspect Book_Roo_Compareto { 
    
        declare parents: Book implements java.lang.Comparable; 
    
        public int Book.compareTo(java.lang.Object obj) { 
            if (!(obj instanceof Book)) { 
                return -1; 
            } 
            Book rhs = (Book) obj; 
            return new CompareToBuilder().append(author, 
                rhs.author).append(id, rhs.id).append(price, rhs.price).append(title, 
                rhs.title).toComparison(); 
        } 
        
    }
  9. 在 Roo shell 上运行 perform package 命令,查看添加附件组件后一切是否编译正确。令人惊讶的是,构建会失败,因为 Maven 不能解决 Spring Roo 绑定依赖关系的问题。这些绑定依赖关系来自于 compareTo 附加组件。您需要该附加组件上的依赖关系,因为您的实体必须使用 Compareto 进行注释。这是您惟一需要从附加组件中做的事情。我发现最好的方法是创建另一个 Maven 模块并拥有其所有附加组件的依赖关系。这跟 Spring Roo 所做的行不同。Spring Roo 不依赖于所用的每个附加组件。它拥有一个包含所有依赖关系的通用 Spring Roo annotations jar。我创建了一个项目 xebia-spring-roo-addon-annotation 并将 Compareto 注释放在此模块中。接着,我更新了 configuration.xml,以便将此 jar 添加到客户端项目,而不是附加组件 jar。 清单 19 显示了 configuration.xml。
    清单 19. configuration.xml
    <?xml version="1.0" encoding="UTF-8" standalone="no"?> 
    <configuration> 
      <dependencies> 
        <dependency> 
          <groupId>org.apache.commons</groupId> 
          <artifactId>commons-lang3</artifactId> 
          <version>3.1</version> 
        </dependency> 
        <dependency> 
          <groupId>org.xebia.roo.addon</groupId> 
          <artifactId>xebia-spring-roo-addon-annotations</artifactId> 
          <version>0.0.1</version> 
        </dependency> 
      </dependencies> 
    
      <repositories> 
        <repository> 
          <id>spring-roo-addon-annoations</id> 
          <name>Spring Roo Addon Annotation</name> 
          <url>https://xebia-spring-roo-addon-annotations.googlecode.com/svn/repo</url> 
        </repository> 
      </repositories>  
    </configuration>

    更新 ComparetoOperationsImpl 类的 setup() 方法来读取已更新的 configuration.xml 文件中指定的新依赖关系和存储库。请参见 清单 20

    清单 20. 更新 ComparetoOperationsImpl 类的 setup() 方法
    public void setup() { bu
    
            List<Dependency> dependencies = new ArrayList<Dependency>(); 
    
            Element configuration = XmlUtils.getConfiguration(getClass()); 
            for (Element dependencyElement : 
                XmlUtils.findElements("/configuration/dependencies/dependency", 
                    configuration)) { 
    
    	    dependencies.add(new Dependency(dependencyElement)); 
            
    	} 
    
            projectOperations.addDependencies("", dependencies); 
    
            List<Element> repositories = XmlUtils.findElements( 
                    "/configuration/repositories/repository", configuration); 
            for (Element repositoryElement : repositories) { 
                Repository repository = new Repository(repositoryElement); 
                projectOperations.addRepository(projectOperations.getFocusedModuleName(),
                    repository); 
            } 
    }

    接着执行以下步骤:

  10. 通过运行 mvn clean install 再次构建附加组件。
  11. 更新客户端,正如您在 步骤 4 中生成它一样。
  12. 要移除旧版附加组件,请在 Roo shell 中键入此命令:
    addon remove --bundleSymbolicName
    org.xebia.roo.addon.compareto
  13. 通过运行 osgi install 命令再次安装该附加组件。
  14. 安装附加组件后,运行 compareto setupcompareto all 命令。

    您将看到 compareTo ITD。运行 perform package 命令,一切表现良好。

一旦测试到附加组件正在部署环境中运行,就可以将它放入您所创建的 Google 代码项目中。要向外部世界发布该附加组件,请遵循与 Spring Roo 简介,第 3 部分:开发 Spring Roo 的附加组件 中发布 i18n 附加组件相同的过程。同样地,要使用 RooBot 注册附加组件,请遵循 Spring Roo 文档


实现非 OSGi JDBC 驱动程序 OSGi 与包装器附加组件的兼容

包装器附加组件通常用于将非 OSGi JDBC 驱动程序转换成 OSGi 兼容绑定。您需要包装 JDBC 驱动程序的一个地方是:您必须使用 Spring Roo 对某个 Oracle 数据库执行反向工程。由于版权问题,pring Roo 并没有提供 OSGi Oracle JDBC 驱动程序。在对一个 Oracle 数据库执行反向工程之前,首先要实现驱动程序 OSGi 的兼容性。要为 Oracle JDBC 驱动程序创建一个包装器附加组件,请执行如下操作:

  1. 通过键入以下命令,将 Oracle JDBC 安装在您的本地机器的 Maven 目录中。
    mvn install:install-file -Dfile=ojdbc5.jar -DgroupId=com.oracle 
      -DartifactId=ojdbc5 -Dversion=11.2.0.2 -Dpackaging=jar
  2. 创建一个名为 oracle-wrapper-addon 的新目录,并从此命令行导航至该目录。
  3. 打开 Roo shell 并执行包装器附加组件命令:addon create wrapper --topLevelPackage com.oracle.wrapper.jdbc --groupId com.oracle --artifactId ojdbc5 --version 11.2.0.2 --vendorName Oracle --licenseUrl oracle.com

    该命令只生成 pom.xml 文件,该文件将用于将一个非 OSGi Oracle JDBC 驱动器转换成一个 OSGi 驱动程序。

  4. 在 Roo shell 内运行此命令,以创建该 OSGi 绑定:perform command --mavenCommand bundle:bundle

就这样,您现在已经成功创建了一个非 OSGi jar 的 OSGi 绑定。


结束语

在本文中,您了解了 Spring Ro 中的高级附加组件和包装器附加组件。还学习了如何创建高级附加组件和包装器附加组件。本文完成了探索 Spring Roo 的一个重要特性的旅程:编写附加组件。无论何时想扩展 Spring Roo 函数,请记得考虑创建附加组件。

在本系列 "Spring Roo 简介" 的下一篇文章,我们将讨论如何使用 Spring Roo 编写 GWT 应用程序。


下载

描述名字大小
样例代码bookshop.zip14KB
样例代码spring-dw-roo-compareto-addon.zip18KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology
ArticleID=815557
ArticleTitle=Spring Roo 简介,第 5 部分: 编写 Spring Roo 的高级附加组件和包装器附加组件
publish-date=05142012