上一篇我们介绍了使用Spring Data REST时的一些高级特性,以及使用代码演示了如何使用这些高级的特性。本文将继续讲解前面我们列出来的七个高级特性中的后四个。至此,这些特性能满足我们大部分的接口开发场景。

需要满足的一些要求:

1.针对字段级别,方法级别,类级别进行限制(禁止某些字段,方法,接口的对外映射)。

2.对数据增删改查的限制(禁止某些请求方法的访问)。

3.能个性化定义请求的路径。

4.对所传参数进行值校验。

5.响应统一处理。

6.异常处理。

7.数据处理的切面。

➡️本文,将演示7个要求中的其余四个要求。


对所传参数进行值校验

对于值校验,Spring 提供了Validator接口,Spring Data REST提供了使用Validator来进行值校验的功能。

首先我们通过实现Validator接口来创建一个校验器,然后在实现RepositoryRestConfigurer或Spring Data REST的RepositoryRestConfigurerAdapter的子类的配置中,重写configureValidatingRepositoryEventListener方法,并在ValidatingRepositoryEventListener上调用addValidator,传递要触发此校验器的事件和校验器的实例。以下示例显示了如何执行此操作:

  1. public class SaveTenantValidator implements Validator {
  2. @Override
  3. public boolean supports(Class<?> clazz) {
  4. return Tenant.class.isAssignableFrom(clazz);
  5. }
  6. @Override
  7. public void validate(Object target, Errors errors) {
  8. Tenant tenant = (Tenant) target;
  9. if (StringUtils.isEmpty(tenant.getMobile())) {
  10. errors.rejectValue("mobile", "1001", "手机号不能为空");
  11. }
  12. }
  13. }

如上,我们声明了一个Validator类,作为对手机号校验的Validator。接着我们通过以下代码注册我们的校验器。

  1. @Component
  2. public class SpringDataRestCustomization implements RepositoryRestConfigurer {
  3. @Override
  4. public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
  5. validatingListener.addValidator("beforeCreate", new SaveTenantValidator());
  6. }
  7. }

validatingListener.addValidator("beforeCreate", new SaveTenantValidator());我们使用validatingListener.addValidator()来注册我们的校验器。该方法传入两个参数,第一个代表着要校验的事件,"beforeCreate"即代表着在插入新纪录之前,对插入数据进行校验。spring Data REST还提供了其他的事件:

  • BeforeCreateEvent
  • AfterCreateEvent
  • BeforeSaveEvent
  • AfterSaveEvent
  • BeforeLinkSaveEvent
  • AfterLinkSaveEvent
  • BeforeDeleteEvent
  • AfterDeleteEvent

我们都可以从字面意思进行理解。

方法中的第二个参数,就是指定我们要注册的校验器,如上代码中,我们对我们刚刚创建的校验器进行注册。

如下为验证效果:


响应统一处理

有时候我们需要对响应结果进行统一处理,比如,我们希望我们的响应结果中包含当前时间的时间戳又或者我们希望我们的HAL格式的响应数据中增加其他的链接。这时候,我们可以通过响应统一处理来完成这种看似重复性的工作。但是Spring Data REST并没有提供现成的功能,不过我们可以通过覆盖Spring Data REST响应处理程序,来实现这一目标。

  1. @RepositoryRestController
  2. public class TenantController {
  3. private final TenantRepository tenantRepository;
  4. @Resource
  5. private RepositoryEntityLinks entityLinks;
  6. @Autowired
  7. public TenantController(TenantRepository tenantRepository) {
  8. this.tenantRepository = tenantRepository;
  9. }
  10. @GetMapping(value = "/tenantPath/search/mobile")
  11. public ResponseEntity<?> getByMobile(@RequestParam String mobile) {
  12. Tenant tenant = tenantRepository.findFirstByMobile(mobile);
  13. EntityModel<Tenant> resource = new EntityModel<>(tenant);
  14. resource.add(linkTo(methodOn(TenantController.class).getByMobile(mobile)).withSelfRel());
  15. resource.add(entityLinks.linkToSearchResource(Tenant.class, LinkRelation.of("findAllByIdCardContaining")));
  16. return ResponseEntity.ok(resource);
  17. }
  18. }

如上代码,我们使用了@RepositoryRestController注解来创建了一个控制器,并定义了一个路径的请求,以此我们覆盖了之前Spring Data REST自动为我们提供的相同路径的接口。我们给接口的响应增加了两个链接。

注意:上述代码中用到了Spring HATEOAS的库,所以我们需要增加Spring HATEOAS的依赖。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-hateoas</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.atteo</groupId>
  7. <artifactId>evo-inflector</artifactId>
  8. </dependency>

