Skip to content

17. 在 Web 上公开数据库

17.1. Web/JSON 服务架构

我们将实现以下架构:

  • 在[1]中,[DAO、[JPA]、JDBC]层采用前几章(特别是第15段)中介绍的24种配置方案之一进行实现;
  • 远程客户端的 [DAO] 层 [3] 实现了与 [DAO] 层 [1] 相同的接口,这使我们能够沿用前几章中的测试层。这仿佛使 [2-3] 层对 [4] 层而言是透明的;

我们将依赖以下项目:

  • [sgbd-config-jdbc] 项目,该项目用于配置六种数据库管理系统(DBMS)中的一种的 JDBC 层;
  • [sgbd-config-jpa-*] 项目,该项目针对所研究的三个 JPA 实现(Hibernate、EclipseLink、OpenJpa)之一,配置所选数据库管理系统(DBMS)的 JPA 层;
  • 通用项目 [spring-jdbc-04],用于实现 [DAO] 层 [1];
  • 通用项目 [spring-jpa-generic],用于实现 [DAO] 层 [2];
  • 通用项目 [spring-webjson-server-jdbc-generic],该项目基于 [spring-jdbc-04] 项目实现了一个 Web 服务;
  • 通用项目 [spring-webjson-server-jpa-generic],该项目基于 [spring-jpa-generic] 项目实现了一个 Web 服务;
  • 通用客户端 [spring-webjson-client-generic],它将成为所有 24 个 Web 服务配置的唯一客户端;

17.2. 搭建开发环境

我们将使用以下组件:

  • MySQL 5.6.25 数据库;
  • Hibernate JPA 实现;

将以下项目导入 STS:

  
  • [spring-webjson-*] 项目位于 [<examples>\spring-database-generic\spring-webjson] 文件夹中;
  • 按下 [Alt-F5],然后重新生成上述所有项目;

要验证开发环境是否安装正确,请按以下步骤操作:

  • 使用 [spring-webjson-server-jpa-generic-hibernate] 运行时配置启动 Web 服务,该配置依赖于 JPA/Hibernate 实现;

然后:

  • 使用 [spring-webjson-client-generic] 运行时配置启动此 Web 服务的客户端,这是一个 JUnit 测试:

该测试应通过:

  • 在 [1] 中,停止 Web 服务,然后使用 [spring-webjson-server-jdbc-generic] 运行时配置启动 Web 服务,该配置依赖于 JDBC 实现:

然后使用 [spring-webjson-client-generic] 运行时配置启动该 Web 服务的客户端:

测试应通过:

 

17.3. Web 服务 / JSON / JDBC 实现

我们将首先关注以下架构:

其中 [DAO] 层 [1] 直接与 DBMS 的 JDBC 层进行通信。

17.3.1. Web 服务的 Eclipse 项目

Web 服务 / JSON / JDBC 的 Eclipse 项目如下:

  

