SPRING-SOURCE.RU

GWT 2 Spring 3 JPA 2 Hibernate 3.5 – Eclipse и Maven 2

Небольшое отступление

Для этого урока не плохо бы знать что такое GWT (Google Web Toolkit).

Напишу кратко, как я понимаю взаимосвязь GWT и Spring:

В каком месте происходит контакт GWT приложения с Spring приложением? Этот вопрос важен для людей, которые хотят начать использовать эти две технологии вместе. Ответ - это происходит на уровне сервисов. Для начала, разрабатывается отдельно приложение GWT без какой-либо связи со Spring, а потом мы присоединяем его к Spring. Давайте поясним эти два шага:

  1. GWT приложение состоит из клиентской части и серверной. Серверная часть - это реализация сервисов, которые находятся у клиента. В GWT приложении мы создаем proxy сервиса в коде которого указана аннотация @RemoteServiceRelativePath с именем, например, "test". Так вот, это же имя должно быть и в url-pattern в web.xml файле. Url-pattern запускает для нас реализацию сервиса "test" на сервере. Весь этот RPC механизм вызова работает через сервлеты. В итоге клиент может вызывать методы которые делают свою работу на сервере.
  2. Когда GWT приложение готово, то мы просто подменяем реализацию GWT сервиса на Spring, используя аннотацию @Service. В данном случае RPC механизм будет работать без явного объявления сервлета сервиса, а через spring4gwt, который автоматически будет связывать сервис с его реализацией. Таким образом мы получаем GWT в паре со Spring.

Статья

Это руководство пошагово рассказывает о том как разработать простое web приложение, используя Google Web Toolkit (GWT) для клиентской части, и Spring для серверной части. Web приложение будет выполнять CRUD операции к базе данных. В качестве слоя доступа к данным мы будем использовать Hibernate, как реализацию JPA, и для базы данных мы будем использовать Hypersonic. Конечно же, вы можете изменять конфигурацию и использовать все, что вы хотите. Развертывать web приложение мы будет на Apache — Tomcat. Также будем использовать maven.

Maven поставляется в двух вариантах, standalone с поддержкой командной строки, и как IDE (Eclipse и Netbeans) плагин. Разрабатывать web приложение мы будет с помощью Eclipse с Maven поддержкой.

Что нам понадобится:

  1. Eclipse
  2. Maven плагин для Eclipse
  3. GWT — Spring «glue» библиотека spring4gwt

Начнем:

  1. Создадим новый Maven проект (File — Project — Maven — Maven Project)
  2. В «Select project name and location», убедитесь, что параметр «Create a simple project (skip archetype selection)» не установлен. Нажимаем «Далее»
  3. В «Select an Archetype» выбираем «Nexus Indexer» в выпадающем списке «Catalog» и после того как зона выбора архетипов обновлена, выберите архетип «gwt-maven-plugin» от «org.codehaus.mojo». Нажимаем «Далее»
  4. В «Enter an artifact id» странице вы можете определить имя и главный пакет вашего проекта. Мы установили «Group Id» как «com.javacodegeeks» и «Artifact Id» как «gwtspring», в итоге пакет определяеся как «com.javacodegeeks.gwtspring» и имя проекта «gwtspring». Жмем «Finish».

Если у кого-то не работает плагин для Eclipse, ту же операцию можно сделать и из командной строки, набрав:

      
mvn archetype:generate \ 
-DarchetypeGroupId=org.codehaus.mojo \ 
-DarchetypeArtifactId=gwt-maven-plugin \ 
-DgroupId=com.javacodegeeks \ 
-DartifactId=gwtspring
		

После этого зайдите в каталог и наберите mvn eclipse:eclipse, при этом вы спишите из интернета ненужные библиотеки. Теперь вы сможете импортировать ваш проект в среду разработки Eclipse.

Давайте рассмотрим структуру проекта Maven GWT:

  1. Директория /src/main/java содержит исходные файлы для динамичного контента приложения
    - поддиректория {main_package}.client содержит исходные файлы, доступные только для клиентской стороны приложения
    - {main_package}.server файлы для серверной части (эта директория не создается по умолчанию)
    - {main_package}.shared содержит файлы, доступные как клиенту так и среверу (директория также не создается автоматически)
  2. /src/main/resources содержит файлы для статического контента (html страницы)
  3. /src/test/java содержит файлы для unit тестирования
  4. /src/main/webapp содежрит основные файлы для создания действующего web прложения (web.xml)
  5. /target содержит откомпилированные и упакованные результаты
  6. /war — используется в процессе build и package (построения и упаковки)
  7. Файл «pom.xml» объектная модель проекта (POM). Файл содержит всю конфигурацию связанную с проектом

