Spring Boot自動配置是一個運行時的過程,考慮衆多因素,決定Spring配置應該使用哪個,而不該用哪個。
Spring Boot自動配置需要考慮:
Spring的JdbcTemplate是否在Classpath中?若有,並且有DataSource的Bean,則自動配置一個JdbcTemplate的Bean。
Thymeleaf是否在Classpath中?若有,則配置Thymeleaf的模板解析器、視圖解析器以及模板引擎。
Spring Security是否在Classpath中?若有,則進行一個基本的Web安全設置。
每當應用程序啓動時,Spring Boot的自動配置都會進行重新配置。
專注應用程序功能
- 定義領域模型
簡單模型,屬性+get/set
import javax.persistence.*;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String reader;
private String isbn;
private String title;
private String author;
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getReader() {
return reader;
}
public void setReader(String reader) {
this.reader = reader;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
- 定義倉庫接口
將實體類對象持久化到數據庫的倉庫當中
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReadingListRepository extends JpaRepository<Book, Long> {
List<Book> findByReader(String reader);//用戶自定義方法
}
因爲使用了Spring Data JPA,所以只需定義一個接口,擴展Spring Data JPA的JpaRepository接口。
通過接口擴展,ReadingListRepository直接繼承了多個執行常用持久化操作的方法。當然,繼承的接口,Spring Data在應用程序啓動後,該接口在運行時會自動實現。
- 創建web界面
使用Spring MVC處理HTTP請求:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@Controller
@RequestMapping("/")
public class ReadingListController {
private ReadingListRepository readingListRepository;
@Autowired
public ReadingListController(ReadingListRepository readingListRepository) {
this.readingListRepository = readingListRepository;
}
@RequestMapping(value = "/{reader}", method = RequestMethod.GET)
public String readersBooks(@PathVariable("reader") String reader, Model model) {
//根據{reader}從倉庫取出Book列表
List<Book> readingList = readingListRepository.findByReader(reader);
if (readingList != null) {
//用books鍵將列表存放在model中(model爲一個Map)
model.addAttribute("books", readingList);
}
//返回"readingList"作爲視圖名
return "readingList";
}
//將正文數據綁定到一個Book對象上,將該對象的reader屬性設置爲讀者姓名
//通過倉庫的save()保存修改後的Book對象,最後進行重定向
@RequestMapping(value="/{reader}", method=RequestMethod.POST)
@ResponseBody
public String addToReadingList(@PathVariable("reader") String reader, @ResponseBody Book book) {
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/{reader}";
}
}
@Controller:掃描並自動將其處理器(此處爲ReadingListController)註冊爲Spring應用程序上下文中的一個Bean。
@RequestMapping:將處理器的方法映射到URL路徑上
呈現閱讀列表的Thymeleaf模板:
在src/main/resources/templates裏創建一個名爲readingList.html的文件
<html>
<head>
<title>Reading List</title>
<link rel="stylesheet" th:href="@{/style.css}"></link>
</head>
<body>
<h2>Your Reading List</h2>
<div th:unless="${#lists.isEmpty(books)}">
<dl th:each="book : ${books}">
<dt class="bookHeadline">
<span th:text="${book.title}">Title</span> by
<span th:text="${book.author}">Author</span>
(ISBN: <span th:text="${book.isbn}">ISBN</span>)
</dt>
<dd class="bookDescription">
<span th:if="${book.description}"
th:text="${book.description}">Description</span>
<span th:if="${book.description eq null}">
No description available</span>
</dd>
</dl>
</div>
<div th:if="${#lists.isEmpty(books)}">
<p>You have no books in your book list</p>
</div>
<hr/>
<h3>Add a book</h3>
<form method="POST">
<label for="title">Title:</label>
<input type="text" name="title" size="50"></input><br/>
<label for="author">Author:</label>
<input type="text" name="author" size="50"></input><br/>
<label for="isbn">ISBN:</label>
<input type="text" name="isbn" size="15"></input><br/>
<label for="description">Description:</label><br/>
<textarea name="description" cols="80" rows="5">
</textarea><br/>
<input type="submit"></input>
</form>
</body>
</html>
該頁面模板有兩部分組成:上方是讀者的閱讀列表中的圖書清單,下方是一個表單,讀者可添加新書。
另外,引入的style.css文件,位於src/main.resources/static中:
body {
background-color: #cccccc;
font-family: arial,helvetica,sans-serif;
}
.bookHeadline {
font-size: 12pt;
font-weight: bold;
}
.bookDescription {
font-size: 10pt;
}
label {
font-weight: bold;
}
省略數據庫配置(在配置文件中配相應的DataSource)
綜上就是一個完整的web應用程序了。
在嚮應用程序加入Spring Boot時,有個名爲spring-boot-autoconfigure的jar文件,包含了很多的配置類。每個配置類都在應用程序的Classpath裏。這些配置類有用於Spring Data JPA的配置,有用於Spring MVC的配置,和其他配置。用戶可以選擇是否要使用他們。
這些配置文件採用條件配置(Spring4.0新特性),允許配置存在於應用程序中,但在滿足特定條件前忽略該配置。
在Spring中要編寫自己的條件,只需實現Condition接口,並覆蓋它的matches()方法。
如,下面的條件類只有在Classpath裏存在JdbcTemplate時纔會生效:
public class jdbcTemplateConditon implements Conditon{
@Override
public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata){
try{
context.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
}catch(Exception e){
return false;
}
}
}
當用Java來聲明Bean時,可以使用這個自定義條件類:
@Conditional(JdbcTemplateConditon.class)
public MyService myService{
...
}
在該例子中,只有當JdbcTemplateCondition類條件成立時,纔會創建MyService這個Bean,即MyService Bean創建的條件是Classpath裏有JdbcTemplate。否則,這個Bean聲明將被忽略。
在Spring Boot的自動配置類中,DataSourceAutoConfiguration添加了@Configuration註解,從其他配置類裏導入了一些額外配置,還自定義了一下Bean。另外,添加了@ConditionalOnClass註解,要求Classpath裏必須要有DataSource和EmbeddedDatabaseType。若他們不存在,DataSourceAutoConfiguration提供的配置會都被忽略。
DataSourceAutoConfiguration內嵌了JdbcTemplateConfiguration類,自動配置了一個JdbcTemplate Bean。
JdbcTemplateConfiguration使用了@Conditional註解,判斷DataSourceAvailableCondition條件是否成立–是否有一個DataSource Bean或自動配置創建一個。假設有DataSource Bean,使用了@Bean註解的jdbcTemplate()方法會配置一個JdbcTemolate Bean。另外使用了@ConditionalOnMissingBean註解,因此,只有在不存在JdbcOperations(JdbcTemplate實現接口)類型的Bean時,會創建JdbcTemplate Bean。
以上,說明了Spring Boot如何利用條件化配置實現自動配置。
自動裝配會做出一下決策:(以上述例子爲例)
因爲Classpath裏有H2,所以會創建一個嵌入式H2數據庫Bean,他的類型爲javax.sql.DataSource,JPA實現(Hibernate)需要它來訪問數據庫。
因爲Classpath裏有Hibernate(Spring Data JPA傳遞引入的)的實體管理器,所以自動配置會配置與Hibernate相關的Bean,包括Spring的LocalContainerEntityManagerFactoryBean和JpaVendorAdapter。
因爲Classpath裏有Spring Data JPA,所以他會自動配置爲根據倉庫的接口創建倉庫實現。
因爲Classpath裏有Spring Data JPA,所以它會自動配置爲Spring MVC的視圖,包括一個Thymeleaf的模板解析器、模板引擎及視圖解析器。視圖解析器會解析相對於Classpath根目錄/templates目錄裏的模板。
因爲Classpath裏有Spring MVC,所以會配置Spring的DispatcherServlet並啓用Spring MVC。
因爲這時一個Spring MVC Web應用,所以會註冊一個資源處理器,把相對於Classpath根目錄/static目錄下的靜態內容提供出來。
因爲Classpath裏有Tomcat(通過web起步依賴傳遞),所以會啓動一個嵌入式Tomcat容器。
參考文獻:Spring Boot實戰 ,丁雪豐 (譯者)