现在我们访问http://localhost:8080/tenantPath/search/mobile?mobile=186****3331,看到响应结果:

  1. {
  2. "name": "王一",
  3. "mobile": "186****3331",
  4. "rentDateTime": "2020-04-22 17:48:40",
  5. "_links": {
  6. "self": {
  7. "href": "http://localhost:8080/tenantPath/search/mobile?mobile=186****3331"
  8. },
  9. "findAllByIdCardContaining": {
  10. "href": "http://localhost:8080/tenantPath/search/findAllByIdCardContaining{?idCard,page,size,sort,projection}",
  11. "templated": true
  12. }
  13. }
  14. }

可以看到,links属性中链接已经变成我们指定的链接了。


异常统一处理

Spring Data REST中并没有提供异常处理的功能,但是我们可以使用Springboot中自带的异常处理功能来实现我们的要求。

  1. @Slf4j
  2. @ControllerAdvice
  3. public class ExceptionTranslator {
  4. @ExceptionHandler
  5. public ResponseEntity<Object> handleEmailAlreadyUsedException(NullPointerException ex, NativeWebRequest request) {
  6. log.info("遇到空指针");
  7. return ResponseEntity.ok(List.of("拦截到空指针异常"));
  8. }
  9. }

如上,我们声明了一个异常处理器。接下来我人为制造一个错误。

  1. @RepositoryRestController
  2. public class TenantController {
  3. private final TenantRepository tenantRepository;
  4. @Autowired
  5. public TenantController(TenantRepository tenantRepository) {
  6. this.tenantRepository = tenantRepository;
  7. }
  8. @GetMapping(value = "/tenantPath/search/mobile")
  9. public ResponseEntity<?> getByMobile(@RequestParam String mobile) {
  10. if (1 == 1) {
  11. throw new NullPointerException();
  12. }
  13. Tenant tenant = tenantRepository.findFirstByMobile(mobile);
  14. EntityModel<Tenant> resource = new EntityModel<>(tenant);
  15. resource.add(linkTo(methodOn(TenantController.class).getByMobile(mobile)).withSelfRel());
  16. return ResponseEntity.ok(resource);
  17. }
  18. }

此时,我们请求此接口:

  1. [
  2. "拦截到空指针异常"
  3. ]

可以看到,我们的异常被我们的异常处理器拦截掉了。


数据切面处理

Spring Data REST提供了类似的Aop切面操作,虽然不能和Spring的原生aop相比,但是其简洁性也能满足需求。Spring Data REST提供的是基于事件的切面。如下我们声明了一个切面。

  1. @Component
  2. @Slf4j
  3. @RepositoryEventHandler
  4. public class TenantEventHandler {
  5. @HandleBeforeDelete
  6. protected void onBeforeDelete(Tenant entity) {
  7. log.info("现在要开始删除操作了,删除对象:{}", entity);
  8. }
  9. @HandleAfterDelete
  10. protected void onAfterDelete(Tenant entity) {
  11. log.info("删除对象完成,删除对象:{}", entity);
  12. }
  13. }

如上,我们声明了一个切面,我们可以在删除操作之前和之后进行额外的逻辑处理,示例中很简单,我们使用日志记录事件的发生。

此时,我们访问项目的删除接口curl --location --request DELETE 'http://localhost:8080/tenantPath/1'

我们可以看到控制输出了相应的日志:

2020-04-23 17:26:29.950 INFO 38077 --- [nio-8080-exec-1] c.e.d.configuration.TenantEventHandler : 现在要开始删除操作了,删除对象:Tenant(id=1, name=王一, idCard=3305221, mobile=1863331, rentDateTime=2020-04-22T17:24:46.105897, house=House(id=2, houseNumber=1101, owner=张三, idCard=3305211))

2020-04-23 17:26:30.035 INFO 38077 --- [nio-8080-exec-1] c.e.d.configuration.TenantEventHandler : 删除对象完成,删除对象:Tenant(id=1, name=王一, idCard=3305221, mobile=1863331, rentDateTime=2020-04-22T17:24:46.105897, house=House(id=2, houseNumber=1101, owner=张三, idCard=3305211))

此时,我们的数据切面处理生效了,除此之外,Spring Data REST还提供了如下几个基于事件的切面:


总结

至此,我们先前列出的所有功能特性三篇文章中都有涉及到,通过引入这些功能特性,我们能更加轻松的使用Spring Data REST,并且也能满足我们大部分接口开发的场景。当然三篇文章不能涉及Spring Data REST的全部,有兴趣的小伙伴可以访问Spring Data REST的官方文档查看更多关于Spring Data REST的特性及信息。

