在我的《Spring Boot 框架介绍和使用》里介绍了Spring Boot,但是没有例子。所以这一篇的主要内容就是来做一个小例子。结合我上面那篇一起看效果更佳。
运行项目
创建项目和上篇文章一样,我用了2.0的快照版本的Spring Boot,因为现版本1.5的Thymeleaf还是2.1的版本,比较旧。在Idea中运行Spring Boot项目不如Spring Tool Suite简单,因为在STS中直接保存文件即可触发devtools的重启,而在IDEA中只能手动点击build project命令。
在这里还遇到一点小情况。我原来不明白IDEA中有一个delegate to gradle有什么作用,就胡乱选上了。现在才发现,原来选中这个选项之后,在点击构建项目的时候不会调用IDEA自己的构建工具,而是使用gradle的构建。所以速度会更慢。如果使用IDEA的构建命令,速度会更快一些。
当然这样感觉还是稍微比STS慢一点。所以我又找到了另外一种方法,就是利用gradle的持续构建选项。首先打开一个终端,输入gradle assemble --continuous
,这样gradle就会一直编译项目。如果检测到文件发生更改,就会自动重复编译项目,直到我们手动关闭了终端。然后在用gradle bootRun
命令正常调试运行项目,当文件发生更改的时候gradle会自动编译项目,从而触发devtools自动重启服务器。
Spring Boot项目在运行的时候支持LiveReload,我们只要在浏览器上安装相应的插件并启用,服务器自动重启之后便会自动刷新浏览器。我们不必每次更改之后手动刷新了。
MVC
多个视图解析器
在Spring Boot中,错误页面可以放在下面的文件夹下。在使用Thymeleaf的时候,情况就变的稍微有点复杂了。
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 404.html
+- <other templates>
我们希望将重复的模板代码抽出来组合成单独的文件,让其他页面引用。这样以后修改的时候只需要修改一处就可以更改所有页面的效果。但是Thymeleaf默认的代码块导入只能支持同级页面,像下面这样错误页面在单独一个文件夹、公用页面也在单独一个文件夹下的情况,默认的配置不能满足我们的需要。这时候就需要覆盖Spring Boot的自动配置了。
经过一番查阅,我找到了解决办法。这种情况下需要配置的多个视图解析器。在Spring Boot中很简单,我们只需要定义自己的视图解析器,Spring就会自动屏蔽默认配置的。
配置代码如下。我们为代码段单独配置一个视图解析器。然后将这些视图解析器都添加到视图引擎中。这些必须都配置为Spring Bean。如果直接在templateEngine()
中new视图解析器并添加,就会抛出ApplicationContext为空的异常。
最后要注意setCheckExistence
方法也必须设置。不然的话视图解析器就会认为视图总是存在,所以渲染页面的时候会出现找不到视图文件的情况。所以设置了这个选项,解析器就会先检查文件是否存在,不存在的话就直接返回。这样另一个视图解析器就会寻找视图,最后我们两个文件夹下的视图就可以都找到了。
@Configuration
public class TemplateConfig {
private boolean cacheable = false;
private String templatesPrefix = "classpath:templates/";
private String fragmentsPrefix = "classpath:templates/fragments/";
private String suffix = ".html";
private TemplateMode templateMode = TemplateMode.HTML;
private String encoding = "UTF-8";
private boolean checkExistence = true;
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix(templatesPrefix);
resolver.setSuffix(suffix);
resolver.setCharacterEncoding(encoding);
resolver.setCacheable(cacheable);
resolver.setCheckExistence(checkExistence);
return resolver;
}
@Bean
public SpringResourceTemplateResolver fragmentResolver() {
SpringResourceTemplateResolver fragmentResolver = new SpringResourceTemplateResolver();
fragmentResolver.setPrefix(fragmentsPrefix);
fragmentResolver.setSuffix(suffix);
fragmentResolver.setCacheable(cacheable);
fragmentResolver.setTemplateMode(templateMode);
fragmentResolver.setCharacterEncoding(encoding);
fragmentResolver.setCheckExistence(checkExistence);
return fragmentResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(templateResolver());
templateEngine.addTemplateResolver(fragmentResolver());
templateEngine.setEnableSpringELCompiler(true);
templateEngine.addDialect(new Java8TimeDialect());
return templateEngine;
}
}
MVC配置
spring自动配置的MVC基本够最基本的使用了,但是在做项目的话肯定要添加自己的配置。我们用Java配置的话也很简单。下面的例子很简单,添加了几个视图控制器,直接将请求和视图连在一起;还定义了两个格式化器。不知道为何Spring没有对这些新日期类的支持,所以我们只能自己写格式化器了。
如果我们只需要向下面添加几个自己的格式化器之类的,向下面这样继承WebMvcConfigurerAdapter
即可。如果想完全控制Mvc的设置,可以添加@EnableMvc
,这样Spring Boot的自动配置就会完全取消。
在此叨叨两句,如果是旧项目的话就算了。如果使用新项目的话我们在处理日期和时间的时候务必使用Java 8提供的新类,LocalDate、LocalDateTime这些,这些新类符合新标准,提供的新方法也更好用。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/system-beans").setViewName("beans");
registry.addViewController("/addUser").setViewName("addUser");
registry.addViewController("/mvc").setViewName("mvc");
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateFormatter());
registry.addFormatter(new LocalDateTimeFormatter());
}
}
class LocalDateFormatter implements Formatter<LocalDate> {
@Override
public LocalDate parse(String text, Locale locale) throws ParseException {
return LocalDate.parse(text);
}
@Override
public String print(LocalDate object, Locale locale) {
return object.toString();
}
}
class LocalDateTimeFormatter implements Formatter<LocalDateTime> {
@Override
public LocalDateTime parse(String text, Locale locale) throws ParseException {
return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Override
public String print(LocalDateTime object, Locale locale) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return object.format(formatter);
}
}
Spring Data
多数据源
在开发的时候我们一般有测试数据库和生产数据库,在测试的时候连接到测试数据库,部署的时候改为生产数据库。所以我们这里首先来配置一下多数据源。
在application-test.properties
中。其实这里什么也不写也可以,Spring 检测到H2 、HSQLDB或Derby的话就会自动创建一个内存嵌入式数据源。
# 数据库设置
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
在application-product.properties
中,生产数据库为MySQL。
# 数据库设置
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
然后在application.properties
中,激活配置即可。以后开发完成之后,改为product即可。
spring.profiles.active=test
使用Hikari连接池
Spring Boot会按照tomcat、HikariCP、DBCP2的顺序查找和使用连接池。不过我看了一下好像HikariCP的性能最好,所以这里我们直接使用HikariCP。
首先需要添加HikariCP的依赖。Spring Boot也包含了对HikariCP的版本号管理,不过它的版本比较低一点,所以我就干脆直接指定了最新的。
compile group: 'com.zaxxer', name: 'HikariCP', version: '2.6.1'
然后添加spring.datasource.type=com.zaxxer.hikari.HikariDataSource
即可。
H2 web控制台
如果嵌入式数据库选择了H2,而且项目中添加了spring-boot-devtools
。那么Spring还会启用H2的web控制台功能。
如果不需要这个功能可以直接关闭。
spring.h2.console.enabled=false
启用审计
最后我希望使用Spring Data的审计功能来帮我设置用户的注册时间。但是审计不是Spring Boot自动配置的内容。所以我们需要手动开启。
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
然后在实体类上添加EntityListeners
注解。
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column
private String nickname;
@Column
private LocalDate birthday;
@Column
@CreatedDate
private LocalDateTime registerTime;
}
这样,当我们处理数据的时候,注册时间就会由Spring自动填充了。
@Transactional
public interface UserRepository extends CrudRepository<User, Integer> {
Optional<User> findByUsername(String username);
}
Actuator
在项目中添加Actuator的依赖项即可。这样Spring就会自动添加相关的路径映射。
compile("org.springframework.boot:spring-boot-starter-actuator")
为了保证安全,这些都需要验证才能访问。我们可以简单的关闭它,management.security.enabled=false
。不过为了安全起见,实际开发中应该设置密码来保护这些敏感信息。
Beans可视化
本来我想将这些端点全做成可视化的,不过看了一些,大部分端点返回来的JSON都比较复杂,是个多层结构,所以最后只做了一个Beans的可视化。推荐一个Chrome插件,json-formatter。安装好之后,在查看这些端点的JSON,就不是糊成一团的了,而是格式化并且语法高亮的形式了。
Beans端点返回的JSON稍微有些奇怪,它是个类似下面这样的对象,也就是个数组,所以获取到数据之后必须使用data[0]
这样的语法才能获取里层的对象。
[
{
context: "xxx",
parent: "xxx",
beans: [
...
...
然后页面就可以写成下面这样的。"[[@{/beans}]]"
是Thymeleaf的语法,Thymeleaf引擎遇到它会转换为实际的URL。然后jquery获取到对象之后,使用了Knockout将数据绑定到页面上。详细使用方法请参考jQuery和Knockout的官方文档。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="_header::header('系统监控')">
</head>
<body>
<nav th:replace="_navbar::navbar"></nav>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Beans</h2>
<h4><a th:href="@{/beans}">点击查看原格式</a></h4>
<p><strong>context:</strong><span data-bind="text: context"></span></p>
<p><strong>parent:</strong><span data-bind="text: parent"></span></p>
<p><strong>数量:</strong>共有<span data-bind="text: beans.length"></span>个Beans</p>
<table class="table table-bordered table-hover table-responsive table-striped">
<thead>
<tr>
<th>Beans</th>
<th>resource</th>
<th>scope</th>
<th>type</th>
</tr>
</thead>
<tbody data-bind="foreach: beans">
<tr>
<td data-bind="text: bean"></td>
<td data-bind="text: resource"></td>
<td data-bind="text: scope"></td>
<td data-bind="text: type"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div th:replace="_footer::footer"></div>
<script>
$(document).ready(function () {
$.getJSON("[[@{/beans}]]", function (data) {
ko.applyBindings(data[0])
})
})
</script>
</body>
</html>
Hypermedia方式查看数据
如果我们添加 Spring HATEOAS的依赖,也就是下面这个。
compile 'org.springframework.hateoas:spring-hateoas'
然后启用Hypermedia功能。
endpoints.hypermedia.enabled=true
Spring就会在默认/actuator
路径下生成一个发现页面,返回所有可用的端点和相应的URL。这个发现页面的URL可以使用endpoints.actuator.path
设置,这个发现功能可以使用endpoints.actuator.enabled
打开或关闭。
如果还存在HAL Browser的jar包,也就是添加下面的依赖,那么Spring还会开启一个可视化页面覆盖/actuator
的JSON形式。可视化页面其实也没啥功能,会把所有的端点和输出以格式化之后的JSON形式输出,如果没安装JSON美化浏览器插件的话这个功能还是挺有用的。
compile group: 'org.springframework.data', name: 'spring-data-rest-hal-browser', version: '2.6.1.RELEASE'
主页
用Markdown格式化
想了想也没啥写的了。最后就来说说Markdown把。我用的是marked。然后在resouces/statis/md/
下建了markdown格式文件。然后页面可以写成类似这样的。同样是通过jQuery获取数据,然后转换为HTML。
<div class="container" id="content">
</div>
<div th:replace="_footer::footer"></div>
<script>
$(document).ready(function () {
$.get("[[@{/md/index.md}]]", function (data) {
$("#content").html(marked(data));
})
})
</script>
完整的Spring Boot小例子代码在CSDN代码库上了,有兴趣的同学可以看看。由于项目是Gradle项目,所以可能有些同学不好编译打包。这里我还上传了完整的二进制程序Spring Boot小例子程序,可以直接使用java -jar XXX.jar
来运行。