1. Spring Boot 介绍
Spring Boot 简化了基于 Spring 的应用开发,你只需要 "run" 就能创建一个独立的,产品级别的 Spring 应用。
你可以使用 Spring Boot 创建 Java 应用,并使用 java -jar 启动它或采用传统的 war 部署方式。我们也提供了一个运行 "Spring 脚本" 的命令行工具。
主要特性:
- 为所有 Spring 开发提供一个从根本上更快,且随处可得的入门体验。
- Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)。
- 开箱即用,但通过不采用默认设置可以快速摆脱这种方式。
- 提供一系列大型项目常用的非功能性特征,比如:内嵌服务器,安全,指标 metrics,健康检测 health checks,外部化配置 externalized configuration。
- 绝对没有代码生成,也不需要XML配置。
关于 Spring Boot 升级到 2.0,请参见 http://blog.didispace.com/spring-boot-2-release/
1.1 系统要求
Java + 构建工具(Maven / Gradle)+ Servlet 容器(Tomcat / Jetty / Undertow)
1.2 Spring Boot CLI 安装
在 Mac 系统中使用 homebrew 来安装:
brew tap pivotal/tap
brew install springboot
homebrew 将把 Spring Boot 安装到 /usr/local/bin
下。
2. 一个 Spring Boot 示例 MyFirstSpringBoot
首先通过 https://start.spring.io/ 来生成基础代码,项目名:MyFirstSpringBoot,这里我们选择使用 Maven 来构建,并添加了 Web 依赖:
将代码下载到本地并解压缩,使用 IDE 打开,这里我们使用 IntelliJ IDEA,结构如下:
我们也可以通过 IntelliJ IDEA(社区版 Community) 的 Spring Assistant 插件来构造项目结构:
打开 pom.xml
文件,可以看到如下的依赖配置:
- 其中就包括了我们在项目创建时添加的 Web 依赖
spring-boot-starter-web
-
spring-boot-maven-plugin
是一个Maven插件,它可以将项目打包成一个可执行jar - starters 是一个依赖描述符的集合,你可以将它包含进项目中,这样添加依赖就非常方便。例如,如果你想使用 Spring 和 JPA 进行数据库访问,只需要在项目中包含
spring-boot-starter-data-jpa
依赖,然后你就可以开始了。完整的 starters 列表请点击此处查看。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
通过运行 mvn dependency:tree
命令,可以看到该项目具体的依赖树:
修改 MyFirstSpringBootApplication.java
类,内容如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableAutoConfiguration
public class MyFirstSpringBootApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(MyFirstSpringBootApplication.class, args);
}
}
对这段代码的几点分析:
- 从 import 中可以看出,
@RestController
和@RequestMapping
是Spring MVC中的注解(它们不是Spring Boot的特定部分) -
@EnableAutoConfiguration
,这个注解告诉 Spring Boot 根据添加的 jar 依赖猜测你想如何配置Spring。由于 spring-boot-starter-web 添加了 Tomcat 和 Spring MVC,所以 auto-configuration 将假定你正在开发一个 web 应用,并对 Spring 进行相应地设置。 -
SpringApplication.run(...)
启动Spring,相应地启动被自动配置的 Tomcat web 服务器。
修改 MyFirstSpringBootApplicationTests.java
类,内容如下:
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyFirstSpringBootApplicationTests {
@Test
public void contextLoads() {
}
}
最后通过 mvn spring-boot:run
来启动这个项目:
可以看出,
run
是定义在 spring-boot-maven-plugin
这个依赖中的。
可以看出启动了 8080 端口 ,因此可以通过 http://localhost:8080/ 来访问:
我们也可以通过 mvn package
创建可执行 jar 包,并通过 jar 包来运行启动:
3. 扩展成一个 Web MVC 的应用
现在我们一步一步扩展上面的项目,并将其扩展为一个完整的 Web MVC 的应用。
注意,请将程序入口 MyFirstSpringBootApplication.java
放在 src 的根目录,如下所示,这样 Spring 就会扫描根目录及所有的子包,例如 /controller
,/model
,/service
。
3.1 POM 中添加 JSP 依赖
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
3.2 增加一个首页
现在我们添加第一个 JSP 页面 welcome.jsp
,位置在 src/main/webapp/WEB-INF/jsp
:
<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html lang="en">
<body>
${welcomeMessage}
</body>
</html>
新建第一个 WelcomeController.java
,新建一个 package 名为 controller
:
@Controller
public class WelcomeController {
@Value("${welcomeMessage}")
private String welcomeMessage;
@RequestMapping("/")
public String welcome(Map<String, Object> model) {
model.put("welcomeMessage", welcomeMessage);
return "welcome";
}
}
在 resources/application.properties
文件中添加如下内容:
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
welcomeMessage=Welcome, This is a Spring Boot Demo
修改程序入口 MyFirstSpringBootApplication.java
文件内容如下:
@SpringBootApplication
public class MyFirstSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MyFirstSpringBootApplication.class, args);
}
}
启动引用,效果如下:
3.3 使用模板
目前 Spring 官方已经不推荐使用 JSP 来开发 WEB 了,而是推荐使用模板引擎来开发。
下面我们使用 Freemaker 模板引擎来重构这个首页。
首先在 pom.xml
中引入 Freemaker:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
随后在 /resources/templates
目录下新建一个模板文件 welcome.ftl
:
<!DOCTYPE html>
<html>
<body>
${welcomeMessage}
</body>
</html>
最后删除文件 welcome.jsp
,并且删除 resources/application.properties
文件中关于 Spring MVC 的两行配置。
3.4 错误处理
Spring Boot 默认提供一个 /error
映射用来以合适的方式处理所有的错误,并将它注册为 servlet 容器中全局的 错误页面。对于浏览器客户端,它会产生一个白色标签样式(whitelabel)的错误视图,该视图将以 HTML 格式显示同样的数据,默认的 404 错误处理如下:
如果想为某个给定的状态码展示一个自定义的 HTML 错误页面,你需要将文件添加到
/error
文件夹下。错误页面既可以是静态HTML(比如,任何静态资源文件夹下添加的),也可以是使用模板构建的,文件名必须是明确的状态码或一系列标签。<html>
<head><title>404</title></head>
<body>
<div style="color: red">Sorry, we can't find it.</div>
</body>
</html>
3.5 静态内容
默认情况下,Spring Boot 从 classpath 下的 /static
(/public
,/resources
或 /META-INF/resources
)文件夹,或从ServletContext 根目录提供静态内容。这是通过 Spring MVC 的 ResourceHttpRequestHandler
实现的,你可以自定义 WebMvcConfigurerAdapter
并覆写 addResourceHandlers
方法来改变该行为(加载静态文件)。
例如,添加一个图片到 /resources/static/images
下:
则可以通过路径
images/home.png
引用到该图片:
<!DOCTYPE html>
<html>
<body>
<img src="images/home.png" width="48" />
${welcomeMessage}
</body>
</html>
3.6 日志
Spring Boot默认的日志输出格式如下:
2018-06-20 17:29:46.426 INFO 85118 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-06-20 17:29:46.427 INFO 85118 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2152 ms
2018-06-20 17:29:46.666 INFO 85118 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2018-06-20 17:29:46.671 INFO 85118 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-06-20 17:29:46.673 INFO 85118 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-06-20 17:29:46.673 INFO 85118 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-06-20 17:29:46.673 INFO 85118 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2018-06-20 17:29:46.861 INFO 85118 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-20 17:29:47.158 INFO 85118 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1bad5e6d: startup date [Wed Jun 20 17:29:44 CST 2018]; root of context hierarchy
2018-06-20 17:29:47.290 INFO 85118 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.example.MyFirstSpringBoot.controller.WelcomeController.welcome(java.util.Map<java.lang.String, java.lang.Object>)
输出的节点(items)如下:
- 日期和时间 - 精确到毫秒,且易于排序。
- 日志级别 - ERROR, WARN, INFO, DEBUG 或 TRACE。
- Process ID。
- ---分隔符,用于区分实际日志信息开头。
- 线程名 - 包括在方括号中(控制台输出可能会被截断)。
- 日志名 - 通常是源class的类名(缩写)。
- 日志信息。
关于 Java 的日志,详细请参见 Java Log 日志。
这里我们选择集成 Logback。Spring Boot包含很多有用的Logback扩展,你可以在 logback-spring.xml
配置文件中使用它们。
首先在 /resources
目录下新建一个文件 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/Users/xianch/Desktop/SpringBoot/MyFirstSpringBoot/logs" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
随后在 WelcomeController.java
中添加对日志的引用:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......
// 使用日志框架SLF4J 中的 API,使用门面模式的日志框架
private final static Logger logger = LoggerFactory.getLogger(WelcomeController.class);
@Value("${welcomeMessage}")
private String welcomeMessage;
@RequestMapping("/")
public String welcome(Map<String, Object> model) {
logger.info("welcome page");
model.put("welcomeMessage", welcomeMessage);
return "welcome";
}
日志打印如下:
2018-06-20 18:22:01.625 [http-nio-8080-exec-1] INFO c.e.MyFirstSpringBoot.controller.WelcomeController - welcome page
3.7 增加完整的功能
现在我们给这个项目增加两个功能:
- 提供一个页面查看所有的用户
- 提供一个页面添加用户
首先添加模型类 User.java
:
package com.example.MyFirstSpringBoot.model;
public class User {
private Integer id;
private String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后添加服务类 UserService.java
:
package com.example.MyFirstSpringBoot.service;
import com.example.MyFirstSpringBoot.model.User;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class UserService {
private List<User> users = new ArrayList<User>();
public UserService() {
// Create some mockup data
users.add(new User(1, "Test User 1"));
users.add(new User(2, "Test User 2"));
}
public List<User> getUsers() {
return users;
}
public void addUser(User user) {
users.add(user);
}
}
然后添加控制器 UserController.java
:
package com.example.MyFirstSpringBoot.controller;
import com.example.MyFirstSpringBoot.model.User;
import com.example.MyFirstSpringBoot.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class UserController {
private final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
UserService userService;
@RequestMapping("/list_user")
public String listUser(Map<String, Object> model) {
logger.info("list user page");
model.put("users", userService.getUsers());
return "user/list_user";
}
@RequestMapping("/add_user")
public String addUser() {
logger.info("add user page");
return "user/add_user";
}
@RequestMapping("/create_user")
public String createUser(@ModelAttribute User user, Map<String, Object> model) {
logger.info("create user");
userService.addUser(user);
model.put("users", userService.getUsers());
return "user/list_user";
}
}
然后添加页面 list_user.ftl
和 add_user.ftl
:
<!DOCTYPE html>
<html>
<body>
<#list users as user>
ID:${user.id} , Name:${user.name}
<br />
</#list>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<form action="/user/create_user" method="post">
ID: <input type="text" name="id" /><br/>
Name: <input type="text" name="name" /><br/>
<input type="submit">
</form>
</body>
</html>
然后修改首页,添加入口 welcome.ftl
:
<!DOCTYPE html>
<html>
<body>
<img src="images/home.png" width="48" />
${welcomeMessage}
<br />
<a href="user/list_user">List User</a>
<br />
<a href="user/add_user">Add User</a>
</body>
</html>
效果如下:
3.8 连接数据库
在上面的例子中,我们使用一个全局变量 List<User> users = new ArrayList<User>();
来存储用户信息,现在我们用一个数据库存储替代。
在这里,我们使用一个内存数据库 H2。我们不需要提供任何连接URLs,只需要添加想使用的内嵌数据库依赖。
首先在 pom.xml
中引入 JPA 和 H2:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
我们可以指定一些SQL文件来在启动时初始化数据库,例如新建文件 user_schema.sql
和 user_data.sql
来分别用来创建数据表和填充内容:
CREATE TABLE USERS(ID INT PRIMARY KEY, NAME VARCHAR(255));
INSERT INTO USERS(ID, NAME) VALUES(1, 'Test User 1');
INSERT INTO USERS(ID, NAME) VALUES(2, 'Test User 2');
然后在 application.properties
里进行数据库连接的配置:
spring.h2.console.settings.web-allow-others=true
spring.h2.console.path=/h2-console
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.schema=classpath:db/user_schema.sql
spring.datasource.data=classpath:db/user_data.sql
spring.datasource.initialization-mode=always
spring.datasource.platform=h2
spring.jpa.hibernate.ddl-auto=none
当完成依赖和连接配置这两步之后,就可以在程序种使用 H2 了。Spring 会自动完成 DataSource 的注入。
同时我们可以通过 http://localhost:8080/h2-console/ 来访问 H2 的控制台:
接下来我们修改实体类 User.java
:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class User {
@Id
@Column
private Integer id;
@Column
private String name;
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
-
@Table
声明此对象映射到数据库的数据表 -
@Id
声明此属性为主键 -
@Column
声明该属性与数据库字段的映射关系。
Spring Data JPA 包含了一些内置的 Repository
,实现了一些常用的方法:findone
,findall
,save
等。我们新建一个 UserDAO.java
来进行数据库的交互:
import com.example.MyFirstSpringBoot.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDAO extends JpaRepository<User, Integer> {
}
最后修改 UserService.java
类:
@Component
public class UserService {
@Autowired
private UserDAO userDAO;
public List<User> getUsers() {
return userDAO.findAll();
}
public void addUser(User user) {
userDAO.save(user);
}
}
重新启动程序,使用效果跟上一小节 3.7 一样。
3.9 提供一个 Restful 接口
假设我们想要提供一个 Restful 接口 /user/list_user_json
返回 JSON 格式的用户数据。
新建一个 UserRestController.java
:
@RestController
@RequestMapping("/user")
public class UserRestController {
private final static Logger logger = LoggerFactory.getLogger(UserRestController.class);
@Autowired
UserService userService;
@RequestMapping("/list_user_json")
public List<User> listUserJSON() {
logger.info("list user in JSON format");
return userService.getUsers();
}
}
3.10 使用缓存 Redis
对于查询用户列表的这个服务,假设我们不希望每次都 Query 数据库,我们可以将用户列表放在缓存中,当有新用户添加时,才更新这个缓存。
这里我们使用 Redis 缓存。关于 Redis 的安装和启动,请参见 Spring 缓存开发实践 & Redis 集成。
在 pom.xml
中添加 Cache 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
修改 application.properties
,添加 Redis 相关配置:
## Redis 配置
spring.cache.type=redis
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
## 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
## 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
## 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
## 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
## 连接超时时间(毫秒)
spring.redis.timeout=0
添加一个 Redis 配置类 RedisConfig.java
:
package com.example.MyFirstSpringBoot.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean("keyGenerator")
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append("-").append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
// 缓存管理器
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory);
return builder.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
}
注意:
-
@Configuration
:相当于传统的 XML 配置文件,如果有些第三方库需要用到 XML 文件,建议仍然通过@Configuration
类作为项目的配置主类。 - 通过
@EnableCaching
注解开启缓存支持,Spring Boot 就会根据实现自动配置一个合适的CacheManager。
同时也要修改模型类 User.java
:
- 继承接口
implements Serializable
- 添加一个
toString()
方法 - 添加一个
serialVersionUID
字段
修改 UserService.java
,通过注解的方式引入 Redis 缓存:
@Component
@CacheConfig(cacheNames = "UserService")
public class UserService {
private final static Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
private UserDAO userDAO;
@Cacheable(value = "users")
public List<User> getUsers() {
logger.info("query DB to get users");
return userDAO.findAll();
}
@CacheEvict(value = "users", allEntries = true)
public void addUser(User user) {
logger.info("update DB to save new user");
userDAO.save(user);
}
}
测试结果如下:
系统启动后,多次刷新页面,调用 http://localhost:8080/user/list_user,日志如下:可以看出,只进行了一次数据库的查询操作。
通过 Redis CLI 可以看到结果已缓存到 Redis 中:其中 key 的生成方式是我们在 RedisConfig.java
中自定义的。
下面我们添加一个新用户,随后多次刷新页面,调用 http://localhost:8080/user/list_user,日志如下:可以看出,添加新用户后,重新查询数据库,更新缓存。
3.11 数据验证
在添加用户页面时,如果我们在 ID 输入框输入一个字母,会发生什么?
现在我们希望实现如下的功能:
- 增加校验
-
id
只能是数字,并且只能为正数 -
name
不能为空
-
- 如果校验不通过,返回添加用户页面,并且现实相关错误信息
在 pom.xml
中添加 Validation 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
修改实体类 User.java
,定义验证规则。更多的注解,请参见 http://www.baeldung.com/javax-validation。
@Id
@Column
@Digits(integer = 3, message = "{validation.user.id}", fraction = 0)
@Positive(message = "{validation.user.id}")
private Integer id;
@Column
@NotEmpty(message = "{validation.user.name}")
private String name;
在 /resources
目录下新建一个文件 ValidationMessages.properties
,定义错误文字信息:
# Validation
validation.user.id=ID can only be positive number
validation.user.name=Name shouldn't be empty
修改控制器 UserController.java
,增加错误处理的逻辑:
@RequestMapping("/add_user")
public String addUser(Map<String, Object> model) {
logger.info("add user page");
model.put("user", new User());
return "user/add_user";
}
@RequestMapping("/create_user")
public String createUser(Map<String, Object> model, @ModelAttribute @Valid User user, BindingResult bindingResult) {
logger.info("create user");
if (bindingResult.hasErrors()) {
logger.info("create user validation failed");
model.put("user", user);
return "user/add_user";
}
userService.addUser(user);
model.put("users", userService.getUsers());
return "user/list_user";
}
修改前端页面 add_user.ftl
,展示错误信息:
<#import "/spring.ftl" as spring />
<!DOCTYPE html>
<html>
<body>
<form action="/user/create_user" method="post">
<@spring.bind "user.id"/>
ID: <input type="text" name="id" value="${user.id!}" />
<@spring.showErrors ""/>
<br/>
<@spring.bind "user.name"/>
Name: <input type="text" name="name" value="${user.name!}" />
<@spring.showErrors ""/>
<br/>
<input type="submit">
</form>
</body>
</html>
测试效果如下:
3.12 使用消息队列
假设我们有了一个新的需求,新增一个用户后,需要为这个用户创建一个银行账号 Bank Account,而创建银行账号的服务不是同步的,它是一个异步服务,每天的早上9点到下午5点才会执行。
这里我们使用 ActiveMQ 消息队列,当新增用户 addUser(User user)
的时候,发送一条消息给队列。
关于 ActiveMQ 的安装和启动,请参见Spring 消息系统 JMS 开发实践
在 pom.xml
中添加 ActiveMQ 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
修改 application.properties
,添加 ActiveMQ 相关配置:
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.in-memory=true
spring.activemq.pool.enabled=false
## 以下的配置使得可以发送 Java Object
spring.activemq.packages.trust-all=true
添加一个 ActiveMQ JMS 配置类 JMSConfig.java
:
package com.example.MyFirstSpringBoot.config;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jms.Queue;
@Configuration
public class JMSConfig {
@Bean(name = "usersQueue")
public Queue counting() {
return new ActiveMQQueue("testing.users");
}
}
修改 UserSerive.java
,在 addUser
操作中添加发送消息的逻辑:
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Queue usersQueue;
@CacheEvict(value = "users", allEntries = true)
public void addUser(User user) {
logger.info("update DB to save new user");
userDAO.save(user);
jmsTemplate.convertAndSend(usersQueue, user);
}
如果需要从队列中获取消息,可以添加 JMSConsumer.java
:
import com.example.MyFirstSpringBoot.model.User;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class JMSConsumer {
@JmsListener(destination = "testing.users")
public void receiveQueue(User user) {
System.out.println("Consumer a user from message queue. ID = " + user.getId() + ", Name = " + user.getName());
}
}
测试效果,启动项目,添加两个用户,可以看到日志如下:
在浏览器中输入:http://127.0.0.1:8161/admin/ 默认用户名密码都为 admin。
3.13 单元测试
如果使用spring-boot-starter-test
‘Starter’(在 test
scope 内),你将发现下列被提供的库:
- JUnit - 事实上的(de-facto)标准,用于Java应用的单元测试。
- Spring Test & Spring Boot Test - 对Spring应用的集成测试支持。
- AssertJ - 一个流式断言库。
- Hamcrest - 一个匹配对象的库(也称为约束或前置条件)。
- Mockito - 一个Java模拟框架。
- JSONassert - 一个针对JSON的断言库。
- JsonPath - 用于JSON的XPath。
你可以使用 @SpringBootTest
的 webEnvironment
属性定义怎么运行测试:
-
MOCK
- 加载WebApplicationContext,并提供一个mock servlet环境,使用该注解时内嵌servlet容器将不会启动。如果classpath下不存在servlet APIs,该模式将创建一个常规的non-web ApplicationContext。 -
RANDOM_PORT
- 加载EmbeddedWebApplicationContext,并提供一个真实的servlet环境。使用该模式内嵌容器将启动,并监听在一个随机端口。 -
DEFINED_PORT
- 加载EmbeddedWebApplicationContext,并提供一个真实的servlet环境。使用该模式内嵌容器将启动,并监听一个定义好的端口(比如application.properties中定义的或默认的8080端口)。 -
NONE
- 使用SpringApplication加载一个ApplicationContext,但不提供任何servlet环境(不管是mock还是其他)。
注 不要忘记在测试用例上添加@RunWith(SpringRunner.class)
,否则该注解将被忽略。
首先,我们修改 MyFirstSpringBootApplicationTests.java
来做 Smoke Test:
import com.example.MyFirstSpringBoot.controller.UserController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Java6Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyFirstSpringBootApplicationTests {
@Autowired
private UserController userController;
@Test
public void contextLoads() {
assertThat(userController).isNotNull();
}
}
可以使用 @WebMvcTest
检测 Spring MVC 控制器是否工作正常,该注解将自动配置 Spring MVC 设施,并且只扫描注解 @Controller
,@ControllerAdvice
,@JsonComponent
,Filter
,WebMvcConfigurer
和 HandlerMethodArgumentResolver
的 beans,其他常规的 @Component
beans 将不会被扫描。
通常 @WebMvcTest
只限于单个控制器(controller)使用,并结合 @MockBean
以提供需要的协作者(collaborators)的 mock 实现。@WebMvcTest
也会自动配置 MockMvc
,Mock MVC 为快速测试MVC 控制器提供了一种强大的方式,并且不需要启动一个完整的 HTTP 服务器。
Spring Boot 提供一个 @MockBean
注解,可用于为 ApplicationContext 中的 bean 定义一个 Mockito mock,你可以使用该注解添加新 beans,或替换已存在的bean定义。
我们编写一个 UserControllerTest.java
类来测试 UserController
:
import com.example.MyFirstSpringBoot.model.User;
import com.example.MyFirstSpringBoot.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.List;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private UserService userService;
@Test
public void testExample() throws Exception {
List<User> mockUpData = Arrays.asList(new User(1, "Test User 1"), new User(2, "Test User 2"));
given(this.userService.getUsers())
.willReturn(mockUpData);
this.mvc.perform(get("/user/list_user").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk());
}
}
最后通过 mvn test
来启动测试:
3.14 安全
SpringSecurity是专门针对基于Spring项目的安全框架,充分利用了依赖注入和 AOP 来实现安全管控。
在 pom.xml
中添加 Spring Security 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果添加了 Spring Security 的依赖,那么 web 应用默认对所有的HTTP路径(也称为终点,端点,表示API的具体网址)使用 'basic' 认证。为了给 web 应用添加方法级别(method-level)的保护,你可以添加@EnableGlobalMethodSecurity
并使用想要的设置。
此时,重启服务,访问:http://localhost:8080/,Spring Security 会为我们提供一个默认的登录页面,界面如下:
默认用户名为
user
,默认密码可以在启动日志中看到:
2018-06-25 10:22:14.546 [main] INFO o.s.b.a.s.s.UserDetailsServiceAutoConfiguration -
Using generated security password: 2b1ce4e0-1603-4bee-8fd9-579917912158
用户名和密码也可以在 application.properties
中自定义配置:
spring.security.user.name=admin
spring.security.user.password=admin
默认的安全配置是通过 SecurityAutoConfiguration
,SpringBootWebSecurityConfiguration
(用于web安全),AuthenticationManagerConfiguration
(可用于非web应用的认证配置)进行管理的。
你可以添加一个 @EnableWebSecurity
bean来彻底关掉 Spring Boot 的默认配置。
想要覆盖访问规则而不改变其他自动配置的特性,你可以添加一个注解@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
的 WebSecurityConfigurerAdapter
类型的 @Bean
。
这里我们希望添加如下的权限控制功能:
- 访问首页
/
,不需要验证 - 访问静态资源,例如图片,CSS等,不需要验证
- 访问用户列表
/list_user
和添加用户页面/add_user
,需要验证
新建一个类 ApplicationSecurity.java
:
package com.example.MyFirstSpringBoot.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeRequests()
.antMatchers("/", "/images/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").loginPage("/login").permitAll()
.and()
.logout().permitAll();
http.csrf().disable();
// @formatter:on
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("{noop}user").roles("USER")
.and()
.withUser("admin").password("{noop}admin").roles("USER", "ADMIN");
}
}
- 通过
@EnableWebSecurity
注解开启 Spring Security 的功能 - 继承
WebSecurityConfigurerAdapter
,并重写它的方法来设置一些 web 安全的细节 -
configure(HttpSecurity http)
方法- 通过
authorizeRequests()
定义哪些URL需要被保护、哪些不需要被保护。 - 通过
formLogin()
定义当需要用户登录时候,转到的登录页面。
- 通过
-
configureGlobal(AuthenticationManagerBuilder auth)
方法,在内存中创建了用户。
然后创建一个我们自己的登陆页面 login.ftl
:
<!DOCTYPE html>
<html>
<body>
<#if RequestParameters['error']??>
<div style="color: red">用户名或者密码错误。</div>
<#elseif RequestParameters['logout']??>
<div style="color: red">您已成功退出登陆。</div>
</#if>
<form name="f" action="/login" method="post">
User Name: <input type="text" name="username" /><br/>
Password: <input type="password" name="password" /><br/>
<input type="submit">
</form>
</body>
</html>
同时,修改 WelcomeController.java
,增加 /login
的 mapping:
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Map<String, Object> model) {
return "login";
}
到这里,我们启用应用,并访问 http://localhost:8080/,可以正常访问。
但是访问 http://localhost:8080/list_user 或者 http://localhost:8080/add_user 的时候被重定向到了 http://localhost:8080/login 页面:
- 如果用户名密码错误,跳转到 http://localhost:8080/login?error
- 如果用户名密码正确,跳转到响应的页面
- 也可以通过访问 http://localhost:8080/login?logout 来退出登陆
如果想要停止 Security,在 ApplicationSecurityConfig.java
中将 .antMatchers("/", "/images/**").permitAll()
修改为 .antMatchers("/**").permitAll()
。
3.15 事务控制
从如下的代码中,我们可以看出,在添加用户的逻辑中,首先往数据库插入一条记录,然后往消息队列发送一条消息。
@CacheEvict(value = "users", allEntries = true)
public void addUser(User user) {
logger.info("update DB to save new user");
userDAO.save(user);
jmsTemplate.convertAndSend(usersQueue, user);
}
现在,我们通过 /activemq stop
命令停止掉 ActiveMQ 服务。然后尝试在页面上添加一个用户,在点击提交后,跳转到错误页面,并且后台日志也会显示相关错误信息:
同时查询数据库,我们发现该新用户被成功的插入到数据库中。
现在我们希望,如果没有成功发送消息到队列中 jmsTemplate.convertAndSend(usersQueue, user);
,该新用户也不能被插入到数据库中 userDAO.save(user);
。
Spring 的 AOP 即声明式事务管理默认是针对 unchecked exception 回滚,也就是默认对RuntimeException
异常或是其子类进行事务回滚;checked 异常,即 Exception
可 try{}
捕获的不会回滚。
在这个例子中,我们在方法前添加 @Transactiona
注解,默认的传播机制是 Propagation.REQUIRED
,即创建一个新的事务。
在应用系统调用声明了 @Transactional
的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional
的属性配置信息,这个代理对象决定该声明 @Transactional
的目标方法是否由拦截器 TransactionInterceptor
来使用拦截,在 TransactionInterceptor
拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager
操作数据源 DataSource
提交或回滚事务。
@CacheEvict(value = "users", allEntries = true)
@Transactional
public void addUser(User user) {
3.16 集成
Spring Boot 为 Spring 集成提供了一些便利,包括 spring-boot-starter-integration
‘Starter’。
Spring 集成提供基于消息和其他传输协议的抽象,比如HTTP,TCP等。
一个示例请参见 https://spring.io/guides/gs/integration/。
3.17 Session 会话
Spring Boot 为 Spring Session 自动配置了各种存储:
- JDBC
- MongoDB
- Redis
- Hazelcast
- HashMap
在这个例子中,我们利用 Redis 透明的存储并共享 Web 应用的 HttpSession
。
在 pom.xml
中添加 Spring Session 的依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
创建一个 HttpSessionConfig.java
:
package com.example.MyFirstSpringBoot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession
@Configuration
public class HttpSessionConfig {
}
添加 @EnableRedisHttpSession
注解即可,该注解会创建一个名字叫 springSessionRepositoryFilter
的 Spring Bean,其实就是一个 Filter,这个 Filter 负责用 Spring Session 来替换原先的默认 HttpSession
实现,在这个例子中,Spring Session 是用 Redis 来实现的 RedisHttpSession
。
如何操作 Spring Session,可以通过如下方式:
@RequestMapping("/")
public String welcome(Map<String, Object> model, HttpServletRequest req) {
req.getSession().setAttribute("testKey", "testValue");
logger.info(req.getSession().getAttribute("testKey").toString());
logger.info("welcome page");
model.put("welcomeMessage", welcomeMessage);
return "welcome";
}
下面我们通过 redis-cli
进入 Redis 命令行。
通过 keys *
可以看到相关的 Sessions:
127.0.0.1:6379> keys *
1) "spring:session:expirations:1530001440000"
2) "users::com.example.MyFirstSpringBoot.service.UserService-getUsers"
3) "spring:session:expirations:1530002760000"
4) "spring:session:sessions:3c23beb2-ea94-409e-a78c-02afa41b9e1f"
5) "spring:session:sessions:expires:3c23beb2-ea94-409e-a78c-02afa41b9e1f"
6) "spring:session:sessions:79e1809f-b0a0-4d72-a9a6-17dfdde94afe"
7) "spring:session:sessions:expires:79e1809f-b0a0-4d72-a9a6-17dfdde94afe"
然后可以通过 hkeys
来查看具体 Session 里面包含的属性:
27.0.0.1:6379> hkeys "spring:session:sessions:3c23beb2-ea94-409e-a78c-02afa41b9e1f"
1) "maxInactiveInterval"
2) "creationTime"
3) "sessionAttr:testKey"
4) "lastAccessedTime"
然后可以通过 hget
来查看具体属性的值:
27.0.0.1:6379> hget "spring:session:sessions:3c23beb2-ea94-409e-a78c-02afa41b9e1f" "sessionAttr:testKey"
"\xac\xed\x00\x05t\x00\ttestValue"
3.18 执行器 Actuator:Production-ready特性
Spring Boot 包含很多其他特性,可用来帮你监控和管理发布到生产环境的应用。你可以选择使用HTTP端点,JMX,甚至通过远程 shell 来管理和监控应用。审计(Auditing),健康(health)和数据采集(metrics gathering)会自动应用到你的应用。
spring-boot-actuator 模块提供Spring Boot所有的production-ready特性,启用该特性的最简单方式是添加spring-boot-starter-actuator
‘Starter’依赖。
在 pom.xml
中添加 Actuator 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.19 监控
执行器端点(endpoints)可用于监控应用及与应用进行交互,Spring Boot 包含很多内置的端点,你也可以添加自己的。参见 https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/V.%20Spring%20Boot%20Actuator/46.%20Endpoints.html
在 application.properties
中添加如下配置:
management.endpoints.web.exposure.include=*
重启应用,访问 http://localhost:8080/actuator/,可以看到如下 response:
{
"_links":{
"self":{
"href":"http://localhost:8080/actuator",
"templated":false
},
"auditevents":{
"href":"http://localhost:8080/actuator/auditevents",
"templated":false
},
"beans":{
"href":"http://localhost:8080/actuator/beans",
"templated":false
},
"health":{
"href":"http://localhost:8080/actuator/health",
"templated":false
},
"conditions":{
"href":"http://localhost:8080/actuator/conditions",
"templated":false
},
"configprops":{
"href":"http://localhost:8080/actuator/configprops",
"templated":false
},
"env":{
"href":"http://localhost:8080/actuator/env",
"templated":false
},
"env-toMatch":{
"href":"http://localhost:8080/actuator/env/{toMatch}",
"templated":true
},
"info":{
"href":"http://localhost:8080/actuator/info",
"templated":false
},
"loggers":{
"href":"http://localhost:8080/actuator/loggers",
"templated":false
},
"loggers-name":{
"href":"http://localhost:8080/actuator/loggers/{name}",
"templated":true
},
"heapdump":{
"href":"http://localhost:8080/actuator/heapdump",
"templated":false
},
"threaddump":{
"href":"http://localhost:8080/actuator/threaddump",
"templated":false
},
"metrics":{
"href":"http://localhost:8080/actuator/metrics",
"templated":false
},
"metrics-requiredMetricName":{
"href":"http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated":true
},
"scheduledtasks":{
"href":"http://localhost:8080/actuator/scheduledtasks",
"templated":false
},
"sessions-sessionId":{
"href":"http://localhost:8080/actuator/sessions/{sessionId}",
"templated":true
},
"sessions":{
"href":"http://localhost:8080/actuator/sessions",
"templated":false
},
"httptrace":{
"href":"http://localhost:8080/actuator/httptrace",
"templated":false
},
"mappings":{
"href":"http://localhost:8080/actuator/mappings",
"templated":false
}
}
}
健康信息
健康信息可以检查应用的运行状态,它经常被监控软件用来提醒人们生产环境是否存在问题。/health
端点暴露的默认信息取决于端点是如何被访问的。健康信息是从你的 ApplicationContext
中定义的所有 HealthIndicator beans
收集过来的。Spring Boot包含很多自动配置的 HealthIndicators
,参见 https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/V.%20Spring%20Boot%20Actuator/46.6.1%20Auto-configured-HealthIndicators.html
通过 http://localhost:8080/actuator/health 来查看健康信息:
为了查看完整的健康信息,可以在
application.properties
中添加如下配置:
management.endpoint.health.show-details=ALWAYS
再次访问 http://localhost:8080/actuator/health,返回的内容如下:
{
"status":"UP",
"details":{
"diskSpace":{
"status":"UP",
"details":{
"total":120124866560,
"free":64647090176,
"threshold":10485760
}
},
"redis":{
"status":"UP",
"details":{
"version":"4.0.10"
}
},
"jms":{
"status":"UP",
"details":{
"provider":"ActiveMQ"
}
},
"db":{
"status":"UP",
"details":{
"database":"H2",
"hello":1
}
}
}
}
也可以写自己的,例如我们创建一个 ExampleHealthIndicator.java
:。你需要实现 health()
方法,并返回一个 Health
响应,该响应需要包含一个 status
和其他用于展示的详情。
package com.example.MyFirstSpringBoot.monitor;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class ExampleHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("counter", 123).build();
}
}
再次访问 http://localhost:8080/actuator/health,返回的内容包含如下:
"example":{
"status":"UP",
"details":{
"counter":123
}
}
应用信息
应用信息会暴露所有 InfoContributor
beans收集的各种信息,Spring Boot 包含很多自动配置的InfoContributors
,你也可以编写自己的实现。
例如我们创建一个 ExampleInfoContributor.java
:
package com.example.MyFirstSpringBoot.monitor;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example", Collections.singletonMap("someKey", "someValue"));
}
}
访问 http://localhost:8080/actuator/info,返回的内容包含如下:
3.20 度量指标(Metrics)
访问 http://localhost:8080/actuator/metrics,会得到如下结果:
{
"names":[
"jvm.memory.max",
"http.server.requests",
"jdbc.connections.active",
"process.files.max",
"jvm.gc.memory.promoted",
"tomcat.cache.hit",
"system.load.average.1m",
"tomcat.cache.access",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.gc.pause",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"tomcat.global.sent",
"jvm.buffer.memory.used",
"tomcat.sessions.created",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"tomcat.global.request.max",
"hikaricp.connections.idle",
"hikaricp.connections.pending",
"tomcat.global.request",
"tomcat.servlet.request.max",
"tomcat.sessions.expired",
"hikaricp.connections",
"tomcat.servlet.request",
"jvm.threads.live",
"jvm.threads.peak",
"tomcat.global.received",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"process.uptime",
"tomcat.sessions.rejected",
"process.cpu.usage",
"tomcat.threads.config.max",
"jvm.classes.loaded",
"tomcat.servlet.error",
"hikaricp.connections.max",
"hikaricp.connections.min",
"jvm.classes.unloaded",
"tomcat.global.error",
"tomcat.sessions.active.current",
"tomcat.sessions.alive.max",
"jvm.gc.live.data.size",
"hikaricp.connections.usage",
"tomcat.threads.current",
"hikaricp.connections.timeout",
"process.files.open",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"tomcat.sessions.active.max",
"hikaricp.connections.acquire",
"tomcat.threads.busy",
"process.start.time"
]
}
选择其中一个指标,例如 tomcat.global.request
,访问 http://localhost:8080/actuator/metrics/tomcat.global.request,可以得到该指标对应的值:
{
"name":"tomcat.global.request",
"measurements":[
{
"statistic":"COUNT",
"value":13.0
},
{
"statistic":"TOTAL_TIME",
"value":1.46
}
],
"availableTags":[
{
"tag":"name",
"values":[
"http-nio-8080"
]
}
]
}
记录自己的指标:
将 CounterService
或 GaugeService
注入到你的 bean 中可以记录自己的度量指标:CounterService
暴露 increment
,decrement
和 reset
方法;GaugeService
提供一个 submit
方法。
注意:In Spring Boot 2.0, the in-house metrics were replaced with Micrometer support. Thus, we can expect breaking changes. If our application was using metric services such as GaugeService
or CounterService
they will no longer be available.
Instead, we’re expected to interact with Micrometer directly. In Spring Boot 2.0, we’ll get a bean of type *MeterRegistry *autoconfigured for us.
3.21 定时任务
关于定时任务,详细请参见 Java 定时任务 & 任务调度
SpringBoot 内置了定时任务 @Scheduled
,操作可谓特别简单。
假设我们有有一个新的需求,每天晚上24点,给用户发一个促销邮件。
首先在启动类 MyFirstSpringBootApplication
上增加一个注解 @EnableScheduling
。
随后创建一个任务类 PromotionMailTask.java
:
package com.example.MyFirstSpringBoot.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class PromotionMailTask {
private final static Logger logger = LoggerFactory.getLogger(PromotionMailTask.class);
@Scheduled(cron = "0 0 24 * * ?")
public void scheduledPromotionMailTask() {
logger.info("sending promotion mail..");
}
}
关于 con
的参数设置,请参考 https://www.cnblogs.com/softidea/p/5833248.html
更多阅读
- Spring Boot 官方示例:https://github.com/spring-projects/spring-boot/tree/2.0.x/spring-boot-samples
- Spring Boot 官方教程:https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/III.%20Using%20Spring%20Boot/17.%20Spring%20Beans%20and%20dependency%20injection.html