Для того, чтобы правильно интегрировать Spring и GWT, мы должны предоставить web приложению все необходимые библиотеки. Откройте графический редактор «pom.xml» и выполните следующие действия:

  1. Секцию «Properties» установите в «Overview» и выполните следующие действия:
    - Создайте новое свойство с именем org.springframework.version и значением 3.0.1.RELEASE
    - Создайте новое свойство с именем org.hibernate.version и значением 3.5.1-Final
    - Измените gwt.version на 2.0.3
    - Измените значения свойства maven.compiler.source и maven.compiler.target в зависимости от вашей версии Java Runtime environment, мы будем использовать 1.6
  2. Перейдите на страницу «Dependencies» в POM редакторе и создайте следующие зависимости (вы должны заполнить «GroupId», «Artifact Id» и «Version» полей «Dependency Details») :

    Group IdArtifact IdVersion
    org.springframeworkspring-orm${org.springframework.version}
    org.springframeworkspring-web${org.springframework.version}
    org.hibernatehibernate-core${org.hibernate.version}
    org.hibernatehibernate-annotations${org.hibernate.version}
    org.hibernatehibernate-entitymanager${org.hibernate.version}
    org.hibernate.javax.persistencehibernate-jpa-2.0-api1.0.0.Final
    org.slf4jslf4j-log4j121.5.8
    org.hsqldbhsqldb1.8.0.10
    c3p0c3p00.9.1.2

  3. Нажмите на кнопку «Show Advanced Tabs» в верхнем правом углу POM редактора. Перейдите на страницу «Repositories» и создайте следующий репозиторий:
    - Id : JBoss URL : http://repository.jboss.org/maven2/

Обратите внимание, что возможны проблемы с версиями артефактов. Если у вас случилось так, что версия отсутствует в репозитории, то решать ее вам придется самим.

Не все библиотеки доступны через публичные репозитории. В нашем случае мы должны создать «lib» директорию в «/src/main/webapp/WEB-INF» и положить туда «spring4gwt-0.0.1.jar» для перехвата RPC вызовов между клиентом и сервером и трансформировать их в Spring обслуживание сервисов.

Давайте добавим в web.xml файл следующее:

      
<listener>
 <listener-class>
  org.springframework.web.context.ContextLoaderListener
 </listener-class>
</listener>
<servlet>
 <servlet-name>springGwtRemoteServiceServlet</servlet-name>
 <servlet-class>org.spring4gwt.server.SpringGwtRemoteServiceServlet
 </servlet-class>
