blog

Spring Boot Scaffoldingをゼロから構築する:汎用的な機能を追加する

1序文今日は河野Spring Bootの足場を構築し始め、最初はSpring MVCを統合し、日々の開発のニーズを満たすためにカスタマイズされ、カスタマイズの厳格な要件のいくつかを行うには、最初に、追...

Mar 12, 2020 · 5 min. read
シェア

序文

Gitee: gitee.com/felord/kono

GitHub: github.com/NotFound403...

統一されたリターンボディ

統一されたリターンデータの開発は非常に重要です。フロントエンドが統一的に処理することが便利です。通常、次のような構造で設計されます:

{
 "code": 200,
 "data": {
 "name": "felord",
 "age": 18
 },
 "msg": "",
 "identifier": ""
}
  • コード ビジネスステータスコード、デザインはhttpステータスコードと異なる必要があります。
  • data フロントエンドのディスプレイに返されるデータをロードするために使用されるデータキャリア。
  • msg プロンプトメッセージ。フロントエンド呼び出しの後に返されるプロンプトメッセージに使用します。
  • identifier 予約済みの識別子ビット。

上記のいくつかの定義に基づいて、統一されたリターン・ボディ・オブジェクト RestBody が宣言され、定義を容易にするためにいくつかの静的メソッドが宣言されています。

package.felord.kono.advice;
import lombok.Data;
import java.io.Serializable;
/**
 * 
 * @since 22:32 2019-04-02
 */
@Data
public class RestBody<T> implements Rest<T>, Serializable {
 private static final long serialVersionUID = -7616216747521482608L;
 private int code = 200;
 private T data;
 private String msg = "";
 private String identifier = "";
 
 public static Rest<?> ok() {
 return new RestBody<>();
 }
 public static Rest<?> ok(String msg) {
 Rest<?> restBody = new RestBody<>();
 restBody.setMsg(msg);
 return restBody;
 }
 public static <T> Rest<T> okData(T data) {
 Rest<T> restBody = new RestBody<>();
 restBody.setData(data);
 return restBody;
 }
 public static <T> Rest<T> okData(T data, String msg) {
 Rest<T> restBody = new RestBody<>();
 restBody.setData(data);
 restBody.setMsg(msg);
 return restBody;
 }
 public static <T> Rest<T> build(int code, T data, String msg, String identifier) {
 Rest<T> restBody = new RestBody<>();
 restBody.setCode(code);
 restBody.setData(data);
 restBody.setMsg(msg);
 restBody.setIdentifier(identifier);
 return restBody;
 }
 public static Rest<?> failure(String msg, String identifier) {
 Rest<?> restBody = new RestBody<>();
 restBody.setMsg(msg);
 restBody.setIdentifier(identifier);
 return restBody;
 }
 public static Rest<?> failure(int httpStatus, String msg ) {
 Rest<?> restBody = new RestBody< >();
 restBody.setCode(httpStatus);
 restBody.setMsg(msg);
 restBody.setIdentifier("-9999");
 return restBody;
 }
 public static <T> Rest<T> failureData(T data, String msg, String identifier) {
 Rest<T> restBody = new RestBody<>();
 restBody.setIdentifier(identifier);
 restBody.setData(data);
 restBody.setMsg(msg);
 return restBody;
 }
 @Override
 public String toString() {
 return "{" +
 "code:" + code +
 ", data:" + data +
 ", msg:" + msg +
 ", identifier:" + identifier +
 '}';
 }
}

@RestControllerAdviceResponseBodyAdvice<T>しかし、毎回明示的にリターンボディを宣言するのはあまりエレガントではないので、控えめな方法でこの機能を実装したいでしょう。Spring Frameworkは、プロジェクトの@RestControllerタグが付けられたコントロールクラスのレスポンスボディでポストカット通知を処理するために、andの助けを借りてこの機能を提供します。

/**
 * 統一リターンボディラッパー
 *
 * 
 * @since 14:58
 **/
@RestControllerAdvice
public class RestBodyAdvice implements ResponseBodyAdvice<Object> {
 @Override
 public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
 return true;
 }
 @Override
 public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
 // 空の場合は、データのない空のリターンボディを返す 
 if (o == null) {
 return RestBody.ok();
 }
 // RestBodyの親クラスが返り値の親型である場合、直接RestBodyを返す。 
 // 利便性は、直接RestBodyを返すインターフェイスメソッドにすることができる
 if (Rest.class.isAssignableFrom(o.getClass())) {
 return o;
 }
 // 統一されたリターンボディのカプセル化
 return RestBody.okData(o);
 }
}

インターフェイスがエンティティクラスを返すとき、それは自動的に統一されたリターンボディRestBodyにカプセル化されます。

