Интеграция Groovy в JEE приложение

В одну телегу впрячь не можно
Коня и трепетную лань
                        А.С. Пушкин

Всем привет!

В данной заметке я хочу рассказать, как интегрировать язык Groovy в существующее JEE приложение на основе Maven. Выражаю благодарность Антону Щастному schaan за разрешение на использование исходного кода его проекта в качестве отправной точки. Поэтому данный топик можно считать продолжением его статьи Учимся готовить: Spring 3 MVC + Spring Security + Hibernate.

Начнем.

Подготовка проекта.

Я использую Eclipse в сборке SpringSource Tool Suite. Выкачиваем проект ContactManager. Но не торопитесь его открывать в IDE. Будет даже надежнее, если вы вообще удалите все файлы проекта: .classpath .project и каталог .settings. Ибо с момента публикации статьи Антона технологии шагнули вперед, вышли новые версии STS (с другой структурой проекта и новой версией плагина m2e), поэтому мы сначала исправим pom-файл, затем на его основе STS нам создаст новый проект.

Для простоты я удалил из pom.xml зависимости от аспектов и Spring Roo. Также заменил MySQL на более привычный мне PostgreSQL (см. файл jdbc.properties). Но это все присказка, а вот и сказка: добавляем зависимость

<dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>1.8.6</version>
</dependency>


Собственно это почти все, Groovy уже интегрирован в наш проект. Осталось только разобраться с совместной компиляцией Java и Groovy.

groovy-eclipse-compiler

Около года мы пользовались плагином gmaven. В работе с ним были свои «подводные камни», нет смысла уже о них вспоминать, потому что мы перешли на groovy-eclipse-compiler. Редактируем pom.xml

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>2.3.2</version>
	<configuration>
		<source>1.6</source>
		<target>1.6</target>
		<encoding>UTF-8</encoding>
		<compilerId>groovy-eclipse-compiler</compilerId>
	</configuration>
	<dependencies>
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-eclipse-compiler</artifactId>
			<version>2.6.0-01</version>
		</dependency>
	</dependencies>
</plugin>


Все, с pom.xml закончили, запускаем STS и импортируем в него проект. File -> Import -> Maven -> Existing Maven Projects. «Зараженный Groovy» проект выглядит совершенно обычно.



STS не ругается, это хорошо, но для чистоты эксперимента нужно собрать все мавеном. Выполняем
mvn clean package

и видим в логе искомое:

[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ contactmanager ---
[INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files


Переходим на Groovy

Итак с java мы практически попрощались, дальше будем писать на Groovy. Контактов набралось уже изрядно и мы хотим эти самые контакты группировать по типам: «семья», «работа» и т.д.

Начнем с POJO, то бишь с POGO.

Создадим каталог src/main/groovy, добавим его в BuildPath, создадим пакет (в нашем примере com.acme.contactmanager.domain)

На пакете кликаем правой кнопкой -> New -> Groovy class

Назовем его… скажем… Полуэкт ContactType и напишем его исходный код:

@Entity
@Table(name = "CONTACT_TYPES", uniqueConstraints = [
	@UniqueConstraint(columnNames = ["code"]),
	@UniqueConstraint(columnNames = ["name"])
])
class ContactType {

	@Id
	@GeneratedValue
	Integer id

	@Basic
	@Column(unique = true, nullable = true)
	String code

	@Basic
	@Column(unique = true, nullable = true)
	String name

	@Basic
	@Column(nullable = true)
	Boolean defaulttype = false

	@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.REFRESH, CascadeType.MERGE], mappedBy = "contacttype")
	List<Contact> contacts = null
}


Ничего сверхестественного, обычные аннотации, разве что в массивах вместо фигурных скобок квадратные. Нет модификаторов, геттеров-сеттеров, точек с запятой, все чисто-аккуратно.

Сообщаем хибернейту, что у нас появилась новая сущность

<hibernate-configuration>
	<session-factory>
		<mapping class="net.schastny.contactmanager.domain.Contact" />
		<mapping class="com.acme.contactmanager.domain.ContactType" />
	</session-factory>
