1. 概要

Spring Bootは、Springプラットフォームへの意見の追加であり、設定より規約に重点を置いています。最小限の労力で開始し、スタンドアロンの本番環境グレードのアプリケーションを作成するのに非常に役立ちます。

このチュートリアルは、Boot の開始点です。つまり、基本的なWebアプリケーションを簡単に開始する方法です。

いくつかのコア構成、フロントエンド、迅速なデータ操作、および例外処理について説明します。

2. 設定

まず、 Spring Initializr を使用して、プロジェクトのベースを生成しましょう。

生成されたプロジェクトは、ブートの親に依存しています。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath />
</parent>

初期の依存関係は非常に単純になります。

<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>
</dependency>

3. アプリケーション構成

次に、アプリケーション用に単純なmainクラスを構成します。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

プライマリアプリケーション構成クラスとして@SpringBootApplicationを使用していることに注目してください。舞台裏では、 @Configuration @EnableAutoConfiguration、と同等です。 @ComponentScanを一緒に。

最後に、単純な application.properties ファイルを定義します。このファイルには、現時点では1つのプロパティしかありません。

server.port=8081

server.port は、サーバーポートをデフォルトの8080から8081に変更します。 もちろん、さらに多くのSpringBootプロパティが利用可能です

4. シンプルなMVCビュー

次に、Thymeleafを使用して単純なフロントエンドを追加しましょう。

まず、spring-boot-starter-thymeleaf依存関係をpom.xmlに追加する必要があります。

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

これにより、デフォルトでThymeleafが有効になります。 追加の構成は必要ありません。

これで、application.propertiesで構成できます。

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true 
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.application.name=Bootstrap Spring Boot

次に、簡単なコントローラーとウェルカムメッセージ付きの基本的なホームページを定義します。

@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;

    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}

最後に、home.htmlを次に示します。

<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>

プロパティで定義したプロパティをどのように使用し、それを挿入してホームページに表示できるかに注意してください。

5. 安全

次に、最初にセキュリティスターターを含めて、アプリケーションにセキュリティを追加しましょう。

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

ここまでで、パターンに気付くことができます。ほとんどのSpringライブラリは、単純なブートスターターを使用してプロジェクトに簡単にインポートできます。

spring -boot-starter-security の依存関係がアプリケーションのクラスパスにあると、httpBasicまたはformLoginのいずれかを使用して、すべてのエンドポイントがデフォルトで保護されます。 Springセキュリティのコンテンツネゴシエーション戦略に基づいています。

That’s why, if we have the starter on the classpath, we should usually define our own custom Security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and()
            .csrf()
            .disable();
        return http.build();
    }
}

この例では、すべてのエンドポイントへの無制限のアクセスを許可しています。

もちろん、Spring Securityは広範なトピックであり、数行の構成で簡単にカバーすることはできません。 したがって、トピックをより深く読むことを強くお勧めします。

6. 単純な永続性

単純なBookエンティティであるデータモデルを定義することから始めましょう。

@Entity
public class Book {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(nullable = false, unique = true)
    private String title;

    @Column(nullable = false)
    private String author;
}

とそのリポジトリ、ここでSpringDataをうまく利用しています:

public interface BookRepository extends CrudRepository<Book, Long> {
    List<Book> findByTitle(String title);
}

最後に、もちろん、新しい永続層を構成する必要があります。

@EnableJpaRepositories("com.baeldung.persistence.repo") 
@EntityScan("com.baeldung.persistence.model")
@SpringBootApplication 
public class Application {
   ...
}

以下を使用していることに注意してください。

  • @EnableJpaRepositories は、指定されたパッケージのリポジトリをスキャンします
  • @EntityScanJPAエンティティを取得します

簡単にするために、ここではH2インメモリデータベースを使用しています。 これは、プロジェクトの実行時に外部の依存関係がないようにするためです。

H2依存関係を含めると、 Spring Bootはそれを自動検出し、データソースプロパティ以外の追加の構成を必要とせずに永続性を設定します。

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=

もちろん、セキュリティと同様に、永続性はここでのこの基本セットよりも幅広いトピックであり、確実にさらに調査するものです。

7. Webとコントローラー

次に、Web層を見てみましょう。 そして、簡単なコントローラーBookControllerをセットアップすることから始めます。

いくつかの簡単な検証を使用して、Bookリソースを公開する基本的なCRUD操作を実装します。

@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public Iterable findAll() {
        return bookRepository.findAll();
    }

    @GetMapping("/title/{bookTitle}")
    public List findByTitle(@PathVariable String bookTitle) {
        return bookRepository.findByTitle(bookTitle);
    }

    @GetMapping("/{id}")
    public Book findOne(@PathVariable Long id) {
        return bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book create(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
        bookRepository.deleteById(id);
    }

    @PutMapping("/{id}")
    public Book updateBook(@RequestBody Book book, @PathVariable Long id) {
        if (book.getId() != id) {
          throw new BookIdMismatchException();
        }
        bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
        return bookRepository.save(book);
    }
}

アプリケーションのこの側面がAPIであるため、ここでは@ RestController アノテーションを使用しました。これは、@ResponseBodyとともに@Controllerと同等です。各メソッドが返されたリソースをHTTP応答にマーシャリングするようにします。

ここでは、Bookエンティティを外部リソースとして公開していることに注意してください。 この単純なアプリケーションでは問題ありませんが、実際のアプリケーションでは、おそらくこれら2つの概念を分離する必要があります。

8. エラー処理

コアアプリケーションの準備ができたので、@ControllerAdviceを使用した単純な集中型エラー処理メカニズムに焦点を当てましょう。

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ BookNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, "Book not found", 
          new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler({ BookIdMismatchException.class, 
      ConstraintViolationException.class, 
      DataIntegrityViolationException.class })
    public ResponseEntity<Object> handleBadRequest(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, ex.getLocalizedMessage(), 
          new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
}

ここで処理している標準の例外に加えて、カスタム例外BookNotFoundExceptionも使用しています。

public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    // ...
}

これにより、このグローバル例外処理メカニズムで何が可能かがわかります。 完全な実装を確認するには、詳細なチュートリアルをご覧ください。

Spring Bootは、デフォルトで /errorマッピングも提供することに注意してください。 単純なerror.htmlを作成することで、ビューをカスタマイズできます。

<html lang="en">
<head><title>Error Occurred</title></head>
<body>
    <h1>Error Occurred!</h1>    
    <b>[<span th:text="${status}">status</span>]
        <span th:text="${error}">error</span>
    </b>
    <p th:text="${message}">message</p>
</body>
</html>

Bootの他のほとんどの側面と同様に、単純なプロパティでそれを制御できます。

server.error.path=/error2

9. テスト

最後に、新しいBooksAPIをテストしてみましょう。

@SpringBootTest を使用して、アプリケーションコンテキストを読み込み、アプリの実行時にエラーがないことを確認できます。

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringContextTest {

    @Test
    public void contextLoads() {
    }
}

次に、 REST Assured を使用して、作成したAPIへの呼び出しを検証するJUnitテストを追加しましょう。

まず、rest-assured依存関係を追加します。

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

そして今、私たちはテストを追加することができます:

public class SpringBootBootstrapLiveTest {

    private static final String API_ROOT
      = "http://localhost:8081/api/books";

    private Book createRandomBook() {
        Book book = new Book();
        book.setTitle(randomAlphabetic(10));
        book.setAuthor(randomAlphabetic(15));
        return book;
    }

    private String createBookAsUri(Book book) {
        Response response = RestAssured.given()
          .contentType(MediaType.APPLICATION_JSON_VALUE)
          .body(book)
          .post(API_ROOT);
        return API_ROOT + "/" + response.jsonPath().get("id");
    }
}

まず、さまざまな方法を使用して本を探すことができます。

@Test
public void whenGetAllBooks_thenOK() {
    Response response = RestAssured.get(API_ROOT);
 
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
}

@Test
public void whenGetBooksByTitle_thenOK() {
    Book book = createRandomBook();
    createBookAsUri(book);
    Response response = RestAssured.get(
      API_ROOT + "/title/" + book.getTitle());
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertTrue(response.as(List.class)
      .size() > 0);
}
@Test
public void whenGetCreatedBookById_thenOK() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    Response response = RestAssured.get(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertEquals(book.getTitle(), response.jsonPath()
      .get("title"));
}

@Test
public void whenGetNotExistBookById_thenNotFound() {
    Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));
    
    assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}

次に、新しい本の作成をテストします。

@Test
public void whenCreateNewBook_thenCreated() {
    Book book = createRandomBook();
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .post(API_ROOT);
    
    assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());
}

@Test
public void whenInvalidBook_thenError() {
    Book book = createRandomBook();
    book.setAuthor(null);
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .post(API_ROOT);
    
    assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());
}

次に、既存の本を更新します。

@Test
public void whenUpdateCreatedBook_thenUpdated() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    book.setId(Long.parseLong(location.split("api/books/")[1]));
    book.setAuthor("newAuthor");
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .put(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    response = RestAssured.get(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertEquals("newAuthor", response.jsonPath()
      .get("author"));
}

そして、本を削除することができます:

@Test
public void whenDeleteCreatedBook_thenOk() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    Response response = RestAssured.delete(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    response = RestAssured.get(location);
    assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}

10. 結論

これは、SpringBootの簡単で包括的な紹介でした。

もちろん、ここではほとんど表面を傷つけませんでした。 このフレームワークには、1つの紹介記事でカバーできる以上のことがたくさんあります。

そのため、サイトでBootをカバーする記事は1つだけではありません。

いつものように、ここでの例の完全なソースコードは、GitHub上のです。