В одну телегу впрячь не можно
Коня и трепетную лань
А.С. Пушкин
Всем привет!
В данной заметке я хочу рассказать, как интегрировать язык 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 и т.д. и т.п.
Спасибо за внимание, пишите на Groovy!
Ссылки
Учимся готовить: Spring 3 MVC + Spring Security + Hibernate
Groovy Home
Groovy Eclipse compiler
SpringSource Tool Suite
Programming Groovy: Dynamic Productivity for the Java Developer
Проект Lombok, или Объявляем войну бойлерплейту
Автор: monzdrpower