本系列文章演示代码地址:https://gitee.com/jeker8chen/spring-data-rest-in-practice.git


关注笔者公众号,推送各类原创/优质技术文章 ⬇️

Spring Data REST不完全指南(三)的更多相关文章

  1. Spring Data REST不完全指南(二)

    上一篇文章介绍了Spring Data REST的功能及特征,以及演示了如何在项目中引入Spring Data REST并简单地启动演示了Spring Data REST项目.在本文中,我们将深入了解 ...

  2. Spring Data REST不完全指南(一)

    简介 Spring Data REST是Spring Data项目的一部分,可轻松在Spring Data存储库上构建超媒体驱动的REST Web服务. Spring Data REST 构建在 Sp ...

  3. ElasticSearch(十三):Spring Data ElasticSearch 的使用(三)——NativeSearchQuery 高亮查询

    在Elasticsearch的实际应用中,经常需要将匹配到的结果字符进行高亮显示,此处采取NativeSearchQuery原生查询的方法,实现查询结果的高亮显示. /** * 高亮查询 */ @Te ...

  4. Spring Data JPA教程, 第二部分: CRUD(翻译)

    我的Spring Data Jpa教程的第一部分描述了,如何配置Spring Data JPA,本博文进一步描述怎样使用Spring Data JPA创建一个简单的CRUD应用.该应用要求如下: pe ...

  5. spring mvc Spring Data Redis RedisTemplate [转]

    http://maven.springframework.org/release/org/springframework/data/spring-data-redis/(spring-data包下载) ...

  6. Spring Data Redis简介以及项目Demo,RedisTemplate和 Serializer详解

    一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis, ...

  7. Spring Data Elasticsearch 用户指南

    https://www.jianshu.com/p/27e1d583aafb 翻译自官方文档英文版,有删减. BioMed Central Development Team version 2.1.3 ...

  8. Spring Data JPA教程, 第三部分: Custom Queries with Query Methods(翻译)

    在本人的Spring Data JPA教程的第二部分描述了如何用Spring Data JPA创建一个简单的CRUD应用,本博文将描述如何在Spring Data JPA中使用query方法创建自定义 ...

  9. spring-boot (三) spring data jpa

    学习文章来自:http://www.ityouknow.com/spring-boot.html spring data jpa介绍 首先了解JPA是什么? JPA(Java Persistence ...

随机推荐

  1. 3 report formats of SFDC

    Choose one of the following report formats using the Format menu of the report builder. Tabular form ...

  2. Java基础语法(9)-面向对象之类的成员

    title: Java基础语法(9)-面向对象之类的成员 blog: CSDN data: Java学习路线及视频 1.面向对象特征--封装 为什么需要封装?封装的作用和含义? 我要用洗衣机,只需要按 ...

  3. jmeter响应乱码(十四)

    方法一: jmeter响应乱码解决方法:在jmeter的bin目录下找到jmeter.propertis这个文件,修改里面的#sampleresult.default.encoding=ISO-885 ...

  4. 【NLP面试QA】激活函数与损失函数

    目录 Sigmoid 函数的优缺点是什么 ReLU的优缺点 什么是交叉熵 为什么分类问题的损失函数为交叉熵而不能是 MSE? 多分类问题中,使用 sigmoid 和 softmax 作为最后一层激活函 ...

  5. coding++ :Layui-form 表单模块

    虽然对layui比较熟悉了,但是今天有时间还是将layui的form表单模块重新看一下. https://www.layui.com/doc/modules/form.html 1):更新渲染 lay ...

  6. Spring中应用的那些设计模式

    设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆. 今天,我们就设计模式的内在价值做一番探讨,并以spring为例进行讲解,只有领略了其设计的思想理念 ...

  7. canvas 实现手机图案解锁

    参考☞: https://www.cnblogs.com/chenyingying0/ 先上效果图: 我是在 vue 里面实现js 文件 ,所以如果需要在vue 里面使用 可以将以下内容import  ...

  8. 《JAVA与模式》之责任链模式 【转载】

    转载自java_my_life的博客 原文地址:http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html 在阎宏博士的&l ...

  9. NCEP CFSR数据下载

    一.简介 CFSR(Climate Forecast SystemReanalysis)再分析资料使用了 GEOS-5(Goddard EarthObserving System)大气模式与资料同化系 ...

  10. 泛型Genericity

    泛型:可以在类或方法中预支地使用未知的类型. 注意: 一般在创建对象时,将未知的类型确定具体的类型.当没有指定泛型时,默认类型为Object类型.           E - Element      ...