Нашей предпочтительной средой разработки является Eclipse , поэтому в качестве предварительного условия у вас должна быть установлена Eclipse с поддержкой GWT . Установка плагина GWT для Eclipse выходит за рамки данного руководства и не обсуждается. Тем не менее вам понадобятся следующие компоненты:
- Затмение отсюда
- Плагин GWT для Eclipse отсюда
- Распределение рамок Spring отсюда
- Распределение фреймворка Hibernate персистентности отсюда
- Гиперзвуковая база данных отсюда
- Библиотека Apache Commons-Logging отсюда
- AOP Alliance (стандарт Java / J2EE AOP) отсюда
- Библиотека SLF4J отсюда
- Библиотека Apache log4j отсюда
- И последнее, но не менее важное: скачайте библиотеку GWT — Spring «glue» spring4gwt отсюда
Мы будем использовать Eclipse Galileo, GWT версии 2.0.3, Spring версии 3.0.1, Hibernate версии 3.5.2, Hypersonic версии 1.8.1.2, Apache commons-logging version 1.1.1, AOP Alliance (Java / J2EE AOP Standard) версия 1.0, SLF4J версии 1.5.8, Apache log4j версии 1.2.16 и spring4gwt версии 0.0.1 для этого руководства.
Хватит разговоров, давайте запачкаем руки!
- Создать новый проект GWT , перейти к файлу? Новый проект веб-приложения
- Мы назовем наш проект GWTSpring. Базовый пакет будет com.javacodegeeks.gwtspring, также используйте только Google Web Toolkit, поэтому снимите флажок «Использовать Google App Engine » в окне мастера.
Давайте вспомним несколько вещей о структуре проекта GWT
- Папка / src содержит все исходные файлы приложения
- {package_name} .client subpackage содержит все исходные файлы, доступные только для клиентской части приложения
- Подпакет {имя_пакета} .server содержит все исходные файлы, доступные только для серверной части приложения.
- Подпакет {имя_пакета} .shared содержит все исходные файлы, доступные как клиентской, так и серверной части приложения.
Чтобы правильно интегрировать Spring с GWT во время выполнения, мы должны предоставить все необходимые библиотеки для веб-приложения. Поэтому скопируйте файлы, перечисленные ниже в / war / WEB-INF / lib (скопируйте соответствующие файлы, если вы используете разные версии)
Из весны распространения
- /dist/org.springframework.expression-3.0.1.RELEASE-A.jar
- /dist/org.springframework.beans-3.0.1.RELEASE-A.jar
- /dist/org.springframework.oxm-3.0.1.RELEASE-A.jar
- /dist/org.springframework.jms-3.0.1.RELEASE-A.jar
- /dist/org.springframework.jdbc-3.0.1.RELEASE-A.jar
- /dist/org.springframework.core-3.0.1.RELEASE-A.jar
- /dist/org.springframework.context-3.0.1.RELEASE-A.jar
- /dist/org.springframework.asm-3.0.1.RELEASE-A.jar
- /dist/org.springframework.aspects-3.0.1.RELEASE-A.jar
- /dist/org.springframework.transaction-3.0.1.RELEASE-A.jar
- /dist/org.springframework.context.support-3.0.1.RELEASE-A.jar
- /dist/org.springframework.aop-3.0.1.RELEASE-A.jar
- /dist/org.springframework.orm-3.0.1.RELEASE-A.jar
- /dist/org.springframework.instrument-3.0.1.RELEASE-A.jar
- /dist/org.springframework.instrument.tomcat-3.0.1.RELEASE-A.jar
- /dist/org.springframework.test-3.0.1.RELEASE-A.jar
- /dist/org.springframework.web-3.0.1.RELEASE-A.jar
- /dist/org.springframework.web.portlet-3.0.1.RELEASE-A.jar
- /dist/org.springframework.web.servlet-3.0.1.RELEASE-A.jar
- /dist/org.springframework.web.struts-3.0.1.RELEASE-A.jar
Из дистрибутива Hibernate
- hibernate3.jar
- /lib/required/antlr-2.7.6.jar
- /lib/required/commons-collections-3.1.jar
- /lib/required/dom4j-1.6.1.jar
- /lib/required/javassist-3.9.0.GA.jar
- /lib/required/jta-1.1.jar
- /lib/required/slf4j-api-1.5.8.jar
- /lib/jpa/hibernate-jpa-2.0-api-1.0.0.Final.jar
- /lib/optional/c3p0/c3p0-0.9.1.jar
Из гиперзвукового распределения
- /lib/hsqldb.jar
Из дистрибутива регистрации Apache Commons
- Обще-каротаж 1.1.1.jar
Из дистрибутива AOP Alliance (стандарт Java / J2EE AOP)
- aopalliance.jar
Из дистрибутива SLF4J
- SLF4J-log4j12-1.5.8.jar
Из дистрибутива Apache log4j
- log4j-1.2.16.jar
Библиотека sping4gwt
- spring4gwt-0.0.1.jar
Теперь нам нужно позаботиться о зависимостях для нашего проекта Eclipse . Следующие jar-файлы должны быть включены в путь сборки Java проекта:
- спящий режим JPA-2,0-апи-1.0.0.Final.jar-
- org.springframework.beans-3.0.1.RELEASE-a.jar
- org.springframework.context-3.0.1.RELEASE-a.jar
- org.springframework.core-3.0.1.RELEASE-a.jar
- org.springframework.orm-3.0.1.RELEASE-a.jar
- org.springframework.transaction-3.0.1.RELEASE-a.jar
Следующим шагом является предоставление хуков для веб-приложения для загрузки контекста Spring при запуске и предоставления возможности Spring4gwt перехватывать вызовы RPC между клиентом и сервером и преобразовывать их в вызовы службы Spring .
Найдите файл web.xml в / war / WEB-INF и добавьте следующее:
Для загрузки контекста Spring при запуске,
1
2
3
|
< listener > < listener-class >org.springframework.web.context.ContextLoaderListener</ listener-class > </ listener > |
В разделе сервлетов есть
1
2
3
4
|
< servlet > < servlet-name >springGwtRemoteServiceServlet</ servlet-name > < servlet-class >org.spring4gwt.server.SpringGwtRemoteServiceServlet</ servlet-class > </ servlet > |
В разделе отображения сервлетов включите, чтобы spring4gwt перехватывал вызовы RPC.
1
2
3
4
|
< servlet-mapping > < servlet-name >springGwtRemoteServiceServlet</ servlet-name > < url-pattern >/gwtspring/springGwtServices/*</ url-pattern > </ servlet-mapping > |
Что следует отметить здесь:
- Дочерний элемент url-pattern элемента servlet-mapping для сервлета springGwtRemoteServiceServlet должен быть изменен на любое имя вашего модуля GWT , например, {module_name} / springGwtServices / *, имя модуля определено в файле {project_name} .gwt.xml. (здесь GWTSpring.gwt.xml), расположенный в корне базового пакета проекта в папке / src
- Вы можете изменить имя сервлета spring4gwt (здесь SpringGwtRemoteServiceServlet) на любое другое
Для продолжения мы должны создать файл persistence.xml, чтобы описать соединение с базой данных с использованием JPA . Файл pesistence.xml должен находиться в каталоге META-INF, который, в свою очередь, должен быть доступен веб-приложению во время выполнения (в пути к классам). Чтобы выполнить вышеупомянутые требования, мы должны создать папку META-INF в папке проекта / war / WEB-INF / classes. Для этого мы создаем новую исходную папку с именем, например, «resources», и создаем внутри нее папку META-INF. Наконец, создайте файл persistence.xml в папке / resources / META-INF. Пример persistence.xml представлен ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 > |
Что следует отметить здесь:
- Если вы намереваетесь развернуть веб-приложение на сервере приложений J2EE, который поддерживает транзакции JTA, например, JBoss, или использовать другие базы данных, например Oracle , MySQL и т. Д., Ознакомьтесь с нашим «Учебником по JBoss Spring JPA Hibernate» здесь , чтобы найти альтернативные конфигурации.
Теперь давайте создадим файл applicationContext.xml, который будет управлять контейнером Spring . Создайте файл в каталоге / war / WEB-INF. Пример applicationContext.xml представлен ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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" 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/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> < context:component-scan base-package = "com.javacodegeeks.gwtspring" /> < task:annotation-driven executor = "myExecutor" scheduler = "myScheduler" /> < task:executor id = "myExecutor" pool-size = "5" /> < task:scheduler id = "myScheduler" pool-size = "10" /> < tx:annotation-driven /> < bean class = "org.springframework.orm.jpa.LocalEntityManagerFactoryBean" id = "entityManagerFactory" > < property name = "persistenceUnitName" value = "MyPersistenceUnit" /> </ bean > < bean class = "org.springframework.orm.jpa.JpaTransactionManager" id = "transactionManager" > < property name = "entityManagerFactory" ref = "entityManagerFactory" /> </ bean > </ beans > |
Что следует отметить здесь:
- Измените атрибут base-package элемента context: component-scan на тот, который является базовым пакетом вашего проекта, чтобы проверять компоненты Spring (службы, DAO и т. Д.).
- Измените атрибут значения свойства компонента persistentUnitName объекта entityManagerFactory на имя вашего постоянного модуля, как указано в файле persistence.xml.
- Если вы намереваетесь развернуть веб-приложение на сервере приложений J2EE, который поддерживает транзакции JTA, например, JBoss, ознакомьтесь с нашим «Учебником по JBoss Sping JPA Hibernate» здесь, чтобы найти альтернативные конфигурации.
В последней части этого руководства мы собираемся представить объект передачи данных (DTO) для передачи данных между клиентом и сервером, объект доступа к данным (DAO), который используется для доступа к базе данных, и сервис Spring для предоставления функциональности. к веб-клиенту GWT .
DTO — это объект, который может использоваться как клиентом, так и сервером, поэтому вы должны создать подпакет «dto» в пакете «shared» и поместить туда DTO. Мы собираемся создать EmployeeDTO, содержащий информацию для сотрудника, как показано ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
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; } } |
Объект DAO будет использоваться для доступа к базе данных и выполнения операций CRUD (Create Retrieve Update Delete). Это серверный компонент, поэтому его следует поместить в подпакет «server» нашего проекта. Создайте подпакет «дао» и поместите туда DAO. Пример DAO представлен ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
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 . Интерфейс сервиса должен быть доступен как клиенту, так и серверу, поэтому его следует поместить в «общий» подпакет нашего проекта. Создайте подпакет «services» и поместите туда интерфейс сервиса. Ниже приведен пример класса интерфейса
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
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; } |
Что следует отметить здесь:
- Клиент GWT должен иметь возможность выполнять асинхронные удаленные вызовы процедур (RPC) для службы на стороне сервера. Таким образом, интерфейс службы должен расширять интерфейс RemoteService. Асинхронный аналог указанного интерфейса также должен быть предоставлен для включения асинхронной связи (см. Ниже)
- Мы аннотируем интерфейс, чтобы определить URL, по которому будет доступен сервис. Поскольку сервис является сервисом Spring, мы хотим, чтобы spring4gwt перехватывает вызовы RPC и выполняет вызов сервиса Spring . Для этого мы определяем относительный путь, который будет обрабатываться «springGwtRemoteServiceServlet», объявленным в нашем файле web.xml, как показано выше.
- Имя службы, объявленное в аннотации «RemoteServiceRelativePath», в данном случае «employeeService», должно соответствовать имени bean-компонента службы Spring . Мы определим имя бина службы Spring в классе реализации службы (см. Ниже)
Асинхронный счетчик части сервисного интерфейса следует
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.javacodegeeks.gwtspring.shared.services; import com.google.gwt.user.client.rpc.AsyncCallback; import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO; public interface EmployeeServiceAsync { void deleteEmployee( long employeeId, AsyncCallback<Void> callback); void findEmployee( long employeeId, AsyncCallback<EmployeeDTO> callback); void saveEmployee( long employeeId, String name, String surname, String jobDescription, AsyncCallback<Void> callback); void saveOrUpdateEmployee( long employeeId, String name, String surname, String jobDescription, AsyncCallback<Void> callback); void updateEmployee( long employeeId, String name, String surname, String jobDescription, AsyncCallback<Void> callback); } |
Класс реализации сервиса является компонентом на стороне сервера, поэтому мы должны поместить его в подпакет «server» нашего проекта. Создайте подпакет «services» и разместите его там. Пример класса реализации сервиса представлен ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
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); } } |
Что следует отметить здесь:
- Мы используем аннотацию стереотипа @Service («employeeService»), чтобы объявить, что этот класс представляет службу Spring с именем «exampleService». Контейнер Spring будет запускать все службы при запуске.
- Мы используем аннотацию @Autowire, чтобы внедрить экземпляр класса DAO в employeeService. Для правильного создания экземпляра службы контейнер Spring должен сначала разрешить все потенциальные ссылки между службами, поэтому он создает экземпляр класса DAO и внедряет экземпляр в соответствующее поле employeeService — поле employeeDAO. В случае, если вам интересно, внедрение зависимости выполняется в соответствии с типом (Class) и, если не выполняется в соответствии с именем, это означает, что если мы определили несколько служб одного и того же типа (Class), то внедренная будет одна с тем же именем как обозначенное поле.
- Мы используем аннотации Java @PostConstruct и @PreDestroy, чтобы объявить методы, которые будут вызываться контейнером Spring после инициализации (все инъекции зависимостей выполняются) и предварительного уничтожения службы.
- Мы используем аннотацию @Transactional для всех методов, которым необходимо выполнить операцию обновления базы данных (INSERT, UPDATE, DELETE).
- Мы НЕ используем аннотацию @Transactional для методов, которые выполняют операции извлечения (FIND) над базой данных (за исключением объектов, которые содержат лениво инициализированные ссылки — см. Ниже), и / или не выполняют никаких операций с базой данных. Это связано с тем, что каждый раз, когда вы вызываете метод, аннотированный как транзакционный, контейнер Spring включает в себя диспетчер сущностей вызова JPA и, как следствие, диспетчер транзакций платформы, чтобы определить поведение транзакции, которое будет применяться, вводя заметное снижение производительности, особенно для приложений с низкой задержкой / высокой пропускной способностью
- Для методов, которые выполняют операции извлечения (FIND) для объектов, которые содержат лениво инициализированные ссылки, вы должны использовать аннотацию @Transactional, обозначая тип распространения «NESTED», чтобы Spring мог поддерживать сеанс Hibernate открытым для всего вызова метода
- Транзакционное поведение применяется только при обращениях клиентов к сервису. Транзакционное поведение не применяется к внутриоперационным вызовам. Например, если клиент вызывает операцию, которая не аннотирована как транзакционная, и реализация последней вводит вызов другой операции того же сервиса, которая аннотирована транзакционной, тогда для объединенных операций транзакционное поведение не будет применено.
Мы почти закончили !, нам нужно разработать пользовательский интерфейс GWT для доступа к нашему сервису Spring . Несмотря на то, что разработка пользовательского интерфейса GWT выходит за рамки данного руководства, мы собираемся предоставить базовый пользовательский интерфейс, чтобы показать пару вызовов службы Spring .
Найдите точку входа вашего приложения GWT . Файл должен называться как {имя_проекта} .java, в нашем случае GWTSpring.java, и находиться в подпакете «client» или в нашем основном пакете. Измените класс точки входа, как показано ниже
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
package com.javacodegeeks.gwtspring.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; 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.EmployeeService; import com.javacodegeeks.gwtspring.shared.services.EmployeeServiceAsync; /** * Entry point classes define <code>onModuleLoad()</code>. */ public class GWTSpring 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 = GWT .create(EmployeeService. class ); /** * 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 , прозрачно для клиента.
Наконец найдите главную веб-страницу для вашего проекта. Файл должен называться как {имя_проекта} .html, в нашем случае GWTSpring.html, и находиться в папке / war нашего проекта. Измените главную веб-страницу, как показано ниже
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
<! doctype html> <!-- The DOCTYPE declaration above will set the --> <!-- browser's rendering engine into --> <!-- "Standards Mode". Replacing this declaration --> <!-- with a "Quirks Mode" doctype may lead to some --> <!-- differences in layout. --> < html > < head > < meta http-equiv = "content-type" content = "text/html; charset=UTF-8" > <!-- --> <!-- Consider inlining CSS to reduce the number of requested files --> <!-- --> < link type = "text/css" rel = "stylesheet" href = "GWTSpring.css" > <!-- --> <!-- Any title is fine --> <!-- --> < title >Spring GWT Web Application Starter Project</ title > <!-- --> <!-- This script loads your compiled module. --> <!-- If you add any GWT meta tags, they must --> <!-- be added before this line. --> <!-- --> < script type = "text/javascript" language = "javascript" src = "gwtspring/gwtspring.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 >Spring GWT 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 > |
Чтобы скомпилировать приложение, щелкните правой кнопкой мыши имя проекта и выберите «Запуск от имени»? Скомпилировать приложение GWT
Для развертывания веб-приложения просто скопируйте папку / war в папку «webapps» Apache — Tomcat . Вы можете изменить имя папки war на любое другое, желательно переименовать его после имени проекта, например, GWTSpring.
Чтобы запустить приложение, укажите ваш браузер по следующему адресу
HTTP: // локальный: 8080 / GWTSpring /
Если все прошло хорошо, вы должны увидеть свою главную веб-страницу. Необходимо отобразить два текстовых поля, каждое из которых должно сопровождаться кнопкой. В первом текстовом поле вы можете сохранить или обновить сотрудника в базе данных. Введите в качестве входных данных идентификатор, имя, фамилию и описание задания, разделенные пробелом. При нажатии на кнопку «SaveOrUpdate» предоставленная информация будет сохранена в базе данных. Для существующих записей сотрудников (с тем же идентификатором) будет выполнено обновление. Второе текстовое поле используется для получения существующих записей сотрудников. Введите идентификатор сотрудника и нажмите кнопку «Получить». Если сотрудник существует, вы должны увидеть его идентификатор, имя, фамилию и описание работы.
Мало, это был большой урок!
Вы можете скачать проект отсюда (необходимые сторонние библиотеки, как описано в начале, не включены)
Надеюсь, вам понравилось
Джастин
- GWT Spring и Hibernate входят в мир Data Grids
- Учебник по интеграции с Spring 3 HornetQ 2.1
- Spring 3 RESTful веб-сервисы
- Учебник по GWT 2 Spring 3 JPA 2 Hibernate 3.5 — демонстрация Eclipse и Maven 2
- JAX – WS с руководством по Spring и Maven