这是一个 Maven 项目,其 [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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-webjson-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <name>spring-webjson-server-jdbc-generic</name>
    <description>démo spring mvc</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>
 
    <dependencies>
        <!-- web layer -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- layer [DAO] -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jdbc-generic-04</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
 
</project>
  • 第 11–15 行:父级 Maven 项目;
  • 第 24–28 行:对由 [spring-jdbc-generic-04] 项目实现的 [DAO / JDBC] 层的依赖;
  • 第 19–22 行:对 [spring-boot-starter-web] 构建成果的依赖。该构建成果包含创建 Web 服务/JSON 所需的所有依赖项,但也包含了一些不必要的库。因此,虽然需要更精确的配置,但此配置对于入门非常有用。

此配置引入的依赖项如下:

1
  • 在[1]中,我们可以看到Eclipse已检测到对[spring-jdbc-generic-04]项目归档的依赖;

上述依赖关系同时属于 [DAO] 层和 [web] 层。

17.3.2. [web]层的配置

[web] 层的配置由两个 Spring 配置文件完成:

  

17.3.2.1. [WebConfig] 类

[WebConfig] 类的主要作用是配置:

  • 将部署 Web 服务的 Tomcat 服务器;
  • 用于序列化和反序列化 [Product] 及 [Category] 对象的 JSON 过滤器:

package spring.webjson.server.config;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
 
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
 
    // -------------------------------- layer configuration [web]
    @Autowired
    private ApplicationContext context;
 
    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet=new DispatcherServlet((WebApplicationContext) context);
        return servlet;
    }
 
    @Bean
    public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new ServletRegistrationBean(dispatcherServlet, "/*");
    }
 
    @Bean
    public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory("", 8081);
    }
 
    // -------------------------------- configuration filters [json]
    ...
}
  • 第 25 行:该类是一个 Spring 配置类;
  • 第 26 行:[@EnableWebMvc] 注解表明 Web 层使用 Spring MVC 实现。这将触发一些隐式配置,我们无需手动设置;
  • 第 30-31 行:注入应用程序的 Spring 上下文;
  • 第 33–37 行:定义 [dispatcherServlet] Bean,在 Spring MVC 应用中,它充当 [FrontController],其作用是将客户端请求路由到能够处理这些请求的控制器;
  • 第 39–42 行:注册 Web 服务 Servlet 及其处理的 URL。此处写入 [/*],表示所有 URL;
  • 第 44–47 行:定义 [embeddedServletContainerFactory] Bean,用于指定要使用的 Web 服务器。在此示例中,将使用 Tomcat Web 服务器 [http://tomcat.apache.org/]。 您也可以使用 Jetty 服务器 [http://www.eclipse.org/jetty/]。这两者都是包含在 Maven 依赖项中的嵌入式服务器。当 [Spring Boot] 启动项目时,它会自动启动配置中指定的 Web 服务器,并将服务或 Web 应用程序部署到该服务器上;

JSON 过滤器的配置如下:


package spring.webjson.server.config;
 
import java.util.List;
...
 
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
 
    // -------------------------------- layer configuration [web]
...
    // -------------------------------- configuration filters [json]
    // mapping jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }
 
    // filters jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }
}
  • 第 8 行:[WebConfig] 类继承自 [WebMvcConfigurerAdapter] 类。 后者用于为 Web 应用程序配置默认值。若需自定义此配置,必须重写该类的特定方法。此处,我们将在第 [22–26] 行重写 [configureMessageConverters] 方法(注意 @Override 注解),该方法定义了一组“转换器”。 Web服务/JSON及其客户端之间交换文本行。转换器是一种能够将接收到的文本行转换为对象(反序列化),并将对象转换为文本行 的工具(序列化)。在此,文本行将是JSON字符串。因此,我们将称之为JSON序列化/反序列化;
  • 第 23 行:[configureMessageConverters] 方法将转换器列表作为参数;
  • 第 24–25 行:将第 [14–20] 行定义的 JSON 转换器 [MappingJackson2HttpMessageConverter] 添加到该列表中。这将实现客户端与服务器之间的 JSON 数据交换;
  • 第 [14–20] 行:定义由 [MappingJackson2HttpMessageConverter] 类实现的 JSON 转换器。该类(第 10 行)可在项目的 Maven 依赖项中找到;
  • 第 [17-18] 行:创建一个 JSON 映射器并将其赋值给 [MappingJackson2HttpMessageConverter];
  • 第 [29-32] 行:将第 17 行创建的 JSON 映射器定义为 Spring Bean。这将其置于 Spring 上下文中,使其可用于注入到其他 Bean 中或在 Web 应用程序代码中使用;
  • 第 34-41 行:为前面的 JSON 映射器定义一个 JSON 过滤器;
  • 第 35 行:注解 [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] 确保此处定义的 Bean 不是单例。每次从上下文中请求该 Bean 时,方法 [jsonMapperCategoryWithoutProducts] 都会被重新执行。此处需要这样做,因为我们定义了四个 JSON 过滤器,但任何时候都应仅有一个处于活动状态。 通过为该 Bean 赋予 [ConfigurableBeanFactory.SCOPE_PROTOTYPE] 作用域,我们确保该方法会被重新执行,并且之前的过滤器会被新的过滤器替换;
  • 要理解这些过滤器,请记住在 [DAO] 层中:
    • [Product] 实体已标注了 [jsonFilterProduct] 注解;
    • [Category] 实体已标注了 [jsonFilterCategory] 注解;

因此,我们必须定义名称与之对应的过滤器。

  • 第 [34-41] 行:定义一个名为 [jsonMapperShortCategory] 的过滤器,该过滤器提供不包含其产品的类别的 JSON 表示形式;
  • 第 [43-51] 行:定义名为 [jsonMapperLongCategory] 的过滤器,该过滤器提供包含其产品的类别的 JSON 表示形式;
  • 第 [53-60] 行:定义名为 [jsonMapperShortProduct] 的过滤器,该过滤器提供不包含所属类别的产品的 JSON 表示形式;
  • 第 [62-70] 行:定义名为 [jsonMapperLongProduct] 的过滤器,该过滤器提供包含所属类别的商品的 JSON 表示形式;

17.3.2.2. [AppConfig] 类

[AppConfig] 类用于配置整个应用程序,即 [web] 和 [DAO] 层:


package spring.webjson.server.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
 
@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {
 
}
  • 第 7 行:该类是一个 Spring 配置类;
  • 第 9 行:我们导入了来自 [DAO / JDBC] 层的 Bean 以及由 [WebConfig] 类定义的 Bean。因此,[DAO] 层中的所有 Bean 都将在 Web/JSON 应用程序中可用;
  • 第 8 行:指定了其他 Spring Bean 的所在包;

17.3.3. [ServerException]

  

正如前几章所述,当 [DAO] 层抛出未捕获的 [DaoException] 时,[web] 层也会抛出未捕获的 [ServerException]:


package spring.webjson.server.infrastructure;
 
import generic.jdbc.infrastructure.UncheckedException;
 
public class ServerException extends UncheckedException {
 
    private static final long serialVersionUID = 1L;
 
    // manufacturers
    public ServerException() {
        super();
    }
 
    public ServerException(int code, Throwable e, String simpleClassName) {
        super(code, e, simpleClassName);
    }
}
  • 第 5 行:[ServerException] 类继承了在配置 JDBC 层的项目中定义的 [UncheckedException] 类(第 3 行);

17.3.4. 控制器

  

这里我们将有两个控制器:

  • [CategoryController] 将处理与分类相关的请求;
  • [CategorieController] 将处理与产品相关的请求;

控制器公开的 URL 与 [DAO] 层中 [DaoCategorie] 和 [DaoProduit] 接口的方法一一对应:

因此,如上所示:

  • Web 方法 [deleteAllCategories] 将调用 [DaoCategorie] 类的 [deleteAllEntities] 方法;
  • Web 方法 [getShortCategoriesById] 将调用 [DaoCategorie] 类的 [getShortEntitiesById] 方法;

产品也是如此:

17.3.4.1. 由 [CategoryController] 暴露的 URL

URL
方法

@RequestMapping(value = "/saveCategories",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<List<CoreCategory>>
 saveCategories
(HttpServletRequest request)
====
该方法通过 POST 请求接收待保存的分类。
这些分类可通过 [HttpServletRequest
 请求]中获取。分类数据由
[DaoCategorie] 层的 [saveEntities] 方法进行持久化。仅
 持久化对象(分类/产品)
 返回给客户端。

@RequestMapping(value = "/deleteAllCategories",
 method = RequestMethod.GET)

public Response<Void> deleteAllCategories()
====
该 URL 没有参数。类别通过
 [DaoCategorie] 层中的 [deleteAllEntities] 方法进行删除。

@RequestMapping(value = "/deleteCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
该方法接收待删除的
 。这些主键可通过
 [HttpServletRequest request] 对象中获取。这些分类将
 通过
 [DaoCategorie] 层中的 [deleteEntitiesById] 方法进行删除。

@RequestMapping(value = "/deleteCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
该方法接收待删除的分类名称
。这些名称可通过
 [HttpServletRequest request] 中获取。类别将通过
 通过
[DaoCategorie] 层中的 [deleteEntitiesByname] 方法进行删除。

@RequestMapping(value = "/getAllShortCategories",
 method = RequestMethod.GET)

public Response<List<Category>> getAllShortCategories()
====
该 URL 不包含参数。简短分类是通过
通过 [getAllShortEntities] 方法从
 [DaoCategorie] 中获取。

@RequestMapping(value = "/getAllLongCategories",
 method = RequestMethod.GET)

public Response<List<Category>> getAllLongCategories()
====
该 URL 没有参数。长类别
 通过 [getAllLongEntities] 方法从
[DaoCategorie] 中通过 [getAllLongEntities] 方法检索。

@RequestMapping(value = "/getLongCategoriesById",
 method = RequestMethod.POST)

public Response<List<Category>> getLongCategoriesById(HttpServletRequest request)
====
该方法通过 POST 请求接收
 所需类别的primary keys。这些键可通过
 [HttpServletRequest request] 中获取。长类别的
 通过
 [DaoCategorie] 的 [getLongEntitiesById] 方法进行检索。

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Category>> getLongCategoriesByName
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需类别的名称
 。这些名称可通过
 [HttpServletRequest request] 中获取。长类别的
通过
 [DaoCategorie] 的 [getLongEntitiesByName] 方法进行检索。

@RequestMapping(value = "/getShortCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Category>> getShortCategoriesByName
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需类别的名称
 。这些名称可通过
 [HttpServletRequest request] 中获取。简短的类别名称
 通过
 [DaoCategorie] 层中的 [getShortEntitiesByName] 方法获取。

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST)

public Response<List<Category>> getShortCategoriesById
(HttpServletRequest request)
====
该方法通过 POST 请求接收
 所需分类的主键。这些主键可通过
 [HttpServletRequest request] 中获取。简短的类别名称是
 使用
 [DaoCategorie] 对象的 [getShortEntitiesById] 方法检索。

17.3.4.2. 由 [ProductController] 暴露的 URL

URL
方法

@RequestMapping(value = "/saveProducts", method =
 RequestMethod.POST, content-type = "application/json;
 charset=UTF-8")

public Response<List<CoreProduct>> saveProducts
(HttpServletRequest request)
====
该方法通过 POST 请求接收待持久化的产品。这些
数据可通过 [HttpServletRequest
 请求]中获取。产品由
 [DaoProduit] 层中的 [saveEntities] 方法进行持久化。仅
 数据会被返回给客户端。

@RequestMapping(value = "/deleteAllProducts",
 method = RequestMethod.GET)

public Response<Void> deleteAllProducts()
====
该 URL 没有参数。产品通过
 [DaoProduit] 层中的 [deleteAllEntities] 方法进行删除。

@RequestMapping(value = "/deleteProductsById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProductsById
(HttpServletRequest request)
====
该方法接收待删除产品的主键
 。这些主键可通过
 [HttpServletRequest request] 中获取。产品将
 使用
[DaoProduit] 中的 [deleteEntitiesById] 方法进行删除。

@RequestMapping(value = "/deleteProductsByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProductsByName
(HttpServletRequest request)
====
该方法接收待删除产品的名称
 。这些名称可通过
 [HttpServletRequest request] 中获取。产品将
 通过 [deleteEntitiesByname] 方法从
 [DaoProduit] 层中的 [deleteEntitiesByname] 方法进行删除。

@RequestMapping(value = "/getAllShortProducts",
 method = RequestMethod.GET)

public Response<List<Product>> getAllShortProducts()
====
该 URL 没有参数。短分类是通过
 通过 [getAllShortEntities] 方法从
 [DaoProduct] 层中通过 [getAllShortEntities] 方法

@RequestMapping(value = "/getAllLongProducts",
 method = RequestMethod.GET)

public Response<List<Product>> getAllLongProducts()
====
该 URL 没有参数。长条形产品是通过
 通过
 [DaoProduct] 层中的 [getAllLongEntities] 方法。

@RequestMapping(value = "/getLongProductsById",
 method = RequestMethod.POST)

public Response<List<Product>> getLongProductsById
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需产品的主键
 。这些主键可通过
 [HttpServletRequest request] 中获取。长产品
通过 [getLongEntitiesById] 方法从

 [DaoProduit] 的 [getLongEntitiesById] 方法进行检索。

@RequestMapping(value = "/getLongProductsByName",
 method = RequestMethod.POST)

public Response<List<Product>> getLongProductsByName
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需产品的名称。
 这些名称可通过 [HttpServletRequest
 请求]中获取。长产品通过
 [DaoProduit] 层中的 [getLongEntitiesByName] 方法检索。

@RequestMapping(value = "/getShortProductsByName",
 method = RequestMethod.POST)

public Response<List<Product>> getShortProductsByName
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需产品的名称。
 这些名称可通过 [HttpServletRequest
 请求]中获取。简短的产品信息通过
[DaoProduit] 层中的 [getShortEntitiesByName] 方法获取。

@RequestMapping(value = "/getShortProductsById",
 method = RequestMethod.POST)

public Response<List<Product>> getShortProductsById
(HttpServletRequest request)
====
该方法通过 POST 请求接收所需产品的主键
 。这些主键可通过
[HttpServletRequest request] 中获取。简短产品
通过
 [DaoProduit] 的 [getShortEntitiesById] 方法进行检索。

17.3.5. Web 服务的通用实现

  

Web 服务公开的 URL 列表显示,我们为管理类别和产品提供了相同类型的 URL。与其编写两个非常相似的控制器,不如让它们继承自一个类,该类将处理这两个控制器共有的所有工作。这就是上文提到的 [AbstractController] 类。该类将实现以下 [Iws] 接口:


package spring.webjson.server.service;
 
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
 
import spring.jdbc.entities.AbstractCoreEntity;
 
public interface Iws<T extends AbstractCoreEntity> {
 
    // list of all T entities
    public Response<List<T>> getAllShortEntities();
 
    public Response<List<T>> getAllLongEntities();
 
    // special entities - short version
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
 
    // special entities - long version
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
 
    // update of several entities
    public Response<List<T>> saveEntities(HttpServletRequest request);
 
    // delete all entities
    public  Response<Void> deleteAllEntities();
 
    // deletion of multiple entities
    public  Response<Void> deleteEntitiesById(HttpServletRequest request);
 
    public  Response<Void> deleteEntitiesByName(HttpServletRequest request);
}

该接口实现了将要使用的 [DAO] 层接口中的方法:


package spring.jdbc.dao;
 
import java.util.List;
 
import spring.jdbc.entities.AbstractCoreEntity;
 
public interface IDao<T extends AbstractCoreEntity> {
 
    // list of all T entities
    public List<T> getAllShortEntities();
 
    public List<T> getAllLongEntities();
 
    // special entities - short version
    public List<T> getShortEntitiesById(Iterable<Long> ids);
 
    public List<T> getShortEntitiesById(Long... ids);
 
    public List<T> getShortEntitiesByName(Iterable<String> names);
 
    public List<T> getShortEntitiesByName(String... names);
 
    // special entities - long version
    public List<T> getLongEntitiesById(Iterable<Long> ids);
 
    public List<T> getLongEntitiesById(Long... ids);
 
    public List<T> getLongEntitiesByName(Iterable<String> names);
 
    public List<T> getLongEntitiesByName(String... names);
 
    // update of several entities
    public List<T> saveEntities(Iterable<T> entities);
 
    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
 
    // delete all entities
    public void deleteAllEntities();
 
    // deletion of multiple entities
    public void deleteEntitiesById(Iterable<Long> ids);
 
    public void deleteEntitiesById(Long... ids);
 
    public void deleteEntitiesByName(Iterable<String> names);
 
    public void deleteEntitiesByName(String... names);
 
    public void deleteEntitiesByEntity(Iterable<T> entities);
 
    public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}

从 [DAO] 层中的 [IDao<T>] 接口到 Web 服务中的 [Iws<T>] 接口的转换遵循以下规则:

  • [Iws<T>] 接口的方法不会抛出异常。如果发生异常,它将被封装在 [Response] 对象中;
  • 移除了第 45 行和第 47 行中类似 [Iterable<String> names, String... names] 的参数变体。这些方法从客户端的 [HttpServletRequest request] 类型的 HTTP 请求中获取参数;

所有 Web 服务响应都将封装在以下 [Response] 对象中:

  

package spring.webjson.server.service;
 
 
public class Response<T> {
 
    // ----------------- properties
    // operation status
    private int status;
    // an error message
    private String exception;
    // the body of the reply
    private T body;
 
    // manufacturers
    public Response() {
 
    }
 
    public Response(int status, String exception, T body) {
        this.status = status;
        this.exception = exception;
        this.body = body;
    }
 
    // getters and setters
...
}
  • 第 4 行:响应封装了一个类型 T;
  • 第 12 行:类型为 T 的响应;
  • 第 7–10 行:方法可能会遇到异常。在这种情况下,它将返回一个包含以下内容的响应:
    • 第 8 行:status!=0;
    • 第 10 行:一条错误消息;

[AbstractController] 类如下所示:


package spring.webjson.server.service;
 
import java.util.List;
 
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
 
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
 
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
 
    @Autowired
    protected ApplicationContext context;
 
    // layer DAO
    private IDao<T> dao;
 
    abstract protected IDao<T> getDao();
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @PostConstruct
    public void init(){
        dao=getDao();
    }
    
    @Override
    public Response<List<T>> getAllShortEntities() {
        try {
            // answer
            return new Response<List<T>>(0, null, dao.getAllShortEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
        }
    }
 
    @Override
    public Response<List<T>> getAllLongEntities() {
        try {
            // answer
            return new Response<List<T>>(0, null, dao.getAllLongEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
        }
    }
 
    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
    ...
    }
 
    @Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
    ...
    }
 
    @Override
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
    ...
    }
 
    @Override
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
    ...
    }
 
        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }
 
 
    @Override
    public Response<Void> deleteAllEntities() {
        try {
            // we delete
            dao.deleteAllEntities();
            // answer
            return new Response<Void>(0, null, null);
        } catch (DaoException e) {
            return new Response<Void>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
        }
    }
 
    @Override
    public Response<Void> deleteEntitiesById(HttpServletRequest request) {
        ...
    }
 
    @Override
    public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
    ...
    }
 
}

所有方法的实现方式都相同:

  1. 如果他们需要信息,就会从 [HttpServletRequest request] 对象中获取;
  2. 他们调用[DAO]层中与自身同名的方法;
  3. 他们处理可能发生的任何异常,无论是操作 1(检索参数)中还是操作 2(调用 [DAO] 层)中;

首先,让我们看看 [DAO] 层是如何注入到 [AbstractController] 类中的:


public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
 
    @Autowired
    protected ApplicationContext context;
 
    // layer DAO
    private IDao<T> dao;
 
    abstract protected IDao<T> getDao();
 
    @PostConstruct
    public void init(){
        dao=getDao();
}

  • 第 1 行:该类是抽象类,并实现了泛型接口 [Iws<T>];
  • 第 3-4 行:注入 Spring 上下文;
  • 第 7 行:指向待使用的 [DAO] 层的引用(此时该引用尚不明确);
  • 第 9 行:抽象方法 [getDao] 将返回待使用的 [DAO] 层的引用。该方法将由子类重写,因此由子类指定使用哪个 [DAO] 层(DaoProduct 或 DaoCategory);
  • 第 11 行:[@PostConstruct] 注解标记了一个在对象实例化完成后执行的方法。一旦实例化完成,Spring 的注入就已完成。此时子类将已获得其 [DAO] 层的引用,因此可以将其传递给父类;

[getShortEntitiesById] 方法如下:


    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
            });
            // answer
            return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
        }
}
  • 第 5 行:客户端提交的值将是一个 JSON 字符串。此处用于检索该值;
  • 第 7–9 行:该 JSON 字符串包含需要获取简短版本的实体的主键列表;
  • 第 11 行:我们调用同名的 [DAO] 方法。类型为 [List<T>] 的响应被封装在 [Response] 对象中;
  • 第 13 行:[DAO] 层抛出异常的情况;
  • 第 15 行:处理其他异常的情况,特别是第 8 行 JSON 参数反序列化过程中可能抛出的异常;

[getShortEntitiesByName] 方法与此类似:


@Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
            });
            // answer
            return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
        }
}
  • 第 4–9 行:此处的 jSON 参数是我们要获取简短版本的类别名称列表;

[saveEntities] 方法尚未实现,因为它高度依赖于要持久化的实体类型,即 [Category] 或 [Product]。可重构的代码很少。因此,此任务留给子类处理。


        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }

17.3.6. [CategoryController]

  

[CategorieController] 控制器负责处理与分类相关的 URL:


package spring.webjson.server.service;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
 
@RestController
public class CategorieController extends AbstractController<Categorie> {
 
    @Autowired
    private IDao<Categorie> daoCategorie;
 
    @Override
    protected IDao<Categorie> getDao() {
        return daoCategorie;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllLongCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
    ...
    }
 
    @RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
    public Response<Void> deleteAllCategories() {
        return super.deleteAllEntities();
    }
 
    @RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }
 
    @RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }
 
}
  • 第 26 行:[CategorieController] 类继承自 [AbstractController] 类;
  • 第 25 行:[@RestController] 注解将该类标记为 Spring 组件。该注解还表明该类是一个 Web 服务,其方法会直接以 JSON 格式将响应发送给客户端;
  • 第 28–29 行:此处注入了对 [DAO] 层的引用;
  • 第 31–34 行:重定义了父类中声明为抽象的 [getDao] 方法,其目的是返回一个指向待使用的 [DAO] 层的引用;

这些方法均遵循相同的模式:

  • 将处理任务委托给父类;
  • 初始化将用于序列化响应的 JSON 映射器;
  • 发送响应;

让我们来看看几个 URL 的签名:


@RequestMapping(value = "/getAllShortCategories", method
 = RequestMethod.GET)
- 通过 GET 方法调用 URL [/getAllShortCategories]

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- 通过 POST 请求调用 URL [/getShortCategoriesById]。POST 数据为包含所需分类主键的 JSON 字符串
目标分类的主键;

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- 通过
POST 请求调用。提交的值是一个 JSON 字符串,其中包含
所需分类的名称;

现在,让我们仔细看看 [saveCategories] 方法,该方法的格式与其他方法不同:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // we persist categories
            categories = daoCategorie.saveEntities(categories);
            // we return the result
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // result
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
        } catch (DaoException e) {
            return new Response<List<CoreCategorie>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
        }
}
  • 第 1 行:URL [/saveCategories] 附带一个 POST 请求值。这是一个 JSON 字符串,其中包含待持久化的类别的完整版本;
  • 第 5–10 行:根据 JSON 字符串重新创建待持久化的分类。连接 [Product] 与其 [Category] 的 [product.category] 链接为空,因为在 [Category] 的完整版本中,每个 [Product] 都处于其简短版本,且不包含 [category] 字段。这并不构成问题,因为使用 JDBC 实现的 [DAO] 层并不需要此信息;
  • 第 12 行:将类别持久化。接收到的类别列表已补充了被持久化元素(即类别和产品)的主键。其他内容均未改变。为了避免返回整个接收列表(这会消耗大量资源),我们将仅返回该列表中元素的主键。为此,我们使用以下 [CoreCategory] 和 [CoreProduct] 类:
  

package spring.webjson.server.entities;
 
import java.util.List;
 
public class CoreCategorie {
 
    // primary key
    private Long id;
    
    // manufacturers
    public CoreCategorie() {
 
    }
 
    public CoreCategorie(Long id) {
        this.id=id;
    }
 
    // list of products
    private List<CoreProduit> coreProduits;
 
    // getters and setters
    ...
}
  • 第 8 行:产品的主键;
  • 第20行:其产品的主键;

package spring.webjson.server.entities;
 
public class CoreProduit {
 
    // primary key
    private Long id;
 
    // manufacturers
    public CoreProduit() {
 
    }

    public CoreProduit(Long id) {
        this.id = id;
    }
 
    // getters and setters
...
}
  • 第 6 行:产品的主键;

让我们回到 [saveCategories] 方法的代码:


...            
// on persiste les catégories
            categories = daoCategorie.saveEntities(categories);
            // on rend le résultat
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // résultat
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
  • 第 5–17 行:我们构建将返回给远程客户端的 [CoreCategory] 对象列表;
  • 第 19 行:返回响应并将其序列化为 JSON;

17.3.7. 处理 JSON 过滤器

对于控制器中的每个方法,JSON序列化/反序列化主要涉及两个环节:

  • 对提交值的反序列化:此处显式处理;
  • 结果的序列化:此处隐式处理;

让我们从 [CategorieController.saveCategories] 中对提交值的反序列化开始:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
  • 第 8 行:我们从 Spring 上下文中获取了一个配置为处理 JSON 过滤器 [jsonMapperLongCategorie] 的映射器。让我们回到配置类 [WebConfig] 中该映射器的定义:

// -------------------------------- configuration filters [json]
    // mapping jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }
 
    // filters jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }
 
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }
 
  • 第 32–40 行:由 [CategorieController] 类检索到的 JSON 映射器 [jsonMapperLongCategorie];
  • 第 35 行:该映射器由第 18–21 行中的 [jsonMapper] 方法返回;
  • 第 18–21 行:[jsonMapper] 方法返回第 3–9 行中 [MappingJackson2HttpMessageConverter] 中的 JSON 映射器;

换言之,即下文第 4 行在 [CategorieController.saveCategories] 中获取的 JSON 映射器:


            // on récupère la valeur postée
            String body = CharStreams.toString(request.getReader());
            // on la désérialise
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});

是 Spring MVC 用于反序列化客户端提交的值并序列化发回给客户端的结果的默认转换器。在上面的代码中,并未对提交的值进行隐式反序列化。若要实现这一点,你需要编写如下代码:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {

在此情况下,[categories] 参数中提交的值本应被自动反序列化。但存在 [Categorie] 实体所携带的 [jsonFilterCategorie] 过滤器的问题,该过滤器需要进行配置。这就是我们选择显式反序列化(第 4–5 行)的原因。 第二个需要注意的点是,第4行中的映射器(即Spring MVC默认使用的映射器)也适用于序列化结果 [Response<List<CoreCategory>>]。事实上,[CoreCategory] 实体本身并不具备JSON过滤器。 因此,无需为生成的 JSON 映射器配置额外的过滤器。在此情况下,发送给客户端的响应将被隐式序列化。

17.3.8. [ProductController]

  

[ProduitController] 控制器负责处理与产品相关的 URL。其代码与 [CategorieController] 控制器类似:


package spring.webjson.server.service;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
 
@RestController
public class ProduitController extends AbstractController<Produit> {
 
    @Autowired
    private IDao<Produit> daoProduit;
 
    @Override
    protected IDao<Produit> getDao() {
        return daoProduit;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllShortProduits() {
        // parent
        Response<List<Produit>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits() {
        // parent
        Response<List<Produit>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        ...
    }
 
    @RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
    public Response<Void> deleteAllProduits() {
        return super.deleteAllEntities();
    }
 
    @RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }
 
    @RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }
 
}

只有 [saveProducts] 方法的结构与其他方法不同:


@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
            List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
            });
            // we persist products
            produits = daoProduit.saveEntities(produits);
            List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
            for (Produit produit : produits) {
                coreProduits.add(new CoreProduit(produit.getId()));
            }
            // we return the answer
            return new Response<List<CoreProduit>>(0, null, coreProduits);
        } catch (DaoException e) {
            return new Response<List<CoreProduit>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
        }
}
  • 第 4–9 行:根据接收到的 JSON 字符串,我们重建待持久化的 [Product] 对象列表。由于接收到的 JSON 字符串包含产品的简短版本,因此其 [category] 字段为 null。同样,DAO/JDBC 层并不需要此信息;
  • 第 11 行:将产品数据持久化;
  • 第 12–15 行:构建待返回的 [CoreProduct] 对象列表;
  • 第 18 行:返回响应,该响应将在发送给远程客户端之前,由第 7 行的映射器进行序列化(由 Spring MVC 执行隐式序列化)(参见第 17.3.7 节的讨论);

17.3.9. Web 服务 / JSON 执行类

  

[Boot] 类是该项目的可执行类:


package spring.webjson.server.boot;
 
import org.springframework.boot.SpringApplication;
 
import spring.webjson.server.config.AppConfig;
 
public class Boot {
 
    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}
  • 第 10 行:执行静态方法 [SpringApplication.run]。[SpringApplication] 类是 [Spring Boot] 项目中的一个类(第 3 行)。向其传递了两个参数:
    • [AppConfig.class]:用于配置整个应用程序的类;
    • [args]:传递给第 9 行 [main] 方法的任何参数。此参数在此处未被使用;

执行该类时,会生成以下日志:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

11:34:08.661 [main] INFO  spring.webjson.server.boot.Boot - Starting Boot on Gportpers3 with PID 6796 (started by ST in D:\data\istia-1415\spring data\dvp\dvp-spring-database-05\spring-database-generic\spring-webjson\spring-webjson-server-jdbc-generic)
11:34:08.700 [main] INFO  o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext - Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:08.916 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapper': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.917 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.918 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:09.409 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat initialized with port(s): 8081 (http)
11:34:09.641 [main] INFO  o.a.catalina.core.StandardService - Starting service Tomcat
11:34:09.642 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet Engine: Apache Tomcat/8.0.20
11:34:09.778 [localhost-startStop-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
11:34:09.778 [localhost-startStop-1] INFO  o.s.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 1081 ms
11:34:09.839 [localhost-startStop-1] INFO  o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/*]
11:34:10.558 [main] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 5.1.3.Final
11:34:10.654 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreCategorie>> spring.webjson.server.service.CategorieController.saveCategories(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllLongCategories()
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllShortCategories()
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteAllCategories()
11:34:10.748 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreProduit>> spring.webjson.server.service.ProduitController.saveProduits(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllLongProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllShortProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteAllProduits()
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.809 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8081"]
11:34:10.826 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8081"]
11:34:10.860 [main] INFO  o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
11:34:11.733 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8081 (http)
11:34:11.934 [main] INFO  spring.webjson.server.boot.Boot - Started Boot in 3.533 seconds (JVM running for 4.137)
11:34:20.382 [http-nio-8081-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring FrameworkServlet 'dispatcherServlet'
11:34:20.384 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization started
11:34:20.410 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
11:34:33.103 [http-nio-8081-exec-8] INFO  o.s.b.f.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
11:34:33.168 [http-nio-8081-exec-8] INFO  o.s.j.support.SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana]
  • 第 11-15 行:发现定义了 JSON 过滤器的 Bean。它们将覆盖在 JDBC 层配置项目中发现的同名 Bean;
  • 第 17-18 行:启动 Tomcat 服务器以运行 Web/JSON 服务;
  • 第 19–21 行:初始化 Spring MVC 上下文;
  • 第 24–43 行:发现已暴露的 URL;

17.3.10. 测试 /jSON Web 服务

为了执行测试,我们使用 [Advanced Rest Client](参见第 23.11 节)来查询 /jSON Web 服务暴露的 URL(当然,/jSON Web 服务必须正在运行,DBMS 也必须正在运行)。 为填充数据库,我们运行名为 [spring-jdbc-generic-04-fillDataBase] 的执行配置,该配置将 5 个类别和 10 个产品数据填入数据库:

 
  • 在[1-3]中,我们通过HTTP GET请求获取URL [/getAllLongCategories];

我们收到以下响应:

  • 在 [1] 中,客户端的 HTTP 请求;
  • 在 [2] 中,服务器的 HTTP 响应;
  • 在 [3] 中,状态码 [200 OK] 表示服务器已成功处理该请求;
  • 在 [4] 中,服务器的 JSON 响应;

完整的 JSON 响应如下:


{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
  • status:0 表示没有服务器端错误;
  • exception: null 表示没有错误信息;
  • body:是响应正文,本例中为包含产品列表的分类列表。共有两个分类,每个分类下有5个产品;

我们将把商品 [product15] 添加到类别 [category1] 中。为此,我们将使用 URL [/saveProducts],该 URL 需要一个包含待保存商品的 JSON 字符串(插入/更新)。该字符串如下所示:

[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]

向 Web 服务 / JSON 发送请求的格式如下:

  • 在 [1] 中,请求的 URL;
  • 在 [2] 中,它是通过 POST 操作请求的;
  • 在 [3] 中,是提交的 JSON 字符串;
  • 在 [4] 中,通知服务器将发送 JSON 数据;

服务器的响应如下:

  • 在[1]中,我们收到了一组包含[CoreProduct]对象及其主键的列表。而在这里,我们收到了一组仅包含一个项的列表,该项的主键正是我们刚刚插入数据库的产品的主键;

现在,让我们请求名为 [category[1]] 的分类的完整版本:

  • 在 [1] 中,请求的 URL;
  • 在 [2] 中,我们发起一个 POST 请求;
  • 在 [3,4] 中,提交的值是一个 JSON 字符串。这代表了我们要获取长版本的类别名称列表;

我们得到以下结果:

  • 在[5]中,范畴[category[1]]现在有了第六个积;

现在让我们删除这个积:

  • 在 [1] 中,请求的 URL;
  • 在 [2] 中,我们发起一个 POST 请求;
  • 在 [3-4] 中,我们提交一个 JSON 字符串,该字符串表示我们要删除的产品的主键列表;

结果如下:

 
  • [status:0] 表示删除成功;

现在,让我们查询产品 [product[1,5]] 以验证它是否确实已被删除:

 

我们得到以下结果:

 
  • [status:0] 表示操作已成功完成,未发生任何异常;
  • [body:[0]] 表示 [body] 是一个空列表。因此,实体 [product[1,5]] 已被成功删除;

所有 [GET] 操作均可在标准网页浏览器中执行:

欢迎读者测试该 Web 服务 / JSON 的其他 URL。