</hibernate-configuration>


Добавляем новое поле в Contact.java, тут уже без геттеров-сеттеров не обойтись

	@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH, optional = false)
	private ContactType contacttype;

	public ContactType getContacttype() {
		return contacttype;
	}

	public void setContacttype(ContactType contacttype) {
		this.contacttype = contacttype;
	}


Собираемся, деплоимся и, если в хибернейте стоит опция hibernate.hbm2ddl.auto=update, то видим в БД новую таблицу CONTACT_TYPES и новое поле CONTACTS.contacttype_id.

Примечание: Если в БД уже есть контакты, то not null ограничение хибернейт поставить не сможет, несмотря на присутствие аннотации optinal=false. Заполнение ссылок дефолтными значениями и выставление not null ограничения оставлю читателям в качестве домашнего задания.

Groovy DAO

Но рано останавливаться на достигнутом. Следующий шаг — groovy-dao. Он, как и в случае с java, должен состоять из пары «интерфейс-реализация», иначе Spring будет недоволен. Повторяем шаги с созданием пакета (на этот раз — com.acme.contactmanager.dao) и созданием класса ContactTypeDAO.groovy

interface ContactTypeDAO {

	void addContactType(ContactType contactType)

	List<ContactType> listContactTypes()

	void removeContactType(Integer id)

	ContactType getDefault()
}


Все на 99% как в java, поэтому переходим к реализации. Не вершина программерского мастерства, но для примера достаточно. Метод listContactTypes() уже содержит основные прелести, ради которых мы все и затеяли (см. комментарии в коде):

@Repository
class ContactTypeDAOImpl implements ContactTypeDAO {

    @Autowired
    private SessionFactory sessionFactory;

    private Session getCurrentSession() {
        sessionFactory.getCurrentSession()
    }

    @Override
    @Transactional
    void addContactType(ContactType contactType) {
        currentSession.save(contactType)
    }

    @Override
    @Transactional
    List<ContactType> listContactTypes() {
        
        // в вызовах любых get-методов можно опускать префикс get и пустые скобки
        def result = currentSession.createQuery("from ContactType").list()

        // проверка, что список пустой, выглядит так
        if(!result){

            // Нужен List<Map<String, Object>>? Что может быть проще!
            def types = [
                // кавычки для ключей не обязательны,
                // значения могут быть любого типа
                [name:'Семья', code:'family', defaulttype: false],
                [name:'Работа', code:'job', defaulttype: false],
                [name:'Знакомые', code:'stuff', defaulttype: true]
            ]

            // вместо цикла можно использовать замыкание 
            types.each { type ->
                ContactType contactType = new ContactType(
                        // в любой Groovy-класс по умолчанию добавляется конструктор,
                        // принимающий параметром Map
                        code: type.code,
                        name : type.name,
                        defaulttype : type.defaulttype
                        )
                currentSession.save(contactType)
                // перегруженный оператор << добавляет элемент в список
                // переменная result доступна в контексте замыкания
                result << contactType
            }
        }
        // ключевое слово return не обязательно
        result
    }

    @Override
    @Transactional
    void removeContactType(Integer id) {
        ContactType contactType = currentSession.get(ContactType.class, id)
        if (contactType) {
            currentSession.delete(contactType)
        }
    }

    @Override
    @Transactional
    ContactType getDefault() {
        currentSession.createCriteria(ContactType.class)
                .add(Restrictions.eq('defaulttype', true))
                .uniqueResult()
    }
}


Осталось интегрировать спежеиспеченный DAO в Java-service:
public interface ContactService {

    // старые методы где-то тут ...

    public List<ContactType> listContactType();
}


@Service
public class ContactServiceImpl implements ContactService {
 
    // старые методы где-то тут ...

    @Autowired
    private ContactTypeDAO contactTypeDAO;
    
    @Override
    @Transactional
    public List<ContactType> listContactType() {
        return contactTypeDAO.listContactTypes();
    }
}


Добавляем вызов в контроллер:
	@RequestMapping("/index")
	public String listContacts(Map<String, Object> map) {

		map.put("contact", new Contact());
		map.put("contactList", contactService.listContact());
		// список типов контактов для JSP
		map.put("contactTypeList", contactService.listContactType());

		return "contact";
	}


