要用Spring创建应用程序,你需要为框架做很多事情。当然,Spring提供了很
多优秀的特性,用于开发令人惊讶的应用程序。但是,你需要自己往项目的构建说明文件里添加各种库依赖,还要自己写配置文件,告诉Spring要做什么。
Spring Boot的出现就是为了解决以上的问题,让你集中精力开发应用程序,而不是繁琐的配置。
以下实践基于spring boot的2.1.5发行版
spring initializer初始化项目方法有以下四种:
我们会使用Spring Boot构建一个简单的阅读列表应用程序。在这个程序里,用户可以输入想读的图书信息,查看列表,用Spring MVC来处理Web请求,用Thymeleaf来定义Web视图,用Spring Data JPA来把阅读列表持久化到数据库里,数据库用Mysql,构建工具用Maven。
项目的结构和遵循传统Maven布局:
DemoApplication.java 项目的启动引导类,也是Spring配置类,稍后会将配置的加载方式
application.properties 用于传统项目的配置信息和Spring Boot的属性
pom.xml maven构建文件,用于管理依赖的jar
引导类也即刚才的DemoApplication.java,有配置和引导启动两个作用。虽然Spring Boot的自动配置的目的是尽量不写配置,但是还是需要少量配置来启动自动配置。
@Controller
@SpringBootApplication
public class DemoApplication {
@Autowired
private SystemProperties systemProperties;
@RequestMapping("/home")
public String home() {
return "home";
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication开启了Spring的组件扫描和Spring Boot的自动配置功能。实际
上,@SpringBootApplication将三个有用的注解组合在了一起:
上面的例子会看到,DemoApplication类不仅是配置和引导启动类,加上@Controller注解后还是注册为一个SpringMVC控制器,路径"/home"返回的home会去templates文件加下寻找home.html静态资源文件并返回给前端,通过地址http://localhost:8080/home可以访问。
端口可以通过在application.properties里设置server.port属性来指定,默认是8000
要理解Spring Boot起步依赖带来的好处,先让我们假设它们尚不存在。如果没用Spring Boot的话,你会向项目里添加哪些依赖呢?要用Spring MVC的话,你需要哪个Spring依赖?你还记得Thymeleaf的Group和Artifact ID吗?你应该用哪个版本的Spring Data JPA呢?它们放在一起兼容吗?这样一来,有不少配置功课要做。
即使你能记得所有需要的依赖,但你是怎么知道的?你怎么保证你选的这些版本能相互兼容?也许可以,但构建并运行应用程序之前你是不知道的。再说了,你怎么知道这个列表是完整的?在一行代码都没写的情况下,你离开始构建还有很长的路要走。
将spring-boot-starter-parent作为上一级,这样一来就能利用Maven的依赖管理功能,继承很多常用库的依赖版本,在你声明依赖时就不用再去指定版本号了。
org.springframework.boot
spring-boot-starter-parent
2.1.5.RELEASE
Spring Boot通过提供众多起步依赖降低项目依赖的复杂度。起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了它们提供的某种或某类功能。
我们还想以Thymeleaf为Web视图,用JPA来实现数据持久化,因此在构建文件里还需要Thymeleaf和Spring Data JPA的起步依赖。
为了能进行测试,我们还需要能在Spring Boot上下文里运行集成测试的库,因此要添加Spring Boot的test起步依赖,这是一个测试时依赖。
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
mysql
mysql-connector-java
6.0.5
这几个起步依赖只是Spring Boot众多起步依赖中的沧海一粟,还有很多可选择的起步依赖,可以自由选择使用的依赖和组合。
现在我们有了一个空的项目结构,构建说明文件也准备好了,是时候开发应用程序了。我们会让Spring Boot来处理配置细节,而我们自己则专注于编写阅读列表功能相关的代码。
自动配置会做出以下配置决策,它们和之前的例子息息相关。
自动配置里比较简单的还是在配置文件里面覆盖配置选项
Spring Boot自动配置的Bean提供了300多个用于微调的属性。当你调整设置时,只
要在环境变量、Java系统属性、JNDI(Java Naming and Directory Interface)、命令行参数或者属性文件里进行指定就好了。
Spring Boot应用程序有多种设置途径。Spring Boot能从多种属性源获得属性,包括
如下几处:
(1) 命令行参数
(2) java:comp/env里的JNDI属性
(3) JVM系统属性
(4) 操作系统环境变量
(5) 随机生成的带random.*前缀的属性(在设置其他属性时,可以引用它们,比如${random. long})
(6) 应用程序以外的application.properties或者appliaction.yml文件
(7) 打包在应用程序内的application.properties或者appliaction.yml文件
(8) 通过@PropertySource标注的属性源
(9) 默认属性
这个列表按照优先级排序,也就是说,任何在高优先级属性源里设置的属性都会覆盖低优先级的相同属性。例如,命令行参数会覆盖其他属性源里的属性。
application.properties和application.yml文件能放在以下四个位置,优先级同理。
(1) 外置,在相对于应用程序运行目录的/config子目录里。
(2) 外置,在应用程序运行的目录里。
(3) 内置,在config包内。
(4) 内置,在Classpath根目录。
开发的过程中你会发现,Thymeleaf模板的变更是不会立即生效的。这是因为Thymeleaf模板默认缓存。这有助于改善应用程序的性能,因为模板只需编译一次,但在开发过程中就不能实时看到变更的效果了。
将spring.thymeleaf.cache设置为false就能禁用Thymeleaf模板缓存。这里使用在application.properties里设置属性:
spring.thymeleaf.cache=false
此处使用Thymeleaf作为应用程序的视图,Spring Boot支持的其他模板也能关闭模板缓存,设置这些属性就好了:
要是所有应用程序都试着让Tomcat服务器监听同一个端口,在启动第二个应用程
序时就会有冲突。这时需要给每个服务器不同的配置。可以设置server.port属性,和spring.thymeleaf.cache一样设置在Classpath目录里的application.properties。
server.port=8080
除了服务器的端口,你还可能希望服务器提供HTTPS服务,这里只是介绍基本的配置服务,不做介绍了,如果有需要可以自行搜索相关配置方法。
Spring Boot会用Logback(http://logback.qos.ch)来记录日志,并用INFO级别输
出到控制台。在运行应用程序和其他例子时,你应该已经看到很多INFO级别的日志了。
可以在指定目录创建logback.xml配置文件,示例内容如下:
[Consociate] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
application.properties需要配置日志的级别和指定logback.xml的路径:
logging.level.root=INFO
logging.config.classpath=logback.xml
logging.path=/var/logs/
logging.file=BookWorm.log
logging.level.root.org.springframework.security=DEBUG
还可以指定日志的输出文件,如果日志的模式是写文件模式。如果根日志级别要设置为WARN,但Spring Security的日志要用DEBUG级别,可以logging.level.root.org.
springframework.security指定Spring Security的日志级别。
虽然你可以显式配置自己的DataSource Bean,但通常并不用这么做,只需简单地通过属性配置数据库的URL和身份信息就可以了。举例来说,如果你用的是MySQL数据库,你的基本连接配置可以是下面这样:
spring.datasource.url=jdbc:mysql://localhost:3306/books_stadium?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=
spring.datasource.drive-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
这里用的mysql-connector是6+版本,驱动类变成了com.mysql.cj.jdbc.Driver,数据库连接池是默认用的Hikari。
通常你都无需指定JDBC驱动,Spring Boot会根据数据库URL识别出需要的驱动
在自动配置DataSource Bean的时候,Spring Boot会使用这里的连接数据。DataSourceBean是一个连接池,如果Classpath里有Tomcat的连接池DataSource,那么就会使用这个连接池;否则,Spring Boot会在Classpath里查找以下连接池:
当应用程序需要部署到不同的运行环境时,一些配置细节通常会有所不同。比如,数据库连接的细节在开发环境下和测试环境下就会不一样,在生产环境下又不一样。Spring Framework从Spring 3.1开始支持基于Profile的配置。Profile是一种条件化配置,基于运行时激活的Profile,会使用或者忽略不同的Bean或配置类。
设置spring.profiles.active属性就能激活Profile,任意设置配置属性的方式都能用于
设置这个值。Spring Boot支持为application.properties和application.yml里的属性配置
Profile。
为了演示区分Profile的属性,假设你希望针对生产环境和开发环境能有不同的日志配置。在生产环境中,你只关心WARN或更高级别的日志项,想把日志写到日志文件里。在开发环境中,你只想把日志输出到控制台,记录DEBUG或更高级别。
如果你正在使用application.properties,可以创建额外的属性文件,遵循application-{profile}. properties这种命名格式,这样就能提供特定于Profile的属性了。
spring.profiles.active=development
对应application-development.properties里的配置就是:
server.port=8081
logging.level.root=DEBUG
这是把开发环境的端口设置为8081,与正式环境区分开来,以便部署的时候不出差错。
Spring Boot自动配置的默认错误处理器会查找名为error的视图,如果找不到就用默认的白标错误视图,会显示一个默认错误页面。
Spring自1.1.1版就向集成测试提供了极佳的支持。自Spring 2.5开始,集成测试支持的形式就变成了SpringJUnit4ClassRunner。这是一个JUnit类运行器,会为JUnit测试加载Spring应用程序上下文,并为测试类自动织入所需的Bean。
如你所见,SpringTest类上加注了@RunWith和@SpringBootTest注解。@RunWith的参数是SpringJUnit4ClassRunner.class,开启了Spring集成测试支持。与此同时,
@SpringBootTest指定了如何加载应用程序上下文。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SpringTest {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setupMockMvc() throws ClassNotFoundException {
mvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
@Test
public void test() {
}
}
Spring MVC有一个优点:它的编程模型是围绕POJO展开的,在POJO上添加注解,声明如何处理Web请求。这种编程模型不仅简单,还让你能像对待应用程序中的其他组件一样对待这些控制器。你还可以针对这些控制器编写测试,就像测试POJO一样。
像下面的添加图书的一个方法:
@RequestMapping(value="/{reader}/add", method=RequestMethod.POST)
public String addToReadingList(
@PathVariable("reader") String reader, Book book) {
log.info("{}添加书单{}",reader,book.getTitle());
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/readingList/"+reader;
}
如果没有@RequestMapping,这只是一个简单的方法,通过直接传参可以测试代码本身有没有问题,但是不能真实体现表单提交的完整过程,也不能测试出真实添加过程中可能出现的问题,所以Spring提供了Mock MVC来模拟一个真实的Servlet容器来测试控制器,而不用启动服务器,这也是非常方便的做法。
Spring的Mock MVC框架模拟了Spring MVC的很多功能。它几乎和运行在Servlet容器里的应用程序一样,尽管实际情况并非如此。
要在测试里设置Mock MVC,可以使用MockMvcBuilders,该类提供了两个静态方法。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MockMvcTest {
Logger log = (Logger)LoggerFactory.getLogger(MockMvcTest.class);
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setupMockMvc() throws ClassNotFoundException {
mvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
@Test
public void test(){
ResultActions result = null;
try {
result = (ResultActions) mvc.perform(get("/readingList/lzy"))
.andExpect(model().attributeExists("reader"))
.andExpect(status().isOk());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Spring完成了ReadingListController的初始化,并从Spring Boot自动配置的应用程序上下文里将其注入。setupMockMvc()方法上添加了JUnit的@Before注解,表明它应该在测试方法之前执行。它将WebApplicationContext注入webAppContextSetup()方法,然后调用build()产生了一个MockMvc实例,该实例赋给了一个实例变量,供测试方法使用。
示例中像/readingList/lzy地址发送了一个GET请求,判断模型属性和状态是否满足期望。其中使用了几个静态方法,可以通过静态导入对应的类,使代码看起来简便优美一些。
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
这样,一个简单的Spring Boot应用程序已经开发完成了。
希望这篇文章对初学Spring Boot的朋友起到帮助作用,因为篇幅的原因,很多代码没有贴出来,有兴趣看的同学可以下载源代码自己调试看看第一个Spring Boot应用程序源代码