Введение в Spring Roo: Часть 5. Создание расширенных дополнений и дополнений-оберток Spring Roo

Расширение возможностей Spring Roo

Расширенные дополнения Spring Roo предоставляют механизмы для добавления в приложения Java™-кода (примером может служить дополнение, которое может писать методы equals и hashcode для вашего доменного объекта). При помощи команды addon create можно создать шаблон дополнения. Затем можно расширить возможности шаблона в соответствии с требованиями разработчиков. В данной статье рассматриваются действия по созданию расширенного дополнения.

Шекхар Гулати, старший консультант, Xebia

Шекхар Гулати (Shekhar Gulati) работает Java-консультантом в Xebia India. Более шести лет он занимается корпоративными Java-приложениями. Имеет обширный опыт работы со Spring-проектами, такими как Spring, Spring-WS и Spring Roo. В сферу его интересов входят Spring, базы данных NoSQL, Hadoop, RAD-среды (такие как Spring Roo), облачные вычисления (в основном PaaS-сервисы, такие как Google App Engine, CloudFoundry, OpenShift). Является активным автором статей для JavaLobby, Developer.com, IBM developerWorks и своего собственного блога http://whyjava.wordpress.com/. Связаться с ним можно в Твиттере (@ http://twitter.com/#!/shekhargulati).



20.02.2013

В третьей статье серии "Введение в Spring Roo" я рассматривал архитектуру дополнений Spring Roo и создание дополнения интернационализации и простого дополнения при помощи команды addon create. Данная статья посвящена двум оставшимся типам дополнений, поддерживаемых Spring Roo, – расширенным дополнениям (advanced add-on) и дополнениям-оберткам (wrapper add-on). Рекомендуется сначала прочесть третью часть серии.

Введение в расширенные дополнения

Расширенные дополнения позволяют Spring Roo делать все, что могут делать простые дополнения (например, обновлять зависимости или плагины в POM-файле Maven и обновлять или добавлять файлы конфигурации), а также расширять существующие Java-типы и вводить новые Java-типы при помощи ITD-объявлений AspectJ. Способность добавлять исходный код делает расширенные дополнения очень мощным инструментом в сравнении со всеми другими дополнениями. Перед созданием собственного расширенного дополнения рассмотрим дополнение, предоставляемое Spring Roo.


Расширенное дополнение в действии

Одним из расширенных дополнений является JPA, выполняющее работу, связанную с персистентностью. Оно добавляет поддержку баз данных и создает новые объекты. Чтобы увидеть его в действии, запустите Roo Shell и выполните команды, приведенные в листинге 1. В этой статье я использую Spring Roo версии 1.2.0.M1.

Листинг 1. Пример JPA
project --topLevelPackage com.dw.demo --projectName entity-demo 
jpa setup --database FIREBIRD --provider HIBERNATE 
entity --class ~.domain.Book

Команды jpa setup и entity соответствуют расширенному дополнению org.springframework.roo.addon.jpa. Результаты работы команд jpa setup и entity, отображаемые в Roo Shell, четко показывают разницу между простыми и расширенными дополнениями. В листинге 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

