您的当前位置:首页正文

Spring Boot 2.0 学习笔记及由浅入深的代码示例

来源:华拓网

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配置。

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 下。

Spring Boot CLI安装

2. 一个 Spring Boot 示例 MyFirstSpringBoot

通过 https://start.spring.io/ 来生成基础代码

将代码下载到本地并解压缩,使用 IDE 打开,这里我们使用 IntelliJ IDEA,结构如下:


IntelliJ IDEA 打开 Spring Boot 示例项目

我们也可以通过 IntelliJ IDEA(社区版 Community) 的 Spring Assistant 插件来构造项目结构:


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 命令,可以看到该项目具体的依赖树:

MyFirstSpringBoot 的依赖树

修改 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 来启动这个项目:

mvn spring-boot:run
可以看出,run 是定义在 spring-boot-maven-plugin 这个依赖中的。 mvn spring-boot:run
可以看出启动了 8080 端口 ,因此可以通过 来访问:
通过 http://localhost:8080/ 来访问

我们也可以通过 mvn package 创建可执行 jar 包,并通过 jar 包来运行启动:

生成在 target 目录下 通过 java -jar 来运行启动

3. 扩展成一个 Web MVC 的应用

现在我们一步一步扩展上面的项目,并将其扩展为一个完整的 Web MVC 的应用。
注意,请将程序入口 MyFirstSpringBootApplication.java 放在 src 的根目录,如下所示,这样 Spring 就会扫描根目录及所有的子包,例如 /controller/model/service

程序入口 MyFirstSpringBootApplication.java 放在 src 的根目录

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

JSP 页面的位置
<!DOCTYPE html>

<%@ taglib prefix="spring" 
<%@ taglib prefix="c" 

<html lang="en">

<body>
    ${welcomeMessage}
</body>

</html>

新建第一个 WelcomeController.java,新建一个 package 名为 controller

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

新建一个模板文件 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 错误处理如下:

默认的404错误处理
如果想为某个给定的状态码展示一个自定义的 HTML 错误页面,你需要将文件添加到 /error 文件夹下。错误页面既可以是静态HTML(比如,任何静态资源文件夹下添加的),也可以是使用模板构建的,文件名必须是明确的状态码或一系列标签。
404错误页面的位置
<html>
<head><title>404</title></head>

<body>
<div style="color: red">Sorry, we can't find it.</div>
</body>
</html>
自定义的404错误处理

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的类名(缩写)。
  • 日志信息。

这里我们选择集成 Logback。Spring Boot包含很多有用的Logback扩展,你可以在 logback-spring.xml 配置文件中使用它们。
首先在 /resources 目录下新建一个文件 logback-spring.xml

Logback 配置文件
<?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 

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.ftladd_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 连接数据库

<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.sqluser_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 的控制台
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,实现了一些常用的方法:findonefindallsave等。我们新建一个 UserDAO.java 来进行数据库的交互:

新建一个 UserDAO
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();
    }
}
一个 Restful 接口

3.10 使用缓存 Redis

<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);
    }
}
只进行了一次数据库的查询操作

通过 Redis CLI 可以看到结果已缓存到 Redis 中:其中 key 的生成方式是我们在 RedisConfig.java 中自定义的。

结果已缓存到 Redis 中 添加新用户后,重新查询数据库,更新缓存

3.11 数据验证

在添加用户页面时,如果我们在 ID 输入框输入一个字母,会发生什么?


在 ID 输入框输入一个字母 错误信息

现在我们希望实现如下的功能:

  • 增加校验
    • id 只能是数字,并且只能为正数
    • name 不能为空
  • 如果校验不通过,返回添加用户页面,并且现实相关错误信息

pom.xml 中添加 Validation 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@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) 的时候,发送一条消息给队列。

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 
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 

@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());
    }

}

测试效果,启动项目,添加两个用户,可以看到日志如下:


消息被读取 ActiveMQ 控制台

3.13 单元测试

如果使用spring-boot-starter-test ‘Starter’(在 test scope 内),你将发现下列被提供的库:

  • - 事实上的(de-facto)标准,用于Java应用的单元测试。
  • & Spring Boot Test  - 对Spring应用的集成测试支持。
  • - 一个流式断言库。
  • - 一个匹配对象的库(也称为约束或前置条件)。
  • - 一个Java模拟框架。
  • - 一个针对JSON的断言库。
  • - 用于JSON的XPath。

你可以使用 @SpringBootTestwebEnvironment 属性定义怎么运行测试:

  • 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@JsonComponentFilterWebMvcConfigurerHandlerMethodArgumentResolver 的 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

UserControllerTest.java
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 并使用想要的设置。

默认的登录页面
默认用户名为 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

默认的安全配置是通过 SecurityAutoConfigurationSpringBootWebSecurityConfiguration(用于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";
    }
  • 如果用户名密码错误,跳转到
  • 如果用户名密码正确,跳转到响应的页面
  • 也可以通过访问 来退出登陆

如果想要停止 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 异常,即 Exceptiontry{} 捕获的不会回滚。

在这个例子中,我们在方法前添加 @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 集成

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 监控

management.endpoints.web.exposure.include=*
{
   "_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
      }
   }
}

健康信息

健康信息
为了查看完整的健康信息,可以在 application.properties 中添加如下配置:
management.endpoint.health.show-details=ALWAYS
{
   "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 

@Component
public class ExampleHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        return Health.up().withDetail("counter", 123).build();
    }

}
      "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 

import java.util.Collections;

@Component
public class ExampleInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("example", Collections.singletonMap("someKey", "someValue"));
    }

}
image.png

3.20 度量指标(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",
      
      "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"
   ]
}
{
   "name":"tomcat.global.request",
   "measurements":[
      {
         "statistic":"COUNT",
         "value":13.0
      },
      {
         "statistic":"TOTAL_TIME",
         "value":1.46
      }
   ],
   "availableTags":[
      {
         "tag":"name",
         "values":[
            "http-nio-8080"
         ]
      }
   ]
}

记录自己的指标:
CounterServiceGaugeService 注入到你的 bean 中可以记录自己的度量指标:CounterService暴露 incrementdecrementreset 方法;GaugeService 提供一个 submit 方法。

3.21 定时任务

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 

@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..");
    }

}

更多阅读

  • Spring Boot 官方示例:
  • Spring Boot 官方教程: