Из этого туториала Вы узнаете, как реализовать CRUD (создание, чтение, обновление, удаление) DataGrid с использованием ExtJS 4.2, Spring MVC 3.2.4 и Maven с IDE NetBeans 7.3.
Что мы обычно хотим делать с данными?
- Создать (вставить)
- Читать / Получить (Выбрать)
- Обновление (Update)
- Удалить / Уничтожить (Удалить)
В этом примере я собираюсь использовать JSON для обмена форматами данных между браузером и сервером.
1. Создайте новый проект в IDE NetBeans
- Перейдите в Файл> Новый проект или нажмите Ctrl + Shift + N
- Выберите maven> Project из архетипа и нажмите кнопку Далее
- В поле поиска напишите extjs, выберите extjs-springmvc-webapp и нажмите «Далее».
- Введите Имя проекта, Местоположение и Пакет и нажмите Готово
Наконец, вы получите каталог проекта, подобный этому:
2. Изменить проект
В этот момент вы можете запустить свой проект, и он покажет вам сетку, в которой вы можете редактировать 2 пользователей. Однако у этого архетипа есть небольшая проблема. Если вы видите файл UserService.java, который находится внутри пакета com.extjs.spring.services , он использует метод parseUserJson, который используется методом updateUser .
...
/**
* Update a user in the system
*/
@RequestMapping(value = "/user/update", method = RequestMethod.POST)
public Map updateUser( HttpServletRequest request, HttpServletResponse response, Principal principal
, @RequestBody String json ) throws Exception
{
//TODO replace this with your real code here.
Collection<User> parsedUsers = parseUserJson(json);
// Update all of the users (client is sending us array of users in json)
if ( parsedUsers != null )
{
for (User parsedUser : parsedUsers)
{
User localUser = users.get(parsedUser.getId());
if ( localUser == null )
{
throw new RuntimeException("Invalid User");
}
// save changes to local user
localUser.setName(parsedUser.getName());
localUser.setEmail(parsedUser.getEmail());
}
}
Map results = new HashMap();
results.put("succes", true);
return results;
}
/**
* Parse an json packet of user(s)
*/
private Collection<User> parseUserJson( String json ) throws Exception
{
try
{
if ( json.startsWith("[") && json.endsWith("]") )
{
// array of users
ObjectMapper mapper = new ObjectMapper();
TypeReference ref = new TypeReference<Collection<User>>(){};
Collection<User> user = (Collection<User>) mapper.readValue(json, ref);
return user;
}
else
{
// Single object
ObjectMapper mapper = new ObjectMapper();
Collection<User> users = new ArrayList<User>();
users.add( (User) mapper.readValue(json, User.class) );
return users;
}
}
catch (Exception ex)
{
throw new RuntimeException("Invalid USER Json");
}
}
...
Проблема этого подхода заключается в том, что если мы хотим создать больше объектов Models или POJO, нам нужно создать метод синтаксического анализатора для каждого контроллера.
Одним из вариантов решения этой проблемы было бы создание базового контроллера и расширение всех новых контроллеров из него. Однако нам по-прежнему необходимо вызывать метод синтаксического анализатора каждый раз, когда мы читаем объект, отправленный как параметр @RequestBody . Другим вариантом может быть создание класса util или helper, который реализует этот метод, и вызов его из контроллеров, но нам все равно нужно вызывать этот метод каждый раз, когда нам нужно десериализовать @RequestBody.
По умолчанию, в пружинных контроллерах, если мы проходим класс или List <Class> в @RequestBody параметра, он анализирует строку и преобразует объект непосредственно. Например:
@RequestMapping(value = "/update", method = RequestMethod.POST) public void updateUser(@RequestBody List<User> inUsers) throws Exception { for (User user : inUsers) { User localUser = users.get(user.getId()); if (localUser == null) { throw new RuntimeException("Invalid User"); } // save changes to local user localUser.setName(user.getName()); localUser.setEmail(user.getEmail()); } }
Это может быть лучшим решением, но у нас есть другая проблема. Когда мы редактируем только одну строку в нашей сетке, она отправляет следующий json на сервер.
{"id":"1","name":"Eds","email":"[email protected]"}
Как видите, он отправляет объект без квадратных скобок «[..]» , представляющий массив. то есть:
[{"id":"1","name":"Eds","email":"[email protected]"}]
Это означает, что он будет работать только тогда, когда мы отредактируем две или более строки в сетке, поскольку он отправляет массив объектов, то есть:
[{"id":"1","name":"Edss","email":"[email protected]"},{"id":"2","name":"Tommys","email":"[email protected]"}]
Есть два возможных решения:
- Модифицируйте ExtJS Store, чтобы он всегда отправлял массив вместо одного объекта. Поскольку я не знаю, как это сделать, и это может сломать другие части фреймворка, я отказался от этой опции
- Создайте CustomMappingJacksonHttpMessageConverter для Spring. Для меня это лучший вариант, так как не влияет на внутреннюю среду Spring Framework.
Чтобы сделать второй вариант, нам нужно сделать 3 вещи:
- Создайте Java-класс CustomMappingJacksonHttpMessageConverter.
- Создайте экземпляр этого класса в файле конфигурации Spring Servlet (в этом случае я собираюсь использовать XML)
- Добавьте некоторые библиотеки Apache commons в наш pom.xml, так как они потребуются для нашего пользовательского класса конвертера.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.springframework.http.converter.json; import com.fasterxml.jackson.databind.JavaType; import java.io.IOException; import java.lang.reflect.Type; import org.apache.commons.io.IOUtils; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; /** * * @author luis */ public class CustomMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter { @Override public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(type, contextClass); return readJavaType(javaType, inputMessage); } private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { try { String inputJson = IOUtils.toString(inputMessage.getBody()); if(javaType.isCollectionLikeType() && !(inputJson.startsWith("[") && inputJson.endsWith("]")) ) { inputJson = "[" + inputJson + "]"; } return getObjectMapper().readValue(inputJson, javaType); } catch (IOException ex) { throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); } } }
2. Создайте экземпляр этого класса в файле конфигурации Spring Servlet (Веб-страницы> WEB-INF> services-servlet.xml).
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!--<mvc:annotation-driven/>--> <bean class='org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'> <property name='messageConverters'> <list> <bean class='org.springframework.http.converter.json.CustomMappingJacksonHttpMessageConverter'/> </list> </property> </bean> <bean name='handlerMapping' class='org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'> <property name='useTrailingSlashMatch' value='false'></property> </bean> <!-- ****** REST API Service ****** --> <bean id="userService" class="com.extjs.spring.services.UserService" /> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="json" value="application/json" /> </map> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"> </bean> </list> </property> </bean> </beans>
3. Добавьте некоторые библиотеки Apache commons в наш pom.xml, так как они потребуются для нашего пользовательского класса конвертера.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lvargas</groupId> <artifactId>extjs-spring</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>extjs-spring-webapp</name> <url>http://maven.apache.org</url> <properties> <java-version>1.6</java-version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <org.springframework.version>3.2.4.RELEASE</org.springframework.version> <org.springframework.security.version>3.2.4.RELEASE</org.springframework.security.version> <spring.integration.version>2.2.6.RELEASE</spring.integration.version> </properties> <dependencies> <!-- link to tomcat to get reference to servlet api --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>servlet-api</artifactId> <version>6.0.33</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit-dep</artifactId> <version>4.9</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>apache-log4j-extras</artifactId> <version>1.1</version> </dependency> <!-- Core utilities used by other modules. Define this if you use Spring Utility APIs (org.springframework.core.*/org.springframework.util.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Bean Factory and JavaBeans utilities (depends on spring-core) Define this if you use Spring Bean APIs (org.springframework.beans.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Aspect Oriented Programming (AOP) Framework (depends on spring-core, spring-beans) Define this if you use Spring AOP APIs (org.springframework.aop.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans) This is the central artifact for Spring's Dependency Injection Container and is generally always defined --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Various Application Context utilities, including EhCache, JavaMail, Quartz, and Freemarker integration Define this if you need any of these integrations --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Transaction Management Abstraction (depends on spring-core, spring-beans, spring-aop, spring-context) Define this if you use Spring Transactions or DAO Exception Hierarchy (org.springframework.transaction.*/org.springframework.dao.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, spring-tx) Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis. (depends on spring-core, spring-beans, spring-context, spring-tx) Define this if you need ORM (org.springframework.orm.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Object-to-XML Mapping (OXM) abstraction and integration with JAXB, JiBX, Castor, XStream, and XML Beans. (depends on spring-core, spring-beans, spring-context) Define this if you need OXM (org.springframework.oxm.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Web application development utilities applicable to both Servlet and Portlet Environments (depends on spring-core, spring-beans, spring-context) Define this if you use Spring MVC, or wish to use Struts, JSF, or another web framework with Spring (org.springframework.web.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Spring MVC for Servlet Environments (depends on spring-core, spring-beans, spring-context, spring-web) Define this if you use Spring MVC with a Servlet Container such as Apache Tomcat (org.springframework.web.servlet.*) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Support for testing Spring applications with tools such as JUnit and TestNG This artifact is generally always defined with a 'test' scope for the integration testing framework and unit testing stubs --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> </dependencies> <build> <finalName>extjs-spring-1.0-SNAPSHOT</finalName> </build> <description>A maven Archetype to create new EXTJS 4 project powered by a spring MVC 3.2.4 service.</description> </project>
Наконец, вы можете снова запустить и протестировать свой проект.