上一篇我們介紹了使用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=330522******1, mobile=1863331, rentDateTime=2020-04-22T17:24:46.105897, house=House(id=2, houseNumber=1101, owner=張三, idCard=330521******1))
2020-04-23 17:26:30.035 INFO 38077 — [nio-8080-exec-1] c.e.d.configuration.TenantEventHandler : 刪除對象完成,刪除對象:Tenant(id=1, name=王一, idCard=330522******1, mobile=1863331, rentDateTime=2020-04-22T17:24:46.105897, house=House(id=2, houseNumber=1101, owner=張三, idCard=330521******1))
此時,我們的數據切面處理生效了,除此之外,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
關注筆者公衆號,推送各類原創/優質技術文章 ⬇️