Spring Data REST 与 Spring RestTemplate 实战详解

这篇分为两部分内容进行介绍(Spring Data REST 和 Spring RestTemplate)。我之前有一篇文章完整的介绍了 HTTP 协议的内容,而这两个工具中一个帮我们完成 Client 的实现,另一个帮我们完成 Server端的实现。

希望大家对 Spring 和 HTTP 之间有个完整的认识,并能够优雅地使用。

RestTemplate

认识 RestTemplate

org.springframework.web.client.RestTemplate 位于 spring-web 的核心项目里面。如下图所示:

RestTemplate 帮我们提供了 HTTP 的rest风格的 API 操作方法。

RestTemplate 快速入门使用

首先,配置 RestTemplatebean。

@Beanpublic RestTemplate restTemplate() {  return new RestTemplate();
}

我们以发短信为例,代码如下:

@Autowiredprivate RestTemplate restTemplate;/**
 * 提交创蓝处理短信请求
 *
 * @param sign
 * @param templateDetail
 * @param messageContent
 * @param phones
 * @return
 */private ResponseEntity<ChuangLanSmsResponse> postChuangLanSmsResponse(String sign, MessageTemplateDetail templateDetail, MessageRequest messageContent, List phones) {
    ChuangLanSmsRequest chuangLanSmsRequest = new ChuangLanSmsRequest();
    chuangLanSmsRequest.setAccount(messageConsumerProperties.getChuangLanAccount().getAccount());
    chuangLanSmsRequest.setPassword(messageConsumerProperties.getChuangLanAccount().getPassword());
    chuangLanSmsRequest.setMsg(new StringBuilder(sign).append(templateDetail.getVendorTemplateTxt()).toString());
    chuangLanSmsRequest.setPhone(String.join(",", phones));
    chuangLanSmsRequest.setReport("true");
    chuangLanSmsRequest.setExtend("1");
    chuangLanSmsRequest.setUid(messageContent.getUuid());
    chuangLanSmsRequest.setParams(messageContent.getTemplateParams());    //直接使用restTemplate调用外包的api接口直接发送短信,并且返回ChuangLanSmsResponse实体结果,不需要我们做任何处理
    return restTemplate.postForEntity("http://smsbj1.253.com/msg/variable/json", chuangLanSmsRequest, ChuangLanSmsResponse.class);
}

它大大帮我们简化了 API 的操作步骤。

我们看一下其提供的方法有哪些,看一下源码:

相关代码如下:

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {    @Override
    @Nullable
    public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
            Object... uriVariables) throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }    @Override
    @Nullable
    public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
            Map<String, ?> uriVariables) throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }    @Override
    @Nullable
    public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =                new HttpMessageConverterExtractor<>(responseType, getMessageConverters());        return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
    }    @Override
    public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
            Class<T> responseType, Object... uriVariables) throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);        return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
    }    @Override
    public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
            Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);        return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
    }    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);        return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor));
    }
......}

其实这个时候我们就可以发现正好针对 HTTP 的 method 有如下几种方法类型:

  • GET
  • POST
  • PUT
  • patch
  • DELETE
  • HEAD
  • OPTIONS
  • EXCHANGE
  • EXECUTE

源码中我们也可以知道 RestTemplate 类中的方法主要是来自接口 RestOperations。我们接下去来分别举个例子。

(1)get 请求样例,根据 HTTP 的 get 方法取用户基本信息。代码如下:

//直接使用getForObject返回接口api/user/1的User实体json对象。User user = restTemplate.getForObject(“http://localhost:8080/api/user/1”, User.class); //直接使用getForEntity返回接口api/user/1的User实体加上http reponse的各种其它属性值,一个完整的httpreponse,里面有状态码,header等信息。ResponseEntity<User> userResponseEntity = restTemplate.getForEntity("http://localhost:8080/api/user/1", User.class);//userResponseEntity里面的状态码,可以作很多自己的逻辑if (HttpStatus.OK.equals(userResponseEntity.getStatusCode())) {
    User user = userResponseEntity.getBody();
}//ResponseEntity里面除了StatusCode外还有Header等信息HttpHeaders httpHeaders = userResponseEntity.getHeaders();//还可以在get方法上加入自己的参数UserParam userParam = new UserParam();
userParam.setUserName("jack");
ResponseEntity<User> userResponseEntity = restTemplate.getForEntity("http://localhost:8080/api/user/1", User.class, userParam);

