知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot 教程 – 構建一個簡單的應用程序

Spring Boot
HongKong
5
02:31 PM · Dec 06 ,2025

1. 概述

Spring Boot 是 Spring 平台的一個帶有指導意見的增強,專注於約定優於配置——對於從最小的努力開始並創建獨立、生產級別的應用程序來説,這一點非常有用。

本教程是 Boot 的一個入門點,換句話説,它提供了一種簡單的方式來入門,使用一個基本的 Web 應用程序。

我們將涵蓋一些核心配置、前端、快速的數據操作以及異常處理。

2. 設置

首先,我們使用 Spring Initializr 生成項目的基礎。

生成的項目依賴於 Boot 父項目:

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

最初的依賴項將會相當簡單:

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

3. 應用配置

接下來,我們將配置一個簡單的 類用於我們的應用程序:

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

請注意,我們正在使用 @SpringBootApplication 作為主要的應用程序配置類。在幕後,這等同於 @Configuration@EnableAutoConfiguration@ComponentScan 的組合。

最後,我們將定義一個簡單的 application.properties 文件,目前只包含一個屬性:

server.port=8081

server.port 更改服務器端口,從默認的 8080 更改為 8081;當然,還有許多其他的 Spring Boot 屬性 可用。

4. 簡單 MVC 視圖

現在我們添加一個簡單的前端,使用 Thymeleaf。

首先,我們需要將 spring-boot-starter-thymeleaf 依賴添加到我們的 pom.xml 中:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>3.5.4</version>
</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;

    @RequestMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}
<p>最後,這是我們的 <em >home.html</em >:</p>
<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>
    <version>3.5.4</version>
</dependency>

現在我們可以注意到一個規律:大多數 Spring 庫都可以通過使用簡單的 Boot 啓動器輕鬆導入到我們的項目中。

一旦 spring-boot-starter-security 依賴項已添加到應用程序的類路徑中,則所有端點默認情況下都已通過使用 httpBasicformLogin(基於 Spring Security 的內容協商策略)進行保護。

因此,如果啓動器已添加到類路徑中,我們通常應定義自己的自定義 Security 配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .authorizeHttpRequests(expressionInterceptUrlRegistry ->
            expressionInterceptUrlRegistry
              .anyRequest()
              .permitAll())
          .csrf(AbstractHttpConfigurer::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;
}

並且使用其存儲庫,充分利用 Spring Data:

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 用於掃描指定的包以查找存儲庫
  • @EntityScan 用於拾取我們的 JPA 實體

為了保持簡單,我們在這裏使用了一個 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

我們將實現基本的 CRUD 操作,以暴露 Book 資源,幷包含一些簡單的驗證。

@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註解——這等同於@Controller與@ResponseBody的組合,從而確保每個方法將返回的資源直接序列化到HTTP響應。

請注意,我們正在將Book實體作為外部資源進行暴露。對於這個簡單的應用程序來説,這沒問題,但在實際應用中,我們可能需要將這兩個概念分開。

8. 錯誤處理

現在核心應用程序已準備就緒,讓我們重點關注使用 <em @ControllerAdvice</em> 實現的簡單集中式錯誤處理機制。

@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. 測試

最後,讓我們測試我們新的 Books API。

我們可以利用 @SpringBootTest 加載應用程序上下文並驗證應用程序運行時沒有錯誤:

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class SpringContextTest {

    @Test
    public void contextLoads() {
    }
}

接下來,讓我們添加一個 JUnit 測試,以驗證我們編寫的 API 調用,並使用 REST Assured。

首先,我們將添加 rest-assured 依賴項:

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

現在我們可以添加測試:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootBootstrapLiveTest {

    @LocalServerPort
    private int port;
    private String API_ROOT;

    @BeforeEach
    public void setUp() {
        API_ROOT = "http://localhost:" + port + "/api/books";
        RestAssured.port = port;
    }

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

    private String createBookAsUri(Book book) {
        final 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. 結論這是一篇快速但全面的 Spring Boot 介紹。

當然,我們在這裏只是打下了基礎。這個框架遠比一篇簡單的介紹文章所能涵蓋的要多。

這就是為什麼我們網站上有不止一篇涵蓋 Spring Boot 的文章的原因。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.