Результаты показывают, что создается один Java-файл Book.java и четыре файла *.aj. Расширенное дополнение легко отличить от других по тому, что оно создает файлы Java, *.aj-файлы или файлы обоих этих типов, как, например, команда entity. Такие *Roo_*.aj файлы называются межтиповыми или ITD-объявлениями (Intertype 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. Все расширенные дополнения регистрируют свои сервисы в среде исполнения OSGi, представляемой Spring Roo. Эти сервисы определяют условия, при которых инициируется генерирование кода. Для всех расширенных дополнений инициирующей точкой (triggering point) является наличие аннотации. Например, расширенное дополнение для генерирования метода toString() инициируется только в том случае, если Java-тип имеет аннотацию RooToString. Это справедливо и для других аннотаций.
  3. При использовании entity --class ~.domain.Book дополнение создаст Java-файл Book.java с аннотациями. Когда Java-тип имеет эти аннотации, активизируются другие дополнения, и для них записываются .aj-файлы.

Более подробное разъяснение вы получите при создании своего собственного расширенного дополнения.

Дополнение org.springframework.roo.addon.jpa – это только один пример расширенного дополнения, предоставляемого Spring Roo. К другим расширенным дополнениям относятся GWT, controller, JSON и многие другие. Версия Spring Roo 1.2.0 содержит еще два расширенных дополнения – addon-equals и addon-jsf. Дополнение addon-equals предоставляет реализацию методов equals и hashcode для логического объекта, а addon-jsf предоставляет поддержку JSF в приложениях Spring Roo. Чтобы поэкспериментировать с последней версией Spring Roo, скомпонуйте исходный код Spring Roo или загрузите ночной снимок системы из репозитория Spring Roo.


Включение метода compareTo() в My Entity Class

Для реализации интерфейса java.lang.Comparable и предоставления реализации метода compareTo() обычно требуются объекты-значения или логические объекты. Интерфейс Comparable предусматривает общий порядок объектов каждого класса, реализующего его. При реализации Comparable можно:

  1. Вызывать java.util.Collections.sort и java.util.Collections.binarySearch.
  2. Вызывать java.util.Arrays.sort и java.util.Arrays.binarySearch.
  3. Использовать объекты в качестве ключей в java.util.TreeMap.
  4. Использовать объекты в качестве элементов в java.util.TreeSet.

В данной статье мы создадим расширенное дополнение, которое будет предоставлять реализацию метода compareTo() для логических объектов, создаваемых в приложении. Поскольку мы хотим добавить в приложение Java-код, необходимо создать расширенное дополнение.


Настройка проекта

В документации по Spring Roo подробно объясняется процесс настройки проекта и Maven-репозитория в Google Code, поэтому я не буду повторяться. В качестве имени проекта я буду использовать spring-dw-roo-compareto-addon.

Если у вас не установлена последняя версия Spring Roo 1.2.0.RC1, загрузите ее с Web-сайта проекта. Разархивируйте и установите ее (см. первую статью (EN) серии).

Некоторые классы, использовавшиеся в более ранних версиях Spring Roo, были удалены или не рекомендуются к применению.


Создание расширенного дополнения

После настройки проекта появится каталог spring-dw-roo-compareto-addon с единственной папкой .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) не нуждаются в объяснении, поскольку рассматривались в третьей части серии (EN). Более интересными артефактами являются:

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

Давайте рассмотрим их по очереди.

  • ComparetoCommands.java. Этот класс реализует интерфейс CommandMarker и предоставляет два вида методов – один с аннотацией CliAvailablityIndicator, а второй с аннотацией CliCommand. Аннотация CliAvailablityIndicator указывает Spring Roo, когда команда должна быть видимой. Например, команда 'entity' не будет доступна до определения пользователем параметров персистенции в Roo Sshell или непосредственно в проекте. Методы с аннотацией @CliCommand регистрируют команду в Roo Shell. Аннотация @CliCommand имеет два атрибута – value, определяющий имя команды, и help, определяющий подсказку, отображаемую командой help. Подробное объяснение класса *Commands приведено в третьей части серии (EN).
  • ComparetoOperationsImpl.java. Класс ComparetoCommands делегирует всю работу классу ComparetoOperationsImpl. В этом классе генерируется четыре метода:
    • isCommandAvailable(). Этот метод вызывается методом с аннотацией CliAvailabilityIndicator ComparetoCommands в классе ComparetoCommands для проверки условия видимости команды. Это гарантирует зависимость команды от контекста. Данный метод выполняет различные проверки. Например, команда должна быть видима только тогда, когда проект создан, или команда должна быть видима только тогда, когда настроена персистенция. Условие видимости команды указывать не обязательно. Просто возвратите true, чтобы установить постоянную видимость команды.
    • setup(). Этот метод вызывается методом setup() в классе ComparetoCommands с аннотацией @CliCommand. По коду видно, что этот класс отвечает за выполнение связанных с настройкой задач, таких как добавление зависимостей Maven, добавление репозиториев Maven, создание или обновление файлов контекста Spring (как в дополнении Jamon Roo, рассмотренном в третьей части (EN) серии).
    • annotateType(). Этот метод и метод annotateAll() – это новые методы, отсутствующие в простых дополнениях. Назначение этого метода – добавлять аннотацию (RooCompareto) в указанный Java-тип. Этот метод использует некоторые предоставляемые Spring Roo сервисы для извлечения подробных сведений о Class для данного Java-типа и добавления к нему аннотации RooJCompareto.
    • annotateAll(). Этот метод ищет все типы с аннотацией RooJavaBean и вызывает для них метод annotateType(). Используйте его, когда все логические объекты должны иметь аннотацию RooCompareto.
  • RooCompareto.java. Присутствие данной аннотации активизирует дополнение для генерирования кода.
  • ComparetoMetadataProvider.java. Этот класс представляет собой сервис Spring Roo и вызывается Roo для извлечения метаданных для дополнения. Этот класс регистрирует триггеры для добавления и удаления метаданных. В этом классе ничего менять не нужно, но помните, что он имеет один метод getMetadata(), который будет вызываться Spring Roo везде, где есть какой-либо Java-тип с аннотацией RooCompareto.
  • ComparetoMetadata.java. Этот класс отвечает за генерирование ITD-объявления, соответствующего дополнению. В сгенерированном коде он использует класс ItdTypeDetailsBuilder для создания ITD-объявления с полем и методом. Далее в статье мы изменим сгенерированный по умолчанию код в соответствии с требованиями добавления метода compareTo и реализации интерфейса Comparable.

