上一篇我们介绍了使用 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, 传递要触发此校验器的事件和校验器的实例. 以下示例显示了如何执行此操作:
- public class SaveTenantValidator implements Validator {
- @Override
- public boolean supports(Class<?> clazz) {
- return Tenant.class.isAssignableFrom(clazz);
- }
- @Override
- public void validate(Object target, Errors errors) {
- Tenant tenant = (Tenant) target;
- if (StringUtils.isEmpty(tenant.getMobile())) {
- errors.rejectValue("mobile", "1001", "手机号不能为空");
- }
- }
- }
如上, 我们声明了一个 Validator 类, 作为对手机号校验的 Validator. 接着我们通过以下代码注册我们的校验器.
- @Component
- public class SpringDataRestCustomization implements RepositoryRestConfigurer {
- @Override
- public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
- validatingListener.addValidator("beforeCreate", new SaveTenantValidator());
- }
- }
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 响应处理程序, 来实现这一目标.
- @RepositoryRestController
- public class TenantController {
- private final TenantRepository tenantRepository;
- @Resource
- private RepositoryEntityLinks entityLinks;
- @Autowired
- public TenantController(TenantRepository tenantRepository) {
- this.tenantRepository = tenantRepository;
- }
- @GetMapping(value = "/tenantPath/search/mobile")
- public ResponseEntity<?> getByMobile(@RequestParam String mobile) {
- Tenant tenant = tenantRepository.findFirstByMobile(mobile);
- EntityModel<Tenant> resource = new EntityModel<>(tenant);
- resource.add(linkTo(methodOn(TenantController.class).getByMobile(mobile)).withSelfRel());
- resource.add(entityLinks.linkToSearchResource(Tenant.class, LinkRelation.of("findAllByIdCardContaining")));
- return ResponseEntity.ok(resource);
- }
- }
如上代码, 我们使用了 @RepositoryRestController 注解来创建了一个控制器, 并定义了一个路径的请求, 以此我们覆盖了之前 Spring Data REST 自动为我们提供的相同路径的接口. 我们给接口的响应增加了两个链接.
注意: 上述代码中用到了 Spring HATEOAS 的库, 所以我们需要增加 Spring HATEOAS 的依赖.
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-hateoas</artifactId>
- </dependency>
- <dependency>
- <groupId>org.atteo</groupId>
- <artifactId>evo-inflector</artifactId>
- </dependency>
现在我们访问 http://localhost:8080/tenantPath/search/mobile?mobile=186****3331, 看到响应结果:
- {
- "name": "王一",
- "mobile": "186****3331",
- "rentDateTime": "2020-04-22 17:48:40",
- "_links": {
- "self": {
- "href": "http://localhost:8080/tenantPath/search/mobile?mobile=186****3331"
- },
- "findAllByIdCardContaining": {
- "href": "http://localhost:8080/tenantPath/search/findAllByIdCardContaining{?idCard,page,size,sort,projection}",
- "templated": true
- }
- }
- }
可以看到, links 属性中链接已经变成我们指定的链接了.
异常统一处理
Spring Data REST 中并没有提供异常处理的功能, 但是我们可以使用 Springboot 中自带的异常处理功能来实现我们的要求.
- @Slf4j
- @ControllerAdvice
- public class ExceptionTranslator {
- @ExceptionHandler
- public ResponseEntity<Object> handleEmailAlreadyUsedException(NullPointerException ex, NativewebRequest request) {
- log.info("遇到空指针");
- return ResponseEntity.ok(List.of("拦截到空指针异常"));
- }
- }
如上, 我们声明了一个异常处理器. 接下来我人为制造一个错误.
- @RepositoryRestController
- public class TenantController {
- private final TenantRepository tenantRepository;
- @Autowired
- public TenantController(TenantRepository tenantRepository) {
- this.tenantRepository = tenantRepository;
- }
- @GetMapping(value = "/tenantPath/search/mobile")
- public ResponseEntity<?> getByMobile(@RequestParam String mobile) {
- if (1 == 1) {
- throw new NullPointerException();
- }
- Tenant tenant = tenantRepository.findFirstByMobile(mobile);
- EntityModel<Tenant> resource = new EntityModel<>(tenant);
- resource.add(linkTo(methodOn(TenantController.class).getByMobile(mobile)).withSelfRel());
- return ResponseEntity.ok(resource);
- }
- }
此时, 我们请求此接口:
- [
- "拦截到空指针异常"
- ]
可以看到, 我们的异常被我们的异常处理器拦截掉了.
数据切面处理
Spring Data REST 提供了类似的 Aop 切面操作, 虽然不能和 Spring 的原生 aop 相比, 但是其简洁性也能满足需求. Spring Data REST 提供的是基于事件的切面. 如下我们声明了一个切面.
- @Component
- @Slf4j
- @RepositoryEventHandler
- public class TenantEventHandler {
- @HandleBeforeDelete
- protected void onBeforeDelete(Tenant entity) {
- log.info("现在要开始删除操作了, 删除对象:{}", entity);
- }
- @HandleAfterDelete
- protected void onAfterDelete(Tenant entity) {
- log.info("删除对象完成, 删除对象:{}", entity);
- }
- }
如上, 我们声明了一个切面, 我们可以在删除操作之前和之后进行额外的逻辑处理, 示例中很简单, 我们使用日志记录事件的发生.
此时, 我们访问项目的删除接口 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://www.cnblogs.com/dongxishaonian/p/12762552.html