</servlet>
<servlet-mapping>
 <servlet-name>springGwtRemoteServiceServlet</servlet-name>
 <url-pattern>/com.javacodegeeks.gwtspring.Application/springGwtServices/*</url-pattern>
</servlet-mapping>
		

Заметьте, что:

  1. <url-pattern> - элемент-потомок в servlet-mapping для springGwtRemoteServiceServlet, следует заменить на любе имя модуля GWT, например, {module_name}/SpringGwtServices/*, имя модуля по умолчанию {main_package}.Application
  2. Вы можете изменить имя spring4gwt сервлета (springGwtRemoteServiceServlet) на какое хотите

Следующим шагом будет создание «persistence.xml» файла для описания соединения с базой данных, используя JPA. Этот файл должен находиться внутри META-INF директории, которая доступна web приложению во время выполнения (в classpath). Для выполнения этих требований мы должны создать META-INF директорию в /src/main/resources. Не забываем создать в этой директории файл «persistence.xml» и заполнить его, как указано ниже:

      
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
 version="2.0">

 <persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>

  <properties>
   <property name="hibernate.hbm2ddl.auto" value="update" />
   <property name="hibernate.show_sql" value="false" />
   <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
   <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
   <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:javacodegeeks" />
   <property name="hibernate.connection.username" value="sa" />
   <property name="hibernate.connection.password" value="" />

   <property name="hibernate.c3p0.min_size" value="5" />
   <property name="hibernate.c3p0.max_size" value="20" />
   <property name="hibernate.c3p0.timeout" value="300" />
   <property name="hibernate.c3p0.max_statements" value="50" />
   <property name="hibernate.c3p0.idle_test_period" value="3000" />

  </properties>

 </persistence-unit>

</persistence>
		

Теперь давайте создадим applicationContext.xml файл, который будет управлять Spring контейнером. Создадим файл в /src/main/webapp/WEB-INF директории. Пример applicationContext.xml представлен ниже:

      
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:task="http://www.springframework.org/schema/task"
 xsi:schemaLocation="
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

 <context:component-scan base-package="com.javacodegeeks.gwtspring" />

 <tx:annotation-driven />

 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
  <property name="persistenceUnitName" value="MyPersistenceUnit" />
 </bean>

 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>

</beans>
		

Заметьте, что:

  1. Установите base-package атрибут для context:component-scan элемента на тот, где у вас располагается основной пакет вашего проекта, чтобы Spring мог просканировать компоненты (сервисы, DAO итп).
  2. Установите значение атрибута entityManagerFactory бина persistentUnitName свойства в имя вашего юнита, который мы определили в «persistance.xml» файле.

В последней части этого урока мы рассмотрим Data Transfer Object (DTO) для транспортировки данных между клиентом и сервером, Data Access Object (DAO), который используется для доступа к базе данных и Spring сервис, который предоставляет функциональность для GWT Web клиента.

DTO — это объект, который может быть использован как клиентом так и сервером, для этого вы должны создать «shared.dto» подпакет под вашим основным пакетом (в нашем случае это com.javacodegeeks.gwtspring) и поместить DTO здесь. Мы создадим EmployeeDTO, содержащий информацию для «работника»:

      
package com.javacodegeeks.gwtspring.shared.dto;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeDTO implements java.io.Serializable {

 private static final long serialVersionUID = 7440297955003302414L;

 @Id
 @Column(name="employee_id")
 private long employeeId;

 @Column(name="employee_name", nullable = false, length=30)
 private String employeeName;

 @Column(name="employee_surname", nullable = false, length=30)
 private String employeeSurname;

 @Column(name="job", length=50)
 private String job;
  
 public EmployeeDTO() {
 }

 public EmployeeDTO(int employeeId) {
  this.employeeId = employeeId;  
 }

 public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,
   String job) {
  this.employeeId = employeeId;
  this.employeeName = employeeName;
  this.employeeSurname = employeeSurname;
  this.job = job;
 }

 public long getEmployeeId() {
  return employeeId;
 }

 public void setEmployeeId(long employeeId) {
  this.employeeId = employeeId;
 }

 public String getEmployeeName() {
  return employeeName;
 }

 public void setEmployeeName(String employeeName) {
  this.employeeName = employeeName;
 }

 public String getEmployeeSurname() {
  return employeeSurname;
 }

 public void setEmployeeSurname(String employeeSurname) {
  this.employeeSurname = employeeSurname;
 }

 public String getJob() {
  return job;
 }

 public void setJob(String job) {
  this.job = job;
 }

}
		

DTO объект должен быть доступен для GWT клиента, для этого мы должны указать GWT компилятору, что нужно его обрабатывать. Давайте найдем GWT файл-модуль, имеющего имя «Application.gwt.xml», и находящегося в главном пакете (в нашем случае это com.javacodegeeks.gwtspring) и добавим в него следующие указания:

      
<!-- Specify the paths for translatable code -->
<source path='client'/>
<source path='shared'/>
		

DAO объект используется для доступа к базе данных и выполняет CRUD операции. Это серверный компонент и должен находиться в подпакете «server» в вашем проекте. Создайте «server.dao» подпакет в вашем главном пакете (в нашем случае это com.javacodegeeks.gwtspring) и разместите здесь DAO. Пример DAO представлен ниже:

      
package com.javacodegeeks.gwtspring.server.dao;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;


@Repository("employeeDAO")
public class EmployeeDAO extends JpaDAO<Long, EmployeeDTO> {

 @Autowired
 EntityManagerFactory entityManagerFactory;

 @PostConstruct
 public void init() {
  super.setEntityManagerFactory(entityManagerFactory);
 }

}
		

Как вы можете видеть EmployeeDAO расширяет базовый DAO класс (JpaDAO). EmployeeDAO класс может содержать определенные запросы относительно EmployeeDTO объекта, но все CRUD операции могут быть обработаны в базовом DAO классе (JpaDAO). Разместите JpaDAO класс на тот же уровень, что и EmployeeDAO класс, под «dao» подпакете. Ниже представлен JpaDAO класс:

      
package com.javacodegeeks.gwtspring.server.dao; 

import java.lang.reflect.ParameterizedType; 
import java.util.List; 

import javax.persistence.EntityManager; 
import javax.persistence.PersistenceException; 
import javax.persistence.Query; 

import org.springframework.orm.jpa.JpaCallback; 
import org.springframework.orm.jpa.support.JpaDaoSupport; 

public abstract class JpaDAO<K, E> extends JpaDaoSupport { 
 protected Class<E> entityClass; 

 @SuppressWarnings("unchecked") 
 public JpaDAO() { 
  ParameterizedType genericSuperclass = (ParameterizedType) getClass() 
    .getGenericSuperclass(); 
  this.entityClass = (Class<E>) genericSuperclass 
    .getActualTypeArguments()[1]; 
 } 

 public void persist(E entity) { 
  getJpaTemplate().persist(entity); 
 } 

 public void remove(E entity) { 
  getJpaTemplate().remove(entity); 
 } 
  
 public E merge(E entity) { 
  return getJpaTemplate().merge(entity); 
 } 
  
 public void refresh(E entity) { 
  getJpaTemplate().refresh(entity); 
 } 

 public E findById(K id) { 
  return getJpaTemplate().find(entityClass, id); 
 } 
  
 public E flush(E entity) { 
  getJpaTemplate().flush(); 
  return entity; 
 } 
  
 @SuppressWarnings("unchecked") 
 public List<E> findAll() { 
  Object res = getJpaTemplate().execute(new JpaCallback() { 

   public Object doInJpa(EntityManager em) throws PersistenceException { 
    Query q = em.createQuery("SELECT h FROM " + 
      entityClass.getName() + " h"); 
    return q.getResultList(); 
   } 
    
  }); 
   
  return (List<E>) res; 
 } 

 @SuppressWarnings("unchecked") 
 public Integer removeAll() { 
  return (Integer) getJpaTemplate().execute(new JpaCallback() { 

   public Object doInJpa(EntityManager em) throws PersistenceException { 
    Query q = em.createQuery("DELETE FROM " + 
      entityClass.getName() + " h"); 
    return q.executeUpdate(); 
   } 
    
  }); 
 } 
  
}
		

В конце мы создадим интерфейс сервиса и классы реализации для доступа GWT клиента. Интерфейс сервиса должен быть доступен как для клиента, так и сервера, поэтому все это должно лежать в подпакете «shared». Создайте подпакет «services» и поместите туда интерфейс сервиса. Вот пример интерфейса:

      
package com.javacodegeeks.gwtspring.shared.services;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;

@RemoteServiceRelativePath("springGwtServices/employeeService")
public interface EmployeeService extends RemoteService {

 public EmployeeDTO findEmployee(long employeeId);
 public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void deleteEmployee(long employeeId) throws Exception;

}
		

Заметье, что:

  1. GWT клиент должен иметь возможность делать асинхронные Remote Procedure Calls (RPC) вызовы к сервису серверной стороны. Таким образом интерфейс сервиса должен расширять RemoteService интерфейс. Асинхронный аналог указанного интерфейса автоматически создается Maven перед компиляцией проекта
  2. Мы сделали аннотации в интерфейсе, с тем чтобы определить URL под которым сервис будет досупен. Мы хотим чтобы «spring4gwt» перехватывал RPC вызовы и выполнял вызов Spring сервиса. Чтобы сделать это мы определили относительный путь, который будет обрабатываться «springGwtRemoteServiceServlet». Его мы объявляли в «web.xml»
  3. Имя сервиса объявлено как аннотация «RemoteServiceRelativePath», где «employeeService», должен совпадать с именем Spring бина. Мы будем определять имя Spring бина в реализации класса сервиса (смотрите ниже)

Реализация класса сервиса является серверным компонентом, поэтому мы должны расположить его в подпакете «server» в нашем проекте. Создайте подпакет «services» и разместите его там. Вот пример реализации класса сервиса:

      
package com.javacodegeeks.gwtspring.server.services; 

import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Propagation; 
import org.springframework.transaction.annotation.Transactional; 

import com.javacodegeeks.gwtspring.server.dao.EmployeeDAO; 
import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO; 
import com.javacodegeeks.gwtspring.shared.services.EmployeeService; 

@Service("employeeService") 
public class EmployeeServiceImpl implements EmployeeService { 
  
 @Autowired 
 private EmployeeDAO employeeDAO; 

 @PostConstruct 
 public void init() throws Exception { 
 } 
  
 @PreDestroy 
 public void destroy() { 
 } 

 public EmployeeDTO findEmployee(long employeeId) { 
   
  return employeeDAO.findById(employeeId); 
   
 } 
  
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) 
 public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception { 
    
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId); 
   
  if(employeeDTO == null) { 
   employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription); 
   employeeDAO.persist(employeeDTO); 
  } 
   
 } 
  
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) 
 public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception { 
   
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId); 
   
  if(employeeDTO != null) { 
   employeeDTO.setEmployeeName(name); 
   employeeDTO.setEmployeeSurname(surname); 
   employeeDTO.setJob(jobDescription); 
  } 

 } 
  
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) 
 public void deleteEmployee(long employeeId) throws Exception { 
   
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId); 
   
  if(employeeDTO != null) 
   employeeDAO.remove(employeeDTO); 

 } 
  
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) 
 public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception { 
   
  EmployeeDTO employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription); 
   
  employeeDAO.merge(employeeDTO); 
   
 } 

}
		

Заметье, что:

  1. Мы используем @Service(«employeeService») аннотацию чтобы объявить, что этот класс представляет Spring сервис с именем «employeeService». Spring контейнер будет обрабатывать все сервисы при старте.
  2. Мы используем @Autowire аннотацию для инъекции DAO класса в «employeeService». Spring контейнер реализцет DAO класс и делает инъекцию его экземпляра в соответствующее поле «employeeService» - в поле «employeeDAO».
  3. Мы использовали Java аннотацию @PostConstruct и @PreDestroy для объявления методов которые будут выполняться Spring контейнером после иницилизации и до уничтожения сервиса.
  4. Мы используем аннотацию @Transactional для всех методов, которые должны выполнять операцию обновления в базе данных (INSERT, UPDATE, DELETE)
  5. Мы НЕ используем аннотацию @Transactional на методах, которые выполняют операции получения (FIND — поиск) в базе данных (исключая объекты, которые содержат lazily — смотрите ниже), и/или выполняют операции не связанные с базой данных. Это потому, что каждый раз при вызове метода аннотированного как transactional, Spring контейнер включает вызов JPA entity manager, с тем чтобы определить транзакционное поведение, которое будет применяться, снижая при этом заметное снижение производительности.
  6. Для методов, выполняющих операцию получения (FIND) для объектов, которые содержат lazily мы используем @Transactional аннотации, назначая propagation type как «NESTED» для того, чтобы Spring поддерживал Hibernate сессию открытой для всего сеанса вызова метода
  7. Транзакционное поведение применяться только на вызовы клиента к сервису. Транзакционное поведение не применяется к внутренним вызовам. Например, если клиент вызывает операцию, которая не аннотирована как transactional

Мы почти закончили. Давайте приступим к разработке GWT пользовательского интерфейса для доступа к нашему Spring сервису.

Давайте перейдем к точке входа нашего GWT приложения. Файл должен называться «Application.java» и расположен в подпакете «client» в главном пакете. Вот этот класс:

      
package com.javacodegeeks.gwtspring.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;
import com.javacodegeeks.gwtspring.shared.services.EmployeeServiceAsync;


/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Application
    implements EntryPoint
{

 /**
  * The message displayed to the user when the server cannot be reached or
  * returns an error.
  */
 private static final String SERVER_ERROR = "An error occurred while "
   + "attempting to contact the server. Please check your network "
   + "connection and try again. The error is : ";

 /**
  * Create a remote service proxy to talk to the server-side Employee service.
  */
 private final EmployeeServiceAsync employeeService = EmployeeServiceAsync.Util.getInstance();

 /**
  * This is the entry point method.
  */
 public void onModuleLoad() {
  final Button saveOrUpdateButton = new Button("SaveOrUpdate");
  final Button retrieveButton = new Button("Retrieve");
  final TextBox employeeInfoField = new TextBox();
  employeeInfoField.setText("Employee Info");
  final TextBox employeeIdField = new TextBox();
  final Label errorLabel = new Label();

  // We can add style names to widgets
  saveOrUpdateButton.addStyleName("sendButton");
  retrieveButton.addStyleName("sendButton");

  // Add the nameField and sendButton to the RootPanel
  // Use RootPanel.get() to get the entire body element
  RootPanel.get("employeeInfoFieldContainer").add(employeeInfoField);
  RootPanel.get("updateEmployeeButtonContainer").add(saveOrUpdateButton);
  RootPanel.get("employeeIdFieldContainer").add(employeeIdField);
  RootPanel.get("retrieveEmployeeButtonContainer").add(retrieveButton);
  RootPanel.get("errorLabelContainer").add(errorLabel);

  // Focus the cursor on the name field when the app loads
  employeeInfoField.setFocus(true);
  employeeInfoField.selectAll();

  // Create the popup dialog box
  final DialogBox dialogBox = new DialogBox();
  dialogBox.setText("Remote Procedure Call");
  dialogBox.setAnimationEnabled(true);
  final Button closeButton = new Button("Close");
  // We can set the id of a widget by accessing its Element
  closeButton.getElement().setId("closeButton");
  final Label textToServerLabel = new Label();
  final HTML serverResponseLabel = new HTML();
  VerticalPanel dialogVPanel = new VerticalPanel();
  dialogVPanel.addStyleName("dialogVPanel");
  dialogVPanel.add(new HTML("<b>Sending request to the server:</b>"));
  dialogVPanel.add(textToServerLabel);
  dialogVPanel.add(new HTML("
<b>Server replies:</b>"));
  dialogVPanel.add(serverResponseLabel);
  dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
  dialogVPanel.add(closeButton);
  dialogBox.setWidget(dialogVPanel);

  // Add a handler to close the DialogBox
  closeButton.addClickHandler(new ClickHandler() {
   public void onClick(ClickEvent event) {
    dialogBox.hide();
    saveOrUpdateButton.setEnabled(true);
    saveOrUpdateButton.setFocus(true);
    retrieveButton.setEnabled(true);
   }
  });

  // Create a handler for the saveOrUpdateButton and employeeInfoField
  class SaveOrUpdateEmployeeHandler implements ClickHandler, KeyUpHandler {
   /**
    * Fired when the user clicks on the saveOrUpdateButton.
    */
   public void onClick(ClickEvent event) {
    sendEmployeeInfoToServer();
   }

   /**
    * Fired when the user types in the employeeInfoField.
    */
   public void onKeyUp(KeyUpEvent event) {
    if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
     sendEmployeeInfoToServer();
    }
   }

   /**
    * Send the employee info from the employeeInfoField to the server and wait for a response.
    */
   private void sendEmployeeInfoToServer() {
    // First, we validate the input.
    errorLabel.setText("");
    String textToServer = employeeInfoField.getText();

    // Then, we send the input to the server.
    saveOrUpdateButton.setEnabled(false);
    textToServerLabel.setText(textToServer);
    serverResponseLabel.setText("");

    String[] employeeInfo = textToServer.split(" ");
    
    long employeeId = Long.parseLong(employeeInfo[0]);
    String employeeName = employeeInfo[1];
    String employeeSurname = employeeInfo[2];
    String employeeJobTitle = employeeInfo[3];
    
    employeeService.saveOrUpdateEmployee(employeeId, employeeName, employeeSurname, employeeJobTitle, 
      new AsyncCallback<Void>() {
       public void onFailure(Throwable caught) {
        // Show the RPC error message to the user
        dialogBox
          .setText("Remote Procedure Call - Failure");
        serverResponseLabel
          .addStyleName("serverResponseLabelError");
        serverResponseLabel.setHTML(SERVER_ERROR + caught.toString());
        dialogBox.center();
        closeButton.setFocus(true);
       }

       public void onSuccess(Void noAnswer) {
        dialogBox.setText("Remote Procedure Call");
        serverResponseLabel
          .removeStyleName("serverResponseLabelError");
        serverResponseLabel.setHTML("OK");
        dialogBox.center();
        closeButton.setFocus(true);
       }
      });
   }
  }
  
  // Create a handler for the retrieveButton and employeeIdField
  class RetrieveEmployeeHandler implements ClickHandler, KeyUpHandler {
   /**
    * Fired when the user clicks on the retrieveButton.
    */
   public void onClick(ClickEvent event) {
    sendEmployeeIdToServer();
   }

   /**
    * Fired when the user types in the employeeIdField.
    */
   public void onKeyUp(KeyUpEvent event) {
    if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
     sendEmployeeIdToServer();
    }
   }

   /**
    * Send the id from the employeeIdField to the server and wait for a response.
    */
   private void sendEmployeeIdToServer() {
    // First, we validate the input.
    errorLabel.setText("");
    String textToServer = employeeIdField.getText();

    // Then, we send the input to the server.
    retrieveButton.setEnabled(false);
    textToServerLabel.setText(textToServer);
    serverResponseLabel.setText("");

    employeeService.findEmployee(Long.parseLong(textToServer),  
      new AsyncCallback<EmployeeDTO>() {
       public void onFailure(Throwable caught) {
        // Show the RPC error message to the user
        dialogBox
          .setText("Remote Procedure Call - Failure");
        serverResponseLabel
          .addStyleName("serverResponseLabelError");
        serverResponseLabel.setHTML(SERVER_ERROR + caught.toString());
        dialogBox.center();
        closeButton.setFocus(true);
       }

       public void onSuccess(EmployeeDTO employeeDTO) {
        dialogBox.setText("Remote Procedure Call");
        serverResponseLabel
          .removeStyleName("serverResponseLabelError");
        if(employeeDTO != null)
         serverResponseLabel.setHTML("Employee Information 
Id : " + employeeDTO.getEmployeeId() + "
Name : " + employeeDTO.getEmployeeName() + "
Surname : " + employeeDTO.getEmployeeSurname() + "
Job Title : " + employeeDTO.getJob());
        else
         serverResponseLabel.setHTML("No employee with the specified id found");
        dialogBox.center();
        closeButton.setFocus(true);
       }
      });
   }
  }

  // Add a handler to send the employee info to the server
  SaveOrUpdateEmployeeHandler saveOrUpdateEmployeehandler = new SaveOrUpdateEmployeeHandler();
  saveOrUpdateButton.addClickHandler(saveOrUpdateEmployeehandler);
  employeeInfoField.addKeyUpHandler(saveOrUpdateEmployeehandler);
  
  // Add a handler to send the employee id to the server
  RetrieveEmployeeHandler retrieveEmployeehandler = new RetrieveEmployeeHandler();
  retrieveButton.addClickHandler(retrieveEmployeehandler);
  employeeIdField.addKeyUpHandler(retrieveEmployeehandler);
 }

}
		

