1. 概要

ユーザー入力の検証に関しては、 Spring Boot は、この一般的でありながら重要なタスクを、箱から出してすぐに強力にサポートします。

Spring Bootはカスタムバリデーターとのシームレスな統合をサポートしていますが、検証を実行するためのデファクトスタンダードは、Bean検証フレームワークのリファレンス実装であるHibernateValidatorです。

このチュートリアルでは、Spring Bootでドメインオブジェクトを検証する方法を見ていきます。

2. Mavenの依存関係

この場合、基本的なRESTコントローラーを構築することにより、Spring Bootでドメインオブジェクトを検証する方法を学習します。

コントローラは最初にドメインオブジェクトを取得し、次にHibernate Validatorで検証し、最後にメモリ内のH2データベースに永続化します。

プロジェクトの依存関係はかなり標準的です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> 
<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId>
    <version>1.4.197</version> 
    <scope>runtime</scope>
</dependency>

上記のように、 spring-boot-starter-webpom.xmlファイルに含めました。これは、RESTコントローラーの作成に必要になるためです。 さらに、MavenCentralのspring-boot-starter-jpaH2データベースの最新バージョンを確認してください。

Boot 2.3以降では、spring-boot-starter-validation依存関係も明示的に追加する必要があります。

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

3. 単純なドメインクラス

プロジェクトの依存関係がすでに整っているので、次に、サンプルのJPAエンティティークラスを定義する必要があります。このクラスの役割は、ユーザーのモデリングのみです。

このクラスを見てみましょう:

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @NotBlank(message = "Name is mandatory")
    private String name;
    
    @NotBlank(message = "Email is mandatory")
    private String email;
    
    // standard constructors / setters / getters / toString
        
}

User エンティティクラスの実装は確かにかなり貧血ですが、BeanValidationの制約を使用してnameemailを制約する方法を簡単に示しています。田畑。

簡単にするために、@NotBlank制約のみを使用してターゲットフィールドを制約しました。 また、message属性でエラーメッセージを指定しました。

したがって、Spring Bootがクラスインスタンスを検証するとき、制約付きフィールドはnullであってはならず、それらのトリミングされた長さはゼロより大きくなければなりません。

さらに、 Bean Validation 他にも多くの便利な制約を提供します @NotBlank。 これにより、制約されたクラスにさまざまな検証ルールを適用して組み合わせることができます。 詳細については、公式のBean検証ドキュメントをお読みください。

Spring Data JPA を使用してユーザーをインメモリH2データベースに保存するため、Userオブジェクトで基本的なCRUD機能を使用するための単純なリポジトリインターフェイスも定義する必要があります。

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

4. RESTコントローラーの実装

もちろん、Userオブジェクトの制約付きフィールドに割り当てられた値を取得できるようにするレイヤーを実装する必要があります。

したがって、検証結果に応じて、それらを検証し、さらにいくつかのタスクを実行できます。

Spring Bootは、RESTコントローラーの実装により、この一見複雑なプロセスを非常に単純なにします。

RESTコントローラーの実装を見てみましょう。

@RestController
public class UserController {

    @PostMapping("/users")
    ResponseEntity<String> addUser(@Valid @RequestBody User user) {
        // persisting the user
        return ResponseEntity.ok("User is valid");
    }
    
    // standard constructors / other methods
    
}

Spring RESTコンテキストでは、 addUser()メソッドの実装はかなり標準的です。

もちろん、最も関連性の高い部分は、@Validアノテーションの使用です。

Spring Bootは、 @Valid で注釈が付けられた引数を見つけると、デフォルトのJSR 380実装(Hibernate Validator)を自動的にブートストラップし、引数を検証します。

ターゲット引数が検証に合格しなかった場合、SpringBootはMethodArgumentNotValidException例外をスローします。

5. @ExceptionHandlerアノテーション

Spring BootがUserオブジェクトをaddUser()メソッドに自動的に渡して検証するのは非常に便利ですが、このプロセスに欠けている側面は、検証結果の処理方法です。

@ExceptionHandler アノテーションを使用すると、1つのメソッドで指定されたタイプの例外を処理できます。

したがって、検証エラーの処理に使用できます。

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

MethodArgumentNotValidException例外を処理する例外として指定しました。 したがって、指定されたUserオブジェクトが無効な場合、SpringBootはこのメソッドを呼び出します。

このメソッドは、無効な各フィールドの名前と検証後のエラーメッセージを Mapに格納します。次に、 Map をJSON表現としてクライアントに送り返し、さらに処理します。

簡単に言うと、RESTコントローラーを使用すると、さまざまなエンドポイントへのリクエストを簡単に処理し、 User オブジェクトを検証し、JSON形式でレスポンスを送信できます。

この設計は、 Thymeleaf などのテンプレートエンジンから、 Angular などのフル機能のJavaScriptフレームワークに至るまで、いくつかのWeb層を介してコントローラーの応答を処理するのに十分な柔軟性を備えています。

6. RESTコントローラーのテスト

統合テストを使用して、RESTコントローラーの機能を簡単にテストできます。

UserRepository インターフェイスの実装、 UserController インスタンス、およびMockMvcオブジェクトのモック/自動配線を開始しましょう。

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

    @MockBean
    private UserRepository userRepository;
    
    @Autowired
    UserController userController;

    @Autowired
    private MockMvc mockMvc;

    //...
    
}

Webレイヤーのみをテストしているため、@WebMvcTestアノテーションを使用します。 MockMvcRequestBuildersおよびMockMvcResultMatchersクラスによって実装された一連の静的メソッドを使用して、要求と応答を簡単にテストできます。

次に、リクエスト本文に渡された有効な Userオブジェクトを使用してaddUser()メソッドをテストしましょう。

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
    MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
    String user = "{\"name\": \"bob\", \"email\" : \"[email protected]\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content()
        .contentType(textPlainUtf8));
}

@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
    String user = "{\"name\": \"\", \"email\" : \"[email protected]\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isBadRequest())
      .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
      .andExpect(MockMvcResultMatchers.content()
        .contentType(MediaType.APPLICATION_JSON_UTF8));
    }
}

さらに、Postmanなどの無料のAPIライフサイクルテストアプリケーションを使用してRESTコントローラーAPIをテストできます。

7. サンプルアプリケーションの実行

最後に、標準の main()メソッドを使用してサンプルプロジェクトを実行できます。

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("Bob", "[email protected]");
            User user2 = new User("Jenny", "[email protected]");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

予想どおり、コンソールにいくつかのUserオブジェクトが印刷されているはずです。

有効なUserオブジェクトを使用してhttp:// localhost:8080 / users エンドポイントにPOSTリクエストを送信すると、String「Userisvalid」が返されます。

同様に、nameおよびemail値のないUserオブジェクトを含むPOST要求は、次の応答を返します。

{
  "name":"Name is mandatory",
  "email":"Email is mandatory"
}

8. 結論

この記事では、 SpringBootで検証を実行するための基本を学びました。

いつものように、この記事に示されているすべての例は、GitHubで入手できます。