ResponseBodyAdviceがある以上、RequestBodyAdviceもあるわけで、これは前処理のために入ってくるもので、後で何か役に立つかもしれません。

統一された例外処理

/**
 * 統一された例外処理
 *
 * 
 * @since 13 :31 2019-04-11
 */
@Slf4j
@RestControllerAdvice
public class ApiExceptionHandleAdvice {
 @ExceptionHandler(BindException.class)
 public Rest<?> handle(HttpServletRequest request, BindException e) {
 logger(request, e);
 List<ObjectError> allErrors = e.getAllErrors();
 ObjectError objectError = allErrors.get(0);
 return RestBody.failure(700, objectError.getDefaultMessage());
 }
 @ExceptionHandler(MethodArgumentNotValidException.class)
 public Rest<?> handle(HttpServletRequest request, MethodArgumentNotValidException e) {
 logger(request, e);
 List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
 ObjectError objectError = allErrors.get(0);
 return RestBody.failure(700, objectError.getDefaultMessage());
 }
 @ExceptionHandler(ConstraintViolationException.class)
 public Rest<?> handle(HttpServletRequest request, ConstraintViolationException e) {
 logger(request, e);
 Optional<ConstraintViolation<?>> first = e.getConstraintViolations().stream().findFirst();
 String message = first.isPresent() ? first.get().getMessage() : "";
 return RestBody.failure(700, message);
 }
 @ExceptionHandler(Exception.class)
 public Rest<?> handle(HttpServletRequest request, Exception e) {
 logger(request, e);
 return RestBody.failure(700, e.getMessage());
 }
 private void logger(HttpServletRequest request, Exception e) {
 String contentType = request.getHeader("Content-Type");
 log.error("統一された例外処理uri: {} content-type: {} exception: {}", request.getRequestURI(), contentType, e.toString());
 }
}

簡易型変換

統合は非常に簡単で、コンパイル時にしか動作しないので、参照時にスコープをコンパイルに設定し、依存関係管理のためにkono-dependenciesに追加するのがベストです:

 @Autowired
 MockMvc mockMvc;
 @Autowired
 BeanMapping beanMapping;
 /**
 * グローバル例外処理をテストする.
 *
 * @throws Exception the exception
 * @see UserController#getUserInfo()
 */
 @Test
 void testGlobalExceptionHandler() throws Exception {
 String rtnJsonStr = "{
" +
 " "code": 700,
" +
 " "data": null,
" +
 " "msg": "test global exception handler",
" +
 " "identifier": "-9999"
" +
 "}";
 mockMvc.perform(MockMvcRequestBuilders.get("/user/get"))
 .andExpect(MockMvcResultMatchers.content()
 .json(rtnJsonStr))
 .andDo(MockMvcResultHandlers.print());
 }
 /**
 * テストユニファイドリターンボディ.
 *
 * @throws Exception the exception
 * @see UserController#getUserVO()
 */
 @Test
 void testUnifiedReturnStruct() throws Exception {
// "{"code":200,"data":{"name":"felord","age":18,"addTime":"2020-07-30T13:08:53.201"},"msg":"","identifier":""}";
 mockMvc.perform(MockMvcRequestBuilders.get("/user/vo"))
 .andExpect(MockMvcResultMatchers.jsonPath("code", Is.is(200)))
 .andExpect(MockMvcResultMatchers.jsonPath("data.name", Is.is("felord")))
 .andExpect(MockMvcResultMatchers.jsonPath("data.age", Is.is(18)))
 .andExpect(MockMvcResultMatchers.jsonPath("data.addTime", Is.is(notNullValue())))
 .andDo(MockMvcResultHandlers.print());
 }
 /**
 * mapStruct型の変換をテストする.
 *
 * @see BeanMapping
 */
 @Test
 void testMapStruct() {
 UserInfo userInfo = new UserInfo();
 userInfo.setName("felord");
 userInfo.setAge(18);
 UserInfoVO userInfoVO = beanMapping.toUserInfoVo(userInfo);
 Assertions.assertEquals(userInfoVO.getName(), userInfo.getName());
 Assertions.assertNotNull(userInfoVO.getAddTime());
 }


プリコンパイル:


編集


実際には、mapStructはまた、ゲッターとセッターを記述するのに役立ちますが、その複雑な変換を使用しないように、学習コストと保守の難しさを増加させます。

ユニットテスト

上記の機能を統合し、それぞれユニットテストを行い、すべてパスしました。


まとめ

自家製の足場は、最初に統一された戻り値本体統一された例外処理高速な型変換を持って、実際には、パラメータのチェックもサポートされています。その後、データベースを統合する必要があります、一般的に使用されるデータベースアクセス技術は、主にMybatisSpring Data JPAJOOQなどですが、私はあなたが好むどれがわからない?議論するメッセージを残して歓迎します。

Read next