В одну телегу впрячь не можно
Коня и трепетную лань
А.С. Пушкин
Всем привет!
В данной заметке я хочу рассказать, как интегрировать язык 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
Назовем его… скажем…
@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-контроллеров.