文 by / 林本托

Tips
做一個終身學習的人。


Spring Boot

源代碼:github下的/code01/ch2。

配置 Web 應用程序

在上一章中,我們學習了如何創建一個基本的應用程序模板,並添加了一些基本功能,並建立與數據庫的連接。 在本章中,我們將繼續增強BookPub應用程序,並提供 Web 支持。

在本章,主要包括以下內容:

  • 創建一個基本的 RESTful 風格的應用程序;
  • 創建一個 Spring Data REST 服務;
  • 配置一個自定義的 Servlet 的過濾器;
  • 配置一個自定義的攔截器;
  • 配置一個HttpMessageConverters的轉換器;
  • 配置一個自定義的PropertyEditors編輯器;
  • 配置一個自定義的類型格式化類。

一. 創建一個基本的 RESTful 風格的應用程序

雖然命令行應用程序確實有其用途,但今天的大多數應用程序開發都圍繞着 Web,REST 和數據服務。 我們開始增強 BookPub 應用程序,提供一個基於Web 的 API,以便訪問圖書目錄。

我們繼續使用前一章創建的應用程序框架,其中定義了實體對象和存儲庫服務,並配置了與數據庫的連接。

首先,第一件事情是我們需要在build.gradle 文件中添加新的依賴模塊spring-boot-starter-web,以便獲取基於 web 服務的所需的類庫。具體的代碼片段如下:

dependencies {
  compile("org.springframework.boot:spring-boot-starter-data-jpa")
  compile("org.springframework.boot:spring-boot-starter-jdbc")
  compile("org.springframework.boot:spring-boot-starter-web")
  runtime("com.h2database:h2")
  testCompile("org.springframework.boot:spring-boot-
    starter-test")
}

接下來,創建一個 Spring 控制器,用於處理我們應用程序中獲取目錄數據的 Web 請求。 新建一個包目錄來存放控制器相關的 Java 程序,以便我們的代碼按照適當的目的分組。 在 src/main/java/org/test/bookpub 目錄下創建一個名為 controllers的包目錄。

因為需要暴露圖書的數據,所以,在新建的包下,創建一個控制器類BookController

package org.test.bookpub.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.test.bookpub.entity.Book;
import org.test.bookpub.entity.Reviewer;
import org.test.bookpub.repository.BookRepository;

import java.util.Collections;
import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {
  @Autowired
  private BookRepository bookRepository;

  @RequestMapping(value = "", method = RequestMethod.GET)
  public Iterable<Book> getAllBooks() {
    return bookRepository.findAll();
  }

  @RequestMapping(value = "/{isbn}", method = 
    RequestMethod.GET)
  public Book getBook(@PathVariable String isbn) {
    return bookRepository.findBookByIsbn(isbn);
  }
}

然後, 使用./gradlew clean bootRun.命令啟動應用程序。

最後,當應用程序程序啟動以後,在瀏覽器中輸入:http://localhost:8080/books, 然後會在頁面上显示“[]”,表示目前沒有圖書的數據。

獲取暴露於Web請求的服務的關鍵是@RestController註解。這是一個元註解使用的例子,正如Spring文檔指出的那樣,我們在以前的代碼看到過。 在@RestController中,定義了兩個註解:@Controller和@ResponseBody。 所以我們可以輕鬆給BookController類添加註解,如下所示:

@Controller
@ResponseBody
@RequestMapping("/books")
public class BookController {...}

@Controller是一個類似於@Bean 和@Repository的Spring的元註解,並將已經添加註解的類聲明為 MVC 控制器。

@ResponseBody是一個Spring MVC的註解,指示來自Web請求映射方法的響應,構成HTTP響應主體有效負載的整個內容,這是RESTful應用程序比較典型的使用場景。

二. 創建一個 Spring Data REST 服務

在上一個例子中,我們使用REST控制器來展示我們的BookRepository,通過Web RESTful API訪問後台的數據。 雖然這是數據訪問的一種快捷方便的方式,但它要求我們手動創建一個控制器並定義所有所需操作的映射。 為了最小化代碼,Spring為我們提供了一種更方便的方法:spring-boot-starter-data-rest模塊。 這允許我們簡單地向存儲庫接口添加一個註解,而Spring將做剩下的事情用以將數據暴露給Web。

首先,我們需要在build.gradle文件中添加spring-boot-starter-data-rest模塊。

dependencies {
  ...
 compile("org.springframework.boot:spring-boot-starter-data-rest")
  ...
}

第二步,在src/main/java/org/test/bookpub/repository目錄下新建AuthorRepository接口,代碼如下:

package org.test.bookpub.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;
import org.test.bookpub.entity.Author;

@RepositoryRestResource
public interface AuthorRepository extends PagingAndSortingRepository<Author, Long> {
}

接下來為剩下的實體模型創建對應的接口,

package org.test.bookpub.repository;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.test.bookpub.entity.Publisher;

@RepositoryRestResource
public interface PublisherRepository extends PagingAndSortingRepository<Publisher, Long> {
}
package org.test.bookpub.repository;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.test.bookpub.entity.Reviewer;

@RepositoryRestResource
public interface ReviewerRepository extends PagingAndSortingRepository<Reviewer, Long> {
}

代碼完成后,執行./gradlew clean bootRun。當啟動成功以後,訪問http://localhost:8080/profile/authors,在 Chrome 瀏覽器下,显示如下內容:

{
  "_embedded" : {
    "authors" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/authors{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/authors"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}

從瀏覽器里显示的內容可以看出,我們將獲得比我們編寫BookController控制器更多的信息。 之所以显示了更多的信息,由於我們沒有擴展CrudRepository接口,而是擴展了PagingAndSortingRepository,而它又是CrudRepository的擴展。 這樣做的原因是獲得PagingAndSortingRepository提供的額外好處。 這將添加額外的功能,使用分頁檢索實體,並能夠對它們進行排序。

@RepositoryRestResource註解是可選項,但可以讓我們更好地控製作為Web數據服務的存儲庫的暴露。 例如,如果我們要將URL路徑由“authers”改成“rel”,則可以此註解調整,如下所示:

@RepositoryRestResource(collectionResourceRel = "writers", path = "writers")

修改以後,之前的 url 現在改完“http://localhost:8080/reviewers”。

由於我們在構建依賴項中包含spring-boot-starter-data-rest模塊,我們還獲得spring-hateoas類庫的支持,此庫給我們提供了很好的ALPS元數據,比如_links對象。 這在構建API驅動的UI時可能非常有用,這可以從元數據推導出導航功能並以適合的方式呈現它們。

Tips
關於更多 ALPS 元數據的信息,請參考https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata。

三. 配置一個自定義的 Servlet 的過濾器

在一個真實的Web應用程序中,我們幾乎總是需要為服務請求添加裝飾器或包裝器,用來記錄它們,過濾XSS(跨站腳本攻擊)的非法字符,執行認證等。Spring Boot自動添加OrderedCharacterEncodingFilter和HiddenHttpMethodFilter 兩個過濾器,除此之外,還有其他的過濾器。 讓我們看看Spring Boot如何幫助我們實現這個任務。

在Spring Boot,Spring Web,Spring MVC等的各種框架中,已經有各種不同的servlet過濾器可用,我們所要做的就是將它們作為一個bea定義到配置中。 假設應用程序將在負載平衡器代理之後運行,並且當我們的應用程序實例收到請求時,我們希望將用戶使用的實際請求IP轉換為代理的IP。 幸運的是,Tomcat 8已經為我們提供了一個實現:RemoteIpFilter。 我們需要做的就是將其添加到我們的過濾器鏈中。

根據功能的不同,便於管理和職責清晰,我們需要把不同的類放在不同的包下,我們創建一個WebConfiguration.java 的文件,放在src/main/java/org/test/bookpub 目錄下。

package org.test.bookpub;

import org.apache.catalina.filters.RemoteIpFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
    @Bean
    public RemoteIpFilter remoteIpFilter() {
        return new RemoteIpFilter();
    }
}

第二步,執行./gradlew clean bootRun命令,在啟動中,查看 log,會出現以下信息,表示過濾器已經添加:

o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'remoteIpFilter' to: [/*]

這個功能背後的功能其實很簡單。 我們從單獨的配置類開始,並且將我們的方法用於過濾器bean的檢測。

我們看看主類BookPubApplication,這個類添加了@SpringBootApplication註解,此註解是是一個元註解,它聲明了@ComponentScan等其他註解。@ComponentScan的存在指示Spring Boot將WebConfiguration類檢測為@Configuration註解的標記類,並將其定義添加到應用程序上下文中。所以,我們將在WebConfiguration類中聲明的任何事情就好像我們將它放在BookPubApplication類中是一樣的。

@Bean public RemoteIpFilter remoteIpFilter(){...}聲明為RemoteIpFilter類創建一個Spring bean。當Spring Boot 檢測到javax.servlet.Filter的所有bean時,它將自動將它們添加到過濾器鏈中。所以我們要做的就是,如果要添加更多的過濾器,那就是將它們聲明為@Bean配置。例如,對於更高級的過濾器配置,如果希望特定的過濾器僅適用於特定的URL模式,可以創建一個FilterBistrationBean類型的@Bean配置,並用來配置精確的設置。

四. 配置一個自定義的攔截器

Servlet過濾器是Servlet API的一部分,與Spring完全沒有任何關係,除了自動添加到過濾器鏈中,Spring MVC為我們提供了另一種方式來包裝Web請求:HandlerInterceptor攔截器。根據文檔,HandlerInterceptor就像一個Filter,但是,攔截器不是在嵌套鏈中包含請求,而是在處理請求、處理視圖或在頁面渲染之前,在不同的階段向我們提供了攔截點,對請求進行攔截。到最後,請求已經完成。它不改變有關請求的任何內容,但是如果攔截器邏輯返回false,它允許我們通過拋出異常來停止執行。

與過濾器的情況類似,Spring MVC附帶了一些預定義的HandlerInterceptor。常用的是LocaleChangeInterceptor和ThemeChangeInterceptor。接下來在應用程序中添加LocaleChangeInterceptor,看看它是如何完成的。

添加一個攔截器並不像剛才聲明一個bean那麼簡單。 實際上需要通過實現WebMvcConfigurer接口或重寫WebMvcConfigurationSupport來實現。

第一步,WebConfiguration類繼承WebMvcConfigurerAdapter類。

public class WebConfiguration extends
  WebMvcConfigurerAdapter {…}

接下來,為LocaleChangeInterceptor攔截器增加@Bean註解,

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
  return new LocaleChangeInterceptor();
}

這實際上只會創建攔截 Spring bean,但不會將其添加到請求處理鏈中。 為了實現這一點,我們需要重寫addInterceptors方法,註冊攔截器。

@Override
public void addInterceptors(InterceptorRegistry registry) {
  registry.addInterceptor(localeChangeInterceptor());
}

整個代碼如下:

package org.test.bookpub;

import org.apache.catalina.filters.RemoteIpFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
    @Bean
    public RemoteIpFilter remoteIpFilter() {
        return new RemoteIpFilter();
    }

        @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        return new LocaleChangeInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
          registry.addInterceptor(localeChangeInterceptor());
    }
}

執行./gradlew clean bootRun

應用程序啟動以後,在瀏覽器中輸入http://localhost:8080/books?locale=foo。

這時,瀏覽器報錯:


locale error

後台的錯誤 log 如下:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy

Tips
上面的錯誤不是因為我們輸入了無效的區域設置,而是因為默認語言環境解析策略不允許重置瀏覽器請求的語言環境。出現一個錯誤,實際上是證明了攔截器已經生效了。

當涉及到配置Spring MVC內部組件時,它並不像只是定義一堆bean那樣簡單—— 至少不總是這樣。 這是因為需要向請求提供更精細的MVC組件映射。 為了簡化難度,Spring為我們提供了WebMvcConfigurerAdapter適配器,它是WebMvcConfigurer接口實現,我們可以擴展和覆蓋我們需要的設置。

在配置攔截器的特定情況下,我們可以重寫addInterceptors(InterceptorRegistry registry)方法。 這是一個典型的回調方法,我們給予一個註冊類,以便根據需要註冊多個附加攔截器。 在MVC自動配置階段,Spring Boot就像過濾器一樣檢測到WebMvcConfigurer的實例,並依次調用所有這些回調方法。 這意味着如果需要其他邏輯分離,可以有多個WebMvcConfigurer類的實現。

五. 配置一個HttpMessageConverters轉換器

在構建RESTful Web服務時,我們定義了控制器,資源庫,並在其上面添加了註解,但是從Java實體bean到HTTP數據流輸出沒有任何類型的對象轉換。 實際上,Spring Boot自動配置了HttpMessageConverters轉換器將實體bean對象轉換為使用了Jackson類庫的JSON的格式,將生成的JSON數據寫入HTTP響應輸出流。 當多個轉換器可用時,根據消息對象類和請求的內容類型選擇最適用的轉換器。

HttpMessageConverters的目的是將各種對象類型轉換為相應的HTTP輸出格式。 轉換器可以支持一系列多種數據類型或多種輸出格式,或兩者的組合。 例如,MappingJackson2HttpMessageConverter類可以將任何Java對象轉換為application/json的格式,而ProtobufHttpMessageConverter類只能對com.google.protobuf.Message的實例進行操作,但可以將其作為application/json,application/xml,text/plain或application/xprotobuf格式。 HttpMessageConverters不僅支持寫入HTTP流,還支持將HTTP請求轉換為適當的Java對象。

我們可以通過多種方式配置轉換器。 這一切都取決於你喜歡哪一個,或者想要實現多少控制。

首先,我們在WebConfiguration類中增加ByteArrayHttpMessageConverter,並加上@Bean註解。

@Bean
public 
  ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
    return new ByteArrayHttpMessageConverter();
}

另一種實現方式是重寫WebConfiguration類中的configureMessageConverters方法,首先需要繼承WebMvcConfigurerAdapter類,具體的代碼如下:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new ByteArrayHttpMessageConverter());
}

如果想獲取更多的控制,還可以重寫extendMessageConverters方法。

@Override
public void 
  extendMessageConverters(List<HttpMessageConverter<?>> 
    converters) {
  converters.clear();
  converters.add(new ByteArrayHttpMessageConverter());
}

如上所示,Spring給了我們多種方式來實現同樣的事情,這一切都取決於我們的偏好或具體的實現細節。

我們介紹了將HttpMessageConverter添加到應用程序中的三種不同的方法。 那有什麼區別呢?

將HttpMessageConverter聲明為@Bean是嚮應用程序添加自定義轉換器的最快捷,最簡單的方法。 它類似於我們在前面的例子中添加了Servlet過濾器。 如果Spring檢測到一個HttpMessageConverter類型的bean,它將自動將其添加到列表中。 如果WebConfiguration類沒有繼承WebMvcConfigurerAdapter父類,那麼這是首選方法。

當應用程序需要指定WebMvcConfigurerAdapter的擴展以配置其他的東西,如攔截器,那麼重寫configureMessageConverters方法並將我們的轉換器添加到列表將更為協調一致。可以從Spring Boot 的模塊中添加多個WebMvcConfigurers實例並自動配置,但是不能保證我們的方法可以以任何特定的順序被調用。

如果我們需要做一些更加具體的事情,比如從列表中刪除所有其他轉換器或清除重複的轉換器,這需要重寫extendMessageConverters方法的地方。 所有WebMvcConfigurer被調用到configureMessageConverter方法並且轉換器列表被完全填充后調用此方法。 當然,WebMvcConfigurer的其他一些實例完全可以重寫extendMessageConverters, 但是這樣做的機會並不多。

六. 配置一個自定義的PropertyEditors編輯器

在前面的例子中,我們學習了如何為HTTP請求和響應數據配置轉換器。 還有其他類型的轉換,特別是在將參數動態轉換為各種對象時,例如String類型轉換為Date或Integer。

當我們在控制器中聲明一個映射方法時,Spring使用確切的對象類型來自由定義方法簽名。 這個方式是通過使用PropertyEditor實現的。 PropertyEditor是一個默認概念,定義為JDK的一部分,旨在允許將文本值轉換為給定類型。 它最初用於構建Java Swing / AWT GUI,後來被證明是適合Spring需要將Web參數轉換為方法參數類型的需要。

Spring MVC已經為很多常見類型(如布爾型,貨幣型和類)提供了大量的PropertyEditor實現。 假設我們要創建一個Isbn類對象,並在我們的控制器中使用它,而不是一個純粹的String類型。

首先,我們需要在WebConfiguration類中移除extendMessageConverters方法,因為調用converters.clear()這段代碼會中斷渲染,因為刪除了所有支持的類型轉換器。

然後,定義Isbn類,和對應的IsbnEditor屬性編輯器,以及重寫initBinde方法給我們的BookController類,使用以下內容配置IsbnEditor

public class Isbn {
  private String isbn;

  public Isbn(String isbn) {
    this.isbn = isbn;
  }

  public String getIsbn() {
    return isbn;
  }
}
public class IsbnEditor extends PropertyEditorSupport {
  @Override
  public void setAsText(String text) throws IllegalArgumentException {
      if (StringUtils.hasText(text)) {
        setValue(new Isbn(text.trim()));
      }
      else {
        setValue(null);
      }
    }

  @Override
  public String getAsText() {
    Isbn isbn = (Isbn) getValue();
    if (isbn != null) {
      return isbn.getIsbn();
    }
    else {
      return "";
    }
  }
}

@InitBinder
public void initBinder(WebDataBinder binder) {
  binder.registerCustomEditor(Isbn.class, new IsbnEditor());
}

第三步,在BookController類中修改getBook方法,以便可以接受Isbn類型的對象,

@RequestMapping(value = "/{isbn}", method = RequestMethod.GET)
public Book getBook(@PathVariable Isbn isbn) {
  return bookRepository.findBookByIsbn(isbn.getIsbn());
}

第四步,啟動./gradlew clean bootRun,啟動成功以後,在瀏覽器中輸入http://localhost:8080/books/978-1-78528-415-1。

雖然我們不會觀察到任何可見的更改,但IsbnEditor確實在工作,從{isbn}參數中創建Isbn類對象實例。我們打印了傳遞過來的Isbn實例,重寫了toString()方法。


屬性轉化為對象實例

Spring自動配置大量的默認編輯器,但是對於自定義類型,我們必須明確地為每個Web請求實例化新的編輯器。 這是在控制器中使用@InitBinder註解的方法完成的。 掃描此註解,所有檢測到的方法應具有接受WebDataBinder作為參數的簽名。 除此之外,WebDataBinder還為我們提供了註冊盡可能多的自定義編輯器的能力,要求控制器的方法被正確綁定。

Tips
PropertyEditor不是線程安全的!
因此,我們必須為每個Web請求創建一個新的自定義編輯器實例,並將其註冊到WebDataBinder。

如果需要新的PropertyEditor,最好通過擴展PropertyEditorSupport類並自定義重寫所需的方法來創建。

七. 配置一個自定義的類型格式化類

PropertyEditor因為它的狀態和非線程安全,從版本3起,Spring添加了一個Formatter接口作為PropertyEditor的替代。 格式化類旨在提供類似的功能,但是以完全線程安全的方式,並專註於解析對象類型中的String並將對象轉換為其字符串表示形式的非常具體的任務。

對於我們的應用程序,希望有一個格式化程序可以使用一個字符串形式的書籍的ISBN號碼並將其轉換為一個Book實體對象。 這樣,當請求URL簽名僅包含ISBN號碼或數據庫ID時,就可以使用Book類型的參數定義控制器請求的方法。

首先,在src/main/java/org/test/bookpub目錄下創建一個新的包formatters,在此包下,創建BookFormatter類並實現Formatter接口,代碼示例如下:

public class BookFormatter implements Formatter<Book> {
  private BookRepository repository;
  public BookFormatter(BookRepository repository) {
    this.repository = repository;
  }
  @Override
  public Book parse(String bookIdentifier, Locale locale) throws ParseException {
    Book book = repository.findBookByIsbn(bookIdentifier);
    return book != null ? book : repository.findOne(Long.valueOf(bookIdentifier));
  }
  @Override
  public String print(Book book, Locale locale) {
    return book.getIsbn();
  }
}

然後,在WebConfiguration類中,重寫addFormatters(FormatterRegistry registry)方法,並把BookFormatter類註冊進去。

@RequestMapping(value = "/{isbn}/reviewers", method = RequestMethod.GET)
public List<Reviewer> getReviewers(@PathVariable("isbn") Book book) {
  return book.getReviewers();
}

接下來,在BookController類中,新增一個請求方法,用來根據給定的圖書的 isbn來显示評論者,

@RequestMapping(value = "/{isbn}/reviewers", method = RequestMethod.GET)
public List<Reviewer> getReviewers(@PathVariable("isbn") Book book) {
  return book.getReviewers();
}

為了一些數據,現在手動添加一些測試數據填充數據庫,通過向StartupRunner類添加兩個自動裝配的資源庫:

@Autowired private AuthorRepository authorRepository;
@Autowired private PublisherRepository publisherRepository;

下面這些代碼添加到StartupRunner類的run(...)方法中:

Author author = new Author("Alex", "Antonov");
author = authorRepository.save(author);
Publisher publisher = new Publisher("Packt");
publisher = publisherRepository.save(publisher);
Book book = new Book("978-1-78528-415-1", "Spring Boot Recipes", author, publisher);
bookRepository.save(book);

輸入./gradlew clean bootRun,在控制台,啟動應用程序。

訪問http://localhost:8080/books/978-1-78528-415-1/reviewers,在瀏覽器中可以看到如下結果:


reviewers

格式化功能旨在提供與PropertyEditors類似的功能。 通過將FormatterRegistry註冊在重寫的addFormatter方法中,告訴Spring使用Formatter將Book的文本表示轉換為實體對象並返回。 由於格式化是無狀態的,因此我們無需在控制器中每次都要註冊; 我們只做一次就好,這確保Spring為每個Web請求使用它。

Tips
如果要定義一個常用類型的轉換(例如String或Boolean),就像我們在IsbnEditor示例中所做的那樣,最好是通過Controller的InitBinder方法中的PropertyEditors初始化來做,因為這樣的改變可能不是全局所期望的,只是針對特定的控制器的功能。

你可能已經注意到,我們還將BookRepository自動裝配到WebConfiguration類,因為這是創建BookFormatter所需的。 這是Spring的一個很酷的東西,它讓我們可以組合配置類,並使它們同時依賴於其他bean。 正如我們指出,為了創建一個WebConfiguration類,我們需要一個BookRepository,Spring確保在創建WebConfiguration類時首先創建BookRepository,然後自動注入作為依賴。 實例化WebConfiguration之後,將對其進行處理以進行配置說明。