Как вы можете видеть, вызов Spring сервиса выполняется так же, как классические вызовы GWT сервисов, прозрачно для клиента.

В конце мы будем работать с «Application.html» файлом.

      
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- The HTML 4.01 Transitional DOCTYPE declaration-->
<!-- above set at the top of the file will set     -->
<!-- the browser's rendering engine into           -->
<!-- "Quirks Mode". Replacing this declaration     -->
<!-- with a "Standards Mode" doctype is supported, -->
<!-- but may lead to some differences in layout.   -->

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <!--                                           -->
    <!-- Any title is fine                         -->
    <!--                                           -->
    <title><a href="http://www.springsource.org/">Spring</a> <a href="http://code.google.com/webtoolkit/">GWT</a> Web Application Starter Project</title>
    
    <!--                                           -->
    <!-- This script loads your compiled module.   -->
    <!-- If you add any <a href="http://code.google.com/webtoolkit/">GWT</a> meta tags, they must   -->
    <!-- be added before this line.                -->
    <!--                                           -->
    <script type="text/javascript" language="javascript" src="com.javacodegeeks.gwtspring.Application.nocache.js"></script>
  </head>

  <!--                                           -->
  <!-- The body can have arbitrary html, or      -->
  <!-- you can leave the body empty if you want  -->
  <!-- to create a completely dynamic UI.        -->
  <!--                                           -->
  <body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>

    <h1 align="center"><a href="http://www.springsource.org/">Spring</a> <a href="http://code.google.com/webtoolkit/">GWT</a> Web Application Starter Project</h1>

    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter employee info (id name surname job):</td>        
      </tr>
      <tr>
        <td id="employeeInfoFieldContainer"></td>
        <td id="updateEmployeeButtonContainer"></td>
      </tr>
      <tr>
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter employee id:</td>        
      </tr>
      <tr>
        <td id="employeeIdFieldContainer"></td>
        <td id="retrieveEmployeeButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
      </tr>
    </table>

  </body>
</html>
		

Чтобы построить приложение нажмите Run As — Maven package. Для развертывания приложения просто скопируйте «war» файл из «target» директории в Apache — Tomcat «webapps» директорию. Заупскайте приложение, набрав в браузере строку (application_name замените на имя приложения):

      
http://localhost:8080/{application_name}/