(2)Post 方法。

其中 Object request 可以是参数,也可以是 HttpEntity。通过 HttpEntity,我们可以设置请求头,等一些额外信息。请见下面代码:

//普通http请求,向服务提交userParam参数,并将User对象返回。User userParam = new User(........);
User user = restTemplate.postForObject("http://localhost:8080/api/user/",userParam, User.class); 
ResponseEntity<User> responseEntity = restTemplate.postForEntity(uri, userParam, User.class); 
//通过HttpEntity,自定义header的一种方式HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
UserParam userParam1 = new UserParam();
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(userParam1, headers);
restTemplate.postForEntity("http://localhost:8080/api/user/",requestEntity,User.class);

(3)PUT、patch、DELETE、OPTIONS,其实操作方法就同上面两个比较类似,我们就不重复了。

(4)HEDER 我们可以单独请求一个 URL 取 Header 信息。代码如下。

//只要header值HttpHeaders httpHeaders = restTemplate.headForHeaders("http://localhost:8080/api/user/1");
HttpHeaders httpHeaders = restTemplate.headForHeaders("http://localhost:8080/api/user/1",userParam);

(5)execute 方法。

我们看源码会发现 restTemplate 帮我们实现了大部分通用的情况,如果遇到特殊情况,我们也可以类似 Servlet 一样的思路直接调用 restTemplate 的 execute 方法。代码如下。

//看一个文件 上传的例子MultiValueMap<String, Object> bodyMap = new LinkedMultiValueMap<>();
bodyMap.add("user-file", "/a.txt");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(bodyMap, headers);

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange("http://localhost:8080/upload",
    HttpMethod.POST, requestEntity, String.class);
System.out.println("response status: " + response.getStatusCode());
System.out.println("response body: " + response.getBody());

实际工作中我们的配置

实际生产项目,我们主要考虑三点:

(1)超时机制。

(2)重试机制。

(3)请求日志。

所以我们会做如下配置:

@Configurationpublic class MessageConsumerConfiguration {    /**
     * 替代默认的SimpleClientHttpRequestFactory
     * 设置超时时间重试次数
     * 还可以设置一些拦截器以便监控
     *
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {        //生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();

        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));

        HttpClient httpClient = builder.build();

        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        RestTemplate restTemplate = new RestTemplate(requestFactory);        //做个日志拦截器
        restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate()));        return restTemplate;
    }
}

而其中 LoggingRestTemplate() 的源码如下:

/**
 * 实现ClientHttpRequestInterceptor接口做拦截器
 */public class LoggingRestTemplate implements ClientHttpRequestInterceptor {    private final static Logger LOGGER = LoggerFactory.getLogger(LoggingRestTemplate.class);    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);        return traceResponse(response);
    }    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        LOGGER.debug(            "====request begin===URI: {}Method            : {},Headers         : {}Request body: {}==request end=====",
            request.getURI(),
            request.getMethod(),
            request.getHeaders(),            new String(body, "UTF-8"));
    }    private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {        final ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(            new InputStreamReader(responseWrapper.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }
        LOGGER.debug(            "===response begin=== Status code    : {}Status text    : {}Headers            : {}Response body: {}==response end===",
            responseWrapper.getStatusCode(),
            responseWrapper.getStatusText(),
            responseWrapper.getHeaders(),
            inputStringBuilder.toString());        return responseWrapper;
    }
}

原理和扩展

我们来看一下 RestTemplate 的架构图,发现它就像 SpringMVC 一样是个完整的体系,有拦截器,有 Converter,还有 Handler 等给我们留了很多的扩展入口。

在这里扩展一下,其实 RestTemplate 再使用得深一点,可以直接做 repository,或者是网关,做透传。

关于RestTemplate 的更详细用法请参考:RestTemplate guideline。

Spring Data Rest

快速入门
Spring Data Rest 介绍

REST 风格的 Web API 服务已成为在 Web 上应用程序集成的首选方式。市场上都在争相定义 REST 风格的JSON API 返回格式,并且提供相应的解决方案。目前 Java 社区常见的有两种对 HTTP 的服务接口返回的 JSON 接口进行了定义。

(1)JSON API

JSON API 来自 JSON 的数据传输,它被隐式地定义在 Ember 的 REST 风格数据适配器。一般来说,Ember Data 被设计用来实现这样的目的:消除那些为不同应用程序与服务器之间通信而写的特殊代码,而且用 REST 风格数据适配器将它们转换成统一的方式。

