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实战 ,丁雪丰 (译者)