Статьи

ExtJS 4.2, Spring MVC 3.2.4 и пример Maven с использованием среды IDE NetBeans 7.3

Из этого туториала Вы узнаете, как реализовать CRUD (создание, чтение, обновление, удаление) DataGrid с использованием ExtJS 4.2, Spring MVC 3.2.4 и Maven с IDE NetBeans 7.3.

Что мы обычно хотим делать с данными?

  • Создать (вставить)
  • Читать / Получить (Выбрать)
  • Обновление (Update)
  • Удалить / Уничтожить (Удалить)

В этом примере я собираюсь использовать JSON для обмена форматами данных между браузером и сервером.

1. Создайте новый проект в IDE NetBeans

  1. Перейдите в  Файл> Новый  проект или нажмите  Ctrl + Shift + N
  2. Выберите  maven> Project из архетипа  и нажмите кнопку Далее
  3. В поле поиска напишите  extjs,  выберите  extjs-springmvc-webapp  и нажмите «Далее».
  4. Введите  Имя проекта, Местоположение и Пакет  и нажмите  Готово

Наконец, вы получите каталог проекта, подобный этому:

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":"ed@sencha.com"}

Как видите, он отправляет объект без квадратных скобок «[..]»  , представляющий массив. то есть:

[{"id":"1","name":"Eds","email":"ed@sencha.com"}]

Это означает, что он будет работать только тогда, когда мы отредактируем две или более строки в сетке, поскольку он отправляет массив объектов, то есть:

[{"id":"1","name":"Edss","email":"ed@sencha.com"},{"id":"2","name":"Tommys","email":"tommy@sencha.com"}]

Есть два возможных решения:

  • Модифицируйте ExtJS Store, чтобы он всегда отправлял массив вместо одного объекта. Поскольку я не знаю, как это сделать, и это может сломать другие части фреймворка, я отказался от этой опции
  • Создайте CustomMappingJacksonHttpMessageConverter для Spring.  Для меня это лучший вариант, так как не влияет на внутреннюю среду Spring Framework.

Чтобы сделать второй вариант, нам нужно сделать 3 вещи:
  1. Создайте Java-класс CustomMappingJacksonHttpMessageConverter.
  2. Создайте экземпляр этого класса в файле конфигурации Spring Servlet (в этом случае я собираюсь использовать XML)
  3. Добавьте некоторые библиотеки Apache commons в наш pom.xml, так как они потребуются для нашего пользовательского класса конвертера.
1.  Создайте Java-класс CustomMappingJacksonHttpMessageConverter
/*
 * 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>

Наконец, вы можете снова запустить и протестировать свой проект.