通过遵循共同的约定,可以提高开发效率,利用更普遍的工具,可以使你更加专注于开发重点:你的程序。基于 JSON API 的客户端还能够充分利用缓存,以提升性能,有时甚至可以完全不需要网络请求。示例代码如下。

//下面是一个使用 JSON API 发送响应(response)的示例:{  "links": {    "posts.author": {      "href": "http://example.com/people/{posts.author}",      "type": "people"
    },    "posts.comments": {      "href": "http://example.com/comments/{posts.comments}",      "type": "comments"
    }
  },  "posts": [{    "id": "1",    "title": "Rails is Omakase",    "links": {      "author": "9",      "comments": [ "5", "12", "17", "20" ]
    }
  }]
}

JSON API 严格规定了返回的 Json 文档结果的格式,JSON API 服务器支持通过 GET 方法获取资源。而且必须独立实现 HTTP POST、PUT 和 DELETE 方法的请求响应,以支持资源的创建、更新和删除。

JSON API 还有很多与之协议规定相对应的客户端实现,包括 Java 语言的。

(2)Spring Data Rest

Spring Data Rest 是基于 Spring Data Repositories,分析实体之间的关系。为我们生成 Hypermedia API(HATEOAS)风格的 HTTP Restful API 接口。

HATEOAS(Hypermedia as the Engine of Application State)是 REST 架构风格中最复杂的约束,也是构建成熟 REST 服务的核心。它的重要性在于打破了客户端和服务器之间严格的契约,使得客户端可以更加智能和自适应,而 REST 服务本身的演化和更新也变得更加容易。

在介绍 HATEOAS 之前,先介绍一下 Richardson 提出的 REST 成熟度模型。该模型把 REST 服务按照成熟度划分成 4 个层次:

  1. 第一个层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP 和 XML-RPC 都属于此类。
  2. 第二个层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达。
  3. 第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源。
  4. 第四个层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。

Spring Data REST 通过构建在 Spring Data Repositories 之上,自动将其导出为 REST 资源的 API,减少了大量重复代码和无聊的样板代码。它利用超媒体来允许客户端查找存储库暴露的功能,并将这些资源自动集成到相关的超媒体功能中。

Spring Data REST 本身就是一个 Spring MVC 应用程序,它的设计方式应该是尽可能少的集成到现有的 Spring MVC 应用程序中。现有的(或将来的)服务层可以与 Spring Data REST 一起运行,只有较小的考虑。

快速开始

我们以 Gradle、Spring Boot 2.0 和 Spring Data Jpa、Spring Data Rest 快速大家建一个 Rest 风格的消费 Server 版 API。

(1)本地数据库我们建立两张表(user 1 对多 user_address),创建脚本如下:

create table user (    id int auto_increment       primary key,    name varchar(50) null,
    email varchar(200) null);create table user_address(    id int auto_increment   primary key,
    user_id int null,
    city varchar(50) null,    constraint user_address_user_id_fk foreign key (user_id) references user (id)
);
//建立外键create index user_address_user_id_fk    on user_address (user_id);

(2)我们利用 Gradle 创建一个如下的项目目录:

利用 Gradle 引入如下相关的 jar 包:

   //spring Data Jpa
   compile('org.springframework.boot:spring-boot-starter-data-jpa:2.0.0.RC1')   //spring Data Rest
   compile('org.springframework.boot:spring-boot-starter-data-rest:2.0.0.RC1')   //数据库连接
   runtime('mysql:mysql-connector-java:5.1.45')   //Spring data Rest API的可视化界面配合hal格式的rest api使用
   compile('org.springframework.data:spring-data-rest-hal-browser:3.0.3.RELEASE')

(3)application.properties 内容如下:

spring.profiles.active=dev
spring.profiles=dev
server.port=8070//////Data Sources Settingspring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/sys?useSSL=falsespring.datasource.username=root
spring.datasource.password=123456spring.jpa.show-sql=true//////Spring Data Rest Settingspring.data.rest.base-path=/api

(4)DemoApplication 内容如下:

package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication {   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}

(5)两个实体的内容 User,如下代码:

package com.example.demo;import javax.persistence.*;import java.util.Collection;@Entitypublic class User {   private int id;   private String name;   private String email;   private Collection<UserAddress> userAddressesById;   @Id
   @Column(name = "id", nullable = false)   public int getId() {      return id;
   }   @Basic
   @Column(name = "name", nullable = true, length = 50)   public String getName() {      return name;
   }   @Basic
   @Column(name = "email", nullable = true, length = 200)   public String getEmail() {      return email;
   }   @OneToMany(mappedBy = "userByUserId")   public Collection<UserAddress> getUserAddressesById() {      return userAddressesById;
   }
}package com.example.demo;import javax.persistence.*;@Entity@Table(name = "user_address", schema = "sys", catalog = "")public class UserAddress {   private int id;   private String city;   private User userByUserId;   @Id
   @Column(name = "id", nullable = false)   public int getId() {      return id;
   }   @Basic
   @Column(name = "city", nullable = true, length = 50)   public String getCity() {      return city;
   }   @ManyToOne
   @JoinColumn(name = "user_id", referencedColumnName = "id")   public User getUserByUserId() {      return userByUserId;
   }
}

(6)两个 Repository 内容如下:

package com.example.demo.repository;import com.example.demo.UserAddress;import org.springframework.data.jpa.repository.JpaRepository;public interface UserAddressRepository extends JpaRepository<UserAddress,Integer> {}package com.example.demo.repository;import com.example.demo.User;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.repository.query.Param;public interface UserRepository extends JpaRepository<User,Integer> {   Page<User> findByName(@Param("name") String name, Pageable pageable);
}

(7)这个时候我们不用新建 Service 也不用新建 Controller,我们直接启动此 application。

控制台输出内容如下:

这时我们发现 Spring Data Rest 通过 RepositoryRestHandlerMapping 自动创建了很多 REST 风格的 API。

(8)直接调用 API 访问,代码如下:

1. {repository}默认是@Entity的name。2. {search}默认是**Repository中的自定义的方法。
\#curl http://127.0.0.1:8070/apiResponse Headers200 success

date: Tue, 20 Feb 2018 14:34:14 GMT
transfer-encoding: chunked
content-type: application/hal+json;charset=UTF-8Response Body
{  "_links": {    "userAddresses": {      "href": "http://127.0.0.1:8070/api/userAddresses{?page,size,sort}",      "templated": true
    },    "users": {      "href": "http://127.0.0.1:8070/api/users{?page,size,sort}",      "templated": true
    },    "profile": {      "href": "http://127.0.0.1:8070/api/profile"
    }
  }
}

由于我们集成了 spring-data-rest-hal-browser,所以我们可以通过控制台界面看到下图中的效果。我们可以直接看到 application 里面提供的 API 方法及其返回结果是什么。

\#curl http://127.0.0.1:8070/api/users?page=0Response Headers
200 successdate: Tue, 20 Feb 2018 14:38:41 GMTtransfer-encoding: chunkedcontent-type: application/hal+json;charset=UTF-8Response Body
{  "_embedded": {    "users": [
      {        "name": "jack",        "email": "jack@email.com",        "_links": {          "self": {            "href": "http://127.0.0.1:8070/api/users/1"
          },          "user": {            "href": "http://127.0.0.1:8070/api/users/1"
          },          "userAddressesById": {            "href": "http://127.0.0.1:8070/api/users/1/userAddressesById"
          }
        }
      }
    ]
  },  "_links": {    "self": {      "href": "http://127.0.0.1:8070/api/users{?page,size,sort}",      "templated": true
    },    "profile": {      "href": "http://127.0.0.1:8070/api/profile/users"
    },    "search": {      "href": "http://127.0.0.1:8070/api/users/search"
    }
  },  "page": {    "size": 20,    "totalElements": 1,    "totalPages": 1,    "number": 0
  }
}

图例如下:

我们会发现如果我们用 JPA 和 REST 会如此的方面和快捷,这就是约定大于配置的好处,可以使用很多开源产品。

Repository 资源接口介绍

(1)基本原理

Spring Data REST 的核心功能是导出 Spring Data repositories 的资源。潜在调整的核心组件可以自定义导出工作的方式是存储库接口。假设以下存储库接口:

public interface OrderRepository extends CrudRepository<Order, Long> { }

对于此存储库,Spring Data REST 在 /orders 显示集合资源。它还为 URI 模板 /orders/{id} 下的存储库管理的每个项目公开了一个项目资源。默认情况下,与这些资源交互的 HTTP 方法映射到 CrudRepository 的相应方法。

(2)默认状态码

对于暴露的资源,我们使用一组默认状态代码:

  • 200 OK:适用于纯粹的 GET 请求。
  • 201 Created:针对创建新资源的 POST 和 PUT 请求。
  • 204 No Content:对于 PUT、PATCH 和 DELETE 请求,配置为不返回资源更新的响应体(RepositoryRestConfiguration.returnBodyOnUpdate)。如果配置值设置为包含 PUT 的响应,则将返回 200 OK 进行更新,PUT 将为 PUT 创建的资源返回 201 Created。如果配置值(RepositoryRestConfiguration.returnBodyOnUpdate和RepositoryRestConfiguration.returnBodyCreate)显式设置为 null),则会使用 HTTP Accept 标头的存在来确定响应代码。

(3)支持的 HTTP 方法

项目资源通常支持 GET、PUT、PATCH、DELETE 和 POST。

  • GET:返回单个实体。
  • PATCH:与 PUT 类似,但部分更新资源状态。
  • DELETE:删除暴露的资源。
  • POST:从给定的请求正文创建一个新的实体。

(4)分页排序

Spring Data REST 会识别一些会影响页面大小和起始页码的 URL 参数。如果您扩展PagingAndSortingRepository<T, ID> 并访问所有实体的列表,您将获得前20个实体的链接。要将页面大小设置为任何其他数字,请添加 size 参数,Page 参数。

例如http://127.0.0.1:8070/api/users/search/findByName{?name,page,size,sort}遵循 Spring Data Jpa 的 Page参数。

又如curl -v“http:// localhost:8080 / people / search / nameStartsWith?name = K&sort = name,desc”

Spring Data Rest 定制化

@RepositoryRestResource

改变 \*\*\*Repository 对应的 Path 路径和资源名字。默认情况下,导出器将使用域类的名称来显示您的 CrudRepository。Spring Data REST 还应用 Evo Inflector 来复数这个词。所以存储库定义如下:

public interface UserRepository extends JpaRepository<User,Integer> {}

默认情况下,将会显示在 URL http://localhost:8080/users/ 下面。

如果我们向更改 users 的 path 请添加如下注解:

@RepositoryRestResource(path = "user")public interface UserRepository extends JpaRepository<User,Integer> {}

现在可以通过 URL 访问存储库:http://localhost:8080/user/。

@RepositoryRestResource 详解

@RepositoryRestResource 使用在 Repository 的接口上。如下代码:

@RepositoryRestResource(
      exported = true, //资源是否暴漏,默认true
      path = "users",//资源暴漏的path访问路径,默认实体名字+s
      collectionResourceRel = "userInfo",//资源名字,默认实体名字
      collectionResourceDescription = @Description("用户基本信息资源"),//资源描述
      itemResourceRel = "userDetail",//取资源详情的Item名字
      itemResourceDescription = @Description("用户详情")
) 

使用案例,我们改变默认的 User 资源的信息,代码如下:

@RepositoryRestResource(
      exported = true,
      path = "users",
      collectionResourceRel = "userInfo",
      collectionResourceDescription = @Description("用户资源"),
      itemResourceRel = "userDetail",
      itemResourceDescription = @Description("用户详情")
)public interface UserRepository extends JpaRepository<User,Integer> {}

现在可以通过 URL 访问存储库:http://127.0.0.1:8070/api/user 会得到如下返回结果,注意 user、UserInfo、UserDetail 所在的位置。