Добавляем локализованные сообщения в файлы messages*.properties и выпадающий список типов на JSP (см. в проекте), собираемся, деплоимся. Проверяем:



Дальше можно использовать Groovy для тестов, парсить XML и т.д. и т.п.

Исходный код проекта на GitHub.

Спасибо за внимание, пишите на Groovy!

Ссылки

Учимся готовить: Spring 3 MVC + Spring Security + Hibernate
Groovy Home
Groovy Eclipse compiler
SpringSource Tool Suite
Programming Groovy: Dynamic Productivity for the Java Developer
Проект Lombok, или Объявляем войну бойлерплейту

PS как добавить тесты в проект написано тут. В том числе — тесты для web-контроллеров.
Share post

Comments 24

    0
    Спасибо! Groovy — рулез!

    А чем groovy-eclipse-compiler лучше gmaven? Он как то связан с эклипсом? Если у нас Intellij можно им пользоваться?
      0
      это-ж в Maven, причем тут Intellij?
        0
        Вот я и удивился названию плагина. Подумал — может он чего то для экслипса паралельно генерирует.
          0
          Нет, он просто пользуется eclipse-овским компилятором.
            0
            Так а всё таки — чем он лучше?
              0
              Это не ко мне, см. комент ниже :)
        +1
        GMaven на первом проходе компилит groovy классы в Java-заглушки, для поддержки UTF-8 требует явного указания опции -Dgroovy.source.encoding=UTF-8 (но даже при этом не справляется с русскими символами в Java-аннотациях). GEC обходится без заглушек, работает быстрее и стабильнее. Но это все больше касается мавена, на который мы как раз и ориентируемся, чтобы избежать специфического для IDE поведения.

        В качестве ответа про Idea скажу лишь, что в нашей конторе я один сижу на Эклипсе :)

          +1
          Ясно, спасибо!
          Idea для Groovy — прекрасен :) Переходите :)
            0
            Вечный спор. Но пока даже Идея в поддержке груви не идеальна. каламбур :)
              +1
                0
                да-да, «идея понимает контекст» (С)
                но когда она для выполнения скрипта

                def a = 1
                println a
                


                собирает целиком проект — это вызывает… эээ… некоторое недоумение
                  +1
                  Это регулируется, Вы можете сказать это не делать.
                    0
                    передал коллегам, но они так и не смогли найти, где это настроить.
                    не подскажете подробнее?
                      0
                      Ну во первых, IDEA не должна при каждом запуске пересобирать проект, она должна перекомпилировать файлы, которые изменились. У нас она почему то несколько файлов все такие перекомпилирует (из огромного проекта), проверяет директории ресурсов и т.д. — но всё это занимает несколько секунд.

                      Другое дело, что на компиляцию мавена она не надеется и в ПЕРВЫЙ запуск она и правда перекомпилирует всё.

                      Во вторых в меню запуска программы (там, где задаёте комманд лайн параметры) можно сказать вообще ничего не строить перед запуском
        0
        Я просто оставлю это здесь:

        gradle.org/docs/current/userguide/groovy_plugin.html
          0
          gradle у нас в планах, но из-за специфики проекта c градлом есть свои сложности, которые пока решить не удалось.
            0
            А давайте вместе решать :)
            Чего не получилось?
              0
              Прямо сейчас я не готов «открыть дискуссию» (С), но спасибо за предложение, обращусь при первой же возможности :)
                0
                Нет проблем, обращайтесь :)
          0
          Видео доклада с ADD#3 на смежную тематику
          addconf.ru/event.sdf/ru/add_3/authors/AlexanderShlyannikov/773
          0
          написал продолжение — добавляем тестирование контроллеров
          habrahabr.ru/post/171911
            0
            Часть 4, добавляем REST-сервис
            habrahabr.ru/post/173593
              0
              Часть 5, добавляем работу через HTTPS
              habrahabr.ru/post/174513

              Only users with full accounts can post comments. Log in, please.