Изменение дополнения в соответствии с требованиями

Мы хотим создать дополнение, которое должно добавлять метод compareTo в классы логических объектов. Необходимо выполнить следующие действия:

  • Добавьте зависимость Maven commons-lang версии 3.1 в целевой проект. Это необходимо, потому что commons-lang предоставляет класс компоновщика CompareToBuilder, который будет использоваться для сборки метода compareTo.
  • Реализуйте в классе логического объекта интерфейс Comparable.
  • Создайте ITD-объявление для метода compareTo.

Добавление зависимости Maven

Для выполнения этих требований необходимо изменить классы ComparetoOperationsImpl и ComparetoMetadata. Поочередно выполните следующие изменения:

  1. Во-первых, добавьте зависимость Maven commons-lang в целевой проект. Измените файл configuration.xml так, чтобы в нем указывалась зависимость commons-lang вместо зависимости, предоставляемой Spring по умолчанию, как показано в листинге 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. Теперь измените реализацию метода setup() в классе ComparetoOperationsImpl, указав чтение зависимости commons-lang Maven вместо установленной по умолчанию зависимости Spring batch (см. листинг 7). Методы annotateType и annotateAll() здесь не показаны, поскольку они не меняются.
    Листинг 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() {
         // Установить Google Code репозиторий дополнений, необходимый
         // для получения аннотации
            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>();
         // Установить зависимость в 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);
        }
    }

Сделанные изменения аналогичны изменениям при создании простого модуля Jamon в третьей статье серии (EN).

Реализация интерфейса Comparable класса логического объекта

После выполнения изменений в коде по добавлению зависимости Maven необходимо убедиться, что ваш класс логического объекта реализует интерфейс java.lang.Comparable. Для этого измените ITD-объявление, сгенерированное классом ComparetoMetadata. Классы метаданных генерируют ITD-объявление, использующее класс ItdTypeDetailsBuilder, который предоставляет различные методы add для добавления в ITD-объявление методов, полей, аннотаций, интерфейса и т.д. Для реализации интерфейса Java-типа используйте метод addImplementsType в классе ItdTypeDetailsBuilder (см. листинг 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();
    }

Создание ITD-объявления для метода compareTo

После реализации интерфейса Comparable Java-типа необходимо предоставить реализацию метода compareTo. Класс CompareToBuilder предоставляет интерфейс для создания метода compareTo. Дополнение Spring Roo equals использует EqualsBuilder и HashcodeBuilder для предоставления реализации методов equals и hashcode. Давайте рассмотрим пример, чтобы выяснить, как CompareToBuilder помогает в создании метода compareTo. Предположим, что у нас имеется логический объект Book, и мы хотим предоставить для него реализацию compareTo, используя CompareToBuilder. В листинге 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;
    }

    // getter- и setter-методы

    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 + "]";
    }

}

Метод compareTo в листинге 9 выполняет следующие действия:

  • Если o не является instanceOfBook, возвращает -1.
  • Если o является instanceOfBook, выполняет приведение типа o в Book.
  • Создает объект класса CompareToBuilder и вызывает метод append для полей.

Создайте метод compareTo, выполнив следующие действия:

  1. Если o не является instanceOfBook, возвратите -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() генерирует метаданные метода 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 Code. Теперь можно приступить к тестированию только что созданного метода compareTo.

  1. Закройте Roo Shell и выполните команду mvn clean install. Во время сборки отобразится запрос на ввод идентификационной фразы GPG.
  2. После завершения сборки дополнения откройте новое окно командной строки и создайте каталог 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 и нажмите клавишу Tab для просмотра трех команд дополнения compareto (см. листинг 15).
    Листинг 15. Просмотр команд дополнения compareto
    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 генерируется только для одного класса логического объекта, или compareto all, если этот метод генерируется для всех классов логических объектов. Добавим метод compareTo во все классы логических объектов примера приложения bookshop (см. раздел Загрузка), как показано в листинге 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 видно, что генерируется ITD-объявление Book_Roo_Compareto.aj. Этот файл будет содержать метод 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 не разрешает зависимость каждого используемого дополнения. Он имеет общий jar-файл аннотаций Spring Roo, содержащий все зависимости. Я создал проект 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>

    Обновите метод setup() класса ComparetoOperationsImpl для чтения новых зависимостей и репозиториев, указанных в обновленном файле configuration.xml (см. листинг 20).

    Листинг 20. Листинг 20. Обновление метода setup() класса ComparetoOperationsImpl
    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 setup и compareto all.

    Вы увидите ITD-объявления compareTo. Выполните команду perform package, и все заработает.

После тестирования в среде разработки можно поместить дополнение в созданный нами проект Google Code. Для публикации дополнения выполните процедуру, аналогичную процедуре публикации дополнения интернационализации в третьей части серии (EN). Также зарегистрируйте дополнение в RooBot, следуя указаниям документации по Spring Roo.


Реализация совместимости JDBC-драйверов с OSGi при помощи дополнения-обертки

Типичным вариантом использования дополнения-обертки является преобразование несовместимых с OSGi JDBC-драйверов в совместимые. Одной из ситуаций, когда нам нужен JDBC-драйвер, является реверсивное проектирование базы данных Oracle с использованием Spring Roo. Spring Roo не содержит OSGi-совместимых JDBC-драйверов для Oracle из-за проблем с авторскими правами. Перед реверсивным проектированием базы данных Oracle сделайте драйвер OSGi-совместимым. Чтобы создать дополнение-обертку для JDBC-драйвера Oracle:

  1. Установите Oracle JDBC jar-файл в 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 JDBC-драйвера Oracle в OSGi-драйвер.

  4. В Roo Shell для создания OSGi-пакета выполните следующую команду: perform command --mavenCommand bundle:bundle.

Это все! Вы успешно создали OSGi-пакет для не-OSGi jar-файла.


Заключение

В данной статье вы узнали о расширенных дополнениях и дополнениях-обертках в Spring Roo. Вы также научились создавать расширенные дополнения и дополнения-обертки. Этим мы завершаем исследование очень важной функциональной возможности Spring Roo – создания дополнений. Всегда, когда нужно расширить функциональность Spring Roo, подумайте о создании дополнений.

В следующей части серии статей Введение в Spring Roo (EN) я расскажу о создании GWT-приложений с использованием Spring Roo.


Загрузка

ОписаниеИмяРазмер
Пример кодаbookshop.zip14 КБ
Пример кодаspring-dw-roo-compareto-addon.zip18 КБ

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java
ArticleID=858637
ArticleTitle=Введение в Spring Roo: Часть 5. Создание расширенных дополнений и дополнений-оберток Spring Roo
publish-date=02202013