{  "_embedded": {    "userInfo": [
      {        "email": "jack@email.com",        "userName": "jack",        "_links": {          "self": {            "href": "http://127.0.0.1:8070/api/user/1"
          },          "userDetail": {            "href": "http://127.0.0.1:8070/api/user/1"
          },          "userAddress": {            "href": "http://127.0.0.1:8070/api/user/1/userAddresses"
          }
        }
      }
    ]
  }
}
@RestResource 改变SearchPath
@RestResource(
      exported = true,//是否暴漏给Search
      path = "findName",//Search后面的path路径
      rel = "names"//资源名字)

其用于\*\*\*Repository 中的方法上和 @Entity 的实体关系上。

例1:作用在 UserRepository 方法上,代码如下:

public interface UserRepository extends JpaRepository<User,Integer> {   @RestResource(
         exported = true,//是否暴漏给Search
         path = "findName",//Search后面的path路径
         rel = "names"//资源名字
   )   Page<User> findByName(@Param("name") String name, Pageable pageable);
}

访问:http://127.0.0.1:8070/api/user/search会得到如下结果(注意:name s和 findName 变化位置)。

{  "_links": {    "names": {      "href": "http://127.0.0.1:8070/api/user/search/findName{?name,page,size,sort}",      "templated": true
    },    "self": {      "href": "http://127.0.0.1:8070/api/user/search"
    }
  }
}

例2:也可以填写在 @Entity 中的关联关系上。

@OneToMany(mappedBy = "userByUserId")@RestResource(path = "userAddresses",rel = "userAddress")public Collection<UserAddress> getUserAddressesById() {   return userAddressesById;
}

可以通过 http://127.0.0.1:8070/api/user/1/userAddresses 访问子资源。

改变返回结果

Spring Data Rest 是利用 Jackson 来处理 JSON 结果的,所以 Jackson 的注解同样在此起作用。

  • Jackson 的 @JsonIgnore 用于阻止 password 字段序列化为 JSON。
  • Jackson 的 @JsonProperty 用于改变 JSON 返回字段的名字。

实例如下:

@Entitypublic class User {   @Basic
   @Column(name = "name", nullable = true, length = 50)   @JsonProperty("userName")   public String getName() {      return name;
   }   @Basic
   @Column(name = "email", nullable = true, length = 200)   @JsonIgnore
   public String getEmail() {      return email;
   }
}
隐藏某些 Repository,Repository 的查询方法或 @Entity 关系字段。

您可能不想要一个存储库,存储库上的查询方法,或者实体导出的一个字段。

请使用 @RestResource,@RepositoryRestResource 注解它们并设置 exported = false。

例如,要跳过Repository:

@RepositoryRestResource(exported = false)interface PersonRepository extends CrudRepository<Person, Long> {}

要跳过查询方法:

@RepositoryRestResource(path = "people", rel = "people")interface PersonRepository extends CrudRepository<Person, Long> {  @RestResource(exported = false)  List<Person> findByName(String name);
}

跳过字段:

@Entitypublic class Person {  @OneToMany
  @RestResource(exported = false)  private Map<String, Profile> profiles;
}
隐藏 Repository 的 CRUD 方法

如果您不想在 CrudRepository 上公开保存或删除方法,则可以使用 @RestResource(exported = false) 设置来覆盖要关闭的方法并将注释放在覆盖版本上。例如,为了防止 HTTP 用户调用 CrudRepository 的删除方法,请覆盖所有这些删除方法,并将注释添加到覆盖方法中。

@RepositoryRestResource(path = "people", rel = "people")interface PersonRepository extends CrudRepository<Person, Long> {  @Override
  @RestResource(exported = false)  void delete(Long id);  @Override
  @RestResource(exported = false)  void delete(Person entity);
}
自定义 JSON 输出

有时在您的应用程序中,您需要提供来自特定实体的其他资源的链接。例如,Customer 响应可能会丰富与当前购物车的链接,或链接以管理与该实体相关的资源。Spring Data REST 提供与 Spring HATEOAS 的集成,并为用户提供一个扩展的钩,用来更改客户端的资源的表示格式。

Spring HATEOAS 定义了一个用于处理实体的 ResourceProcessor<> 接口。类型为ResourceProcessor<Resource<T>>的所有 bean 将自动由 Spring Data REST 导出器拾取,并在序列化类型为 T 的实体时触发。

例如,要为 Person 实体定义一个处理器,请向您的 “ApplicationContext” 添加一个 @Bean,如下所示:

@Beanpublic ResourceProcessor<Resource<Person>> personProcessor() {   return new ResourceProcessor<Resource<Person>>() {     @Override
     public Resource<Person> process(Resource<Person> resource) {       //可以扩展很多
      resource.add(new Link("http://localhost:8080/people", "added-link"));      return resource;
     }
   };
}

Spring Boot 2.0 加载其原理

通过我们前面的快速开始,我们大概知道了如何配置 Spring Data Rest,我们来解刨一下它在 Spring Boot 2.0 下是如何工作的。

Gradle 我们引入 compile('org.springframework.boot:spring-boot-starter-data-rest:2.0.0.RC1'),它会帮我们引入 Spring boot 2.0 和 Spring Boot AutoConfigure2.0。 AutoConfigure 所在的 jar 包下面,我们可以找到 spring.factories 文件,里面有默认会加载的类,我们可以找到org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration

我们打开 RepositoryRestMvcAutoConfiguration 有如下内容:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnMissingBean(RepositoryRestMvcConfiguration.class)
@ConditionalOnClass(RepositoryRestMvcConfiguration.class)
@AutoConfigureAfter({ HttpMessageConvertersAutoConfiguration.class,
      JacksonAutoConfiguration.class })
@EnableConfigurationProperties(RepositoryRestProperties.class)
@Import(RepositoryRestMvcConfiguration.class)
public class RepositoryRestMvcAutoConfiguration {
   @Bean
   public SpringBootRepositoryRestConfigurer springBootRepositoryRestConfigurer() {      return new SpringBootRepositoryRestConfigurer();
   }
}

我们会发现 Spring Boot 帮我们做了自动加载 RepositoryRest 的事情。我们看一下有哪些配置打开 RepositoryRestProperties。

@ConfigurationProperties(prefix = "spring.data.rest")    public class RepositoryRestProperties {    private String basePath;    private Integer defaultPageSize;    private Integer maxPageSize;    private String pageParamName;    private String limitParamName;    private String sortParamName;
}

所以我们可以通过 application.proeroties 中添加 spring.data.rest*** 来配置 Spring Data Rest 的很多默认值。

通过源码我们可以发现@Import(RepositoryRestMvcConfiguration.class)这个类,是 SpringRestMvc 的配置类。

也就是说,如果您有一个现有的 Spring MVC 应用程序,而您希望集成 Spring Data REST,那其实很简单。

您的 Spring MVC 配置(很可能在配置 MVC 资源的地方)的某处会向负责配置 RepositoryRestController 的 JavaConfig 类添加一个 bean 引用。班级名称为org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration

在 Java 中,这样就像:

import org.springframework.context.annotation.Import;import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration;
@Configuration@Import(RepositoryRestMvcConfiguration.class)
public class MyApplicationConfiguration {
  …
}

欢迎大家在读者圈留言,一起讨论碰到的 Java 数据结构问题。

原文发布于微信公众号 - CSDN技术头条(CSDN_Tech)

原文发表时间:2018-03-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

spring 3.2.x + struts2 + mybatis 3.x + logback 整合配置

与前面的一篇mybatis 3.2.7 与 spring mvc 3.x、logback整合 相比,只是web层的MVC前端框架,从spring mvc转换成s...

46450
来自专栏JAVA高级架构

Java面试中常问的Spring方面问题(涵盖七大方向共55道题,含答案)

Spring 配置文件是 XML 文件。该文件主要包含类信息。它描述了这些类是如何配置以及相互引入的。但是,XML 配置文件冗长且更加干净。如果没有正确规划和编...

11530
来自专栏技术墨客

Spring-boot特性(2) 原

在使用Spring-boot时,永远要记住它仅仅是Spring Framework的延伸(或者说整合),其底层还是基于Spring Framework(core...

27820
来自专栏WindCoder

Spring基础小结

Spring是一个开源的轻量级Java SE(Java 标准版本)/Java EE(Java 企业版本)开发应用框架,其目的是用于简化企业级应用程序开发。

7610
来自专栏精讲JAVA

Spring MVC 到底是如何工作的?

这篇文章将深入探讨Spring框架的一部分——Spring Web MVC的强大功能及其内部工作原理。

10630
来自专栏java系列博客

Java面试通关要点汇总集之框架篇参考答案

23240
来自专栏李家的小酒馆

Spring MVC面试整理

Spring MVC执行过程 1. 客户端的请求提交到dispatcherServlet 2. DispatcherServ...

29400
来自专栏SpringBoot 核心技术

第四十八章:SpringBoot2.0新特性 - RabbitMQ信任package设置本章目标SpringBoot 企业级核心技术学习专题构建项目总结

39640
来自专栏JAVA高级架构

Spring 知识点提炼

1. Spring框架的作用 轻量:Spring是轻量级的,基本的版本大小为2MB 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,...

40690
来自专栏我的博客

zend framework项目分组(初级版)

Zend_Controller_Front 控制了Zend_Controller系统的整个工作流。它是前端控制器(FrontController)模型的解释。Z...

29770

扫码关注云+社区

领取腾讯云代金券