SpringBoot

11/28/2019 Spring

# 简介

SpringBoot是由Pivotal团队在2013年开始研发、2014年4月发布第一个版本的全新开源的轻量级框架。它基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。

IDE:Intellij IDEA

# hello world

直接使用IDEA新建Spring Initializr,然后和创建maven项目一样修改group和artifact,type为maven project(默认就是),值得一提的是可以直接使用jar方式发布到生产环境,所以packaging可以选择jar,然后next,会要求选择依赖,这里先勾选个web->spring web,以后需要可以追加,之后next-finish。

然后找到src/main/java/包名/里面应该应该会有一个这样的文件

package com.alight.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

可以直接在里面写controller 像这样:

package com.alight.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class SpringBootDemoApplication {
    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

然后打开浏览器 访问localhost:8080 (opens new window)

然后它就输出了hello world,so cool。

# 创建可执行Jar

通过创建可以在生产环境中运行的完全独立的可执行jar文件来结束示例。可执行jar(有时称为“fat jars”)是包含您的已编译类以及代码需要运行的所有jar依赖项的归档文件。

可执行Jar和Java

Java没有提供加载嵌套jar文件(jar中本身包含的jar文件)的标准方法。如果您要分发独立的应用程序,则可能会出现问题。
为了解决这个问题,许多开发人员使用uber jars。uber jar将来自应用程序所有依赖项的所有类打包到单个存档中。这种方法的问题在于,很难查看应用程序中包含哪些库。如果在多个jar中使用相同的文件名(但具有不同的内容),也可能会产生问题。
Spring Boot采用了另一种方法,实际上允许您直接嵌套jar。

要创建可执行jar,我们需要将添加spring-boot-maven-pluginpom.xml。为此,请在该dependencies部分下方插入以下行:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
1
2
3
4
5
6
7
8

如果刚才是使用上述方式创建的项目,则应该会自动添加该插件

在项目根目录输入mvn package

此时,如果查看target目录,则应该看到spring-boot-demo-0.0.1-SNAPSHOT.jar。该文件的大小应为10 MB左右。如果您想窥视内部,可以使用jar tvf``

$ jar tvf target / spring-boot-demo-0.0.1-SNAPSHOT.jar
1

您还应该在target目录中看到一个更小的文件spring-boot-demo-0.0.1-SNAPSHOT.jar.original。这是Maven在Spring Boot重新打包之前创建的原始jar文件。

要运行该应用程序,请使用以下java -jar命令:

$ java -jar target / spring-boot-demo-0.0.1-SNAPSHOT.jar 
1

和以前一样,要退出该应用程序,请按ctrl-c。

# 格式化JSON时间

在REST API中,返回的时间(json)将会是标准时间,加上时区等一大串字符,可读性会比较差。通常情况下,我们希望返回一个可读性比较强的时间,因此需要一些简单的配置。

spring
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
1
2
3
4

通过以上配置,返回的时间格式可读性明显提升。

# Hot Swap

弃坑,并不好用,经常出现更新不及时等其他问题。

总结:不如手动重新启动省心 - -

厌倦了在idea每次修改代码后都需要重新启动了?

ok,是时候使用热插拔(有的时候也有人叫热交换,热部署,whatever,总之都差不多)来简化你的开发了。(当你觉得花了太多时间在等待时,你就应该考虑了解新技术来简化这个过程了)

环境依然是springboot+idea

首先你需要导入spring-boot-devtools,没有加上版本是因为通常都配置了spring-boot-starter-parent

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    <scope>true</scope>
</dependency>
1
2
3
4
5
6

同时还需加上插件配置

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!--如果要使用构建插件从Maven或Gradle重新启动,则必须将forking设置保留为enabled-->
        <fork>true</fork>
    </configuration>
</plugin>
1
2
3
4
5
6
7
8

之后打开IDEA设置,搜索compiler找到Build project automatically勾选。

再然后在IDEA中使用快捷键 Ctrl+Shift+Alt+/,点击Registry...,找到 compiler.automake.allow.when.app.running,勾选--->close。

重启进行测试,发现功能正常生效,但是每次修改都需等待片刻(目测5s+)才能自动重新部署。

注意,如果修改导致代码出现错误而导致应用自动退出,则仍需要手动重启。

配合浏览器livereload插件食用效果更佳

更多参考

https://www.jetbrains.com/help/idea/spring-boot.html#application-update-policies (opens new window)

https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-devtools (opens new window)

# PathVariable

路径参数可以说是十分常见的功能了。

这里简单贴个demo

@GetMapping("products/{id}")
@ApiOperation("通过id获取指定产品信息")
public Product getOne(@PathVariable String id){
    return ProductService.getById(id);
}
1
2
3
4
5

# 全局异常捕获

创建一个如下所示的配置类即可,可自行添加特殊捕获处理的异常

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class) // 可换成更细粒度的异常捕获
    public ResponseEntity error(Exception e) {
        e.printStackTrace();
        return ResponseEntityFactory.build(HttpStatus.BAD_REQUEST, e.getCause().getMessage());
    }

}
1
2
3
4
5
6
7
8
9
10

404异常捕获(未存在的url)需要添加以下配置

application.yml

spring:
  mvc:
    throw-exception-if-no-handler-found: true # non-existed url will throw exception
1
2
3

然后自定义error控制器

@RestController
@RequestMapping("/")
public class BaseController implements ErrorController {

    @RequestMapping("error")
    public ResponseEntity error() {
        // return anything you want
        return ResponseEntityFactory.build(HttpStatus.NOT_FOUND);
    }

    @Override
    public String getErrorPath() {
        return "error";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 参数校验

在 Spring Boot 2.3 1前,spring-boot-starter-web自带spring-boot-starter-validation,如果是没有自带的话,则需要自己添加

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1
2
3
4

在controler中需要校验的参数前加上 @valid 注解

@PostMapping("products")
public ResponseEntity insert(@Valid Products products) {
    ...
}
1
2
3
4

除了实体类,单个参数也可以直接使用该注解并加上想应约束注解即可

然后在相关实体类加上相应的注解进行约束即可

常见的注解
注解 描述
@Null 必须为Null
@NotNull 不能为Null
@AssertTrue 必须为True
@AssertFalse 必须为false
@Min(value) 数字类型的最小值
@Max(value) 数字类型的最大值
@DecimalMin(value) 数字类型的最小值
@DecimalMax(value) 数字类型的最大值
@Size(max=, min=) 字符或者集合的大小区间
@Digits (integer, fraction) 数字取值区间
@Past 必须是过去的时间
@Future 必须是将来的时间
@Pattern(regex=,flag=) 正则匹配
@NotBlank(message =) 字符串非Null,且trim之后大于0
@Email 邮件地址
@Length(min=,max=) 字符串长度范围
@NotEmpty 字符串非空
@Range(min=,max=,message=) 元素范围限制

# 统一返回结果

Spring自带的ReponseEntity+HttpStatus十分好用。

备份一个RepsoneEntityFactory
public class ResponseEntityFactory {

    private ResponseEntityFactory() {
    }

    public static ResponseEntity build(HttpStatus httpStatus, Object data) {
        Map resultMap = null;
        if (data instanceof Map) {
            resultMap = (Map) data;
        } else {
            resultMap = new HashMap();
            resultMap.put("data", data);
        }
        resultMap.put("code", httpStatus.value());
        resultMap.put("msg", httpStatus.getReasonPhrase());
        return new ResponseEntity(resultMap, httpStatus);
    }

    public static ResponseEntity build(HttpStatus httpStatus, String msg, Object data) {
        Map resultMap = null;
        if (data instanceof Map) {
            resultMap = (Map) data;
        } else {
            resultMap = new HashMap();
            resultMap.put("data", data);
        }
        resultMap.put("code", httpStatus.value());
        resultMap.put("msg", msg);
        return new ResponseEntity(resultMap, httpStatus);
    }

    public static ResponseEntity build(HttpStatus httpStatus) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", httpStatus.value());
        resultMap.put("msg", httpStatus.getReasonPhrase());
        return new ResponseEntity(resultMap, httpStatus);
    }

    public static ResponseEntity build(HttpStatus httpStatus, String msg) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", httpStatus.value());
        resultMap.put("msg", msg);
        return new ResponseEntity(resultMap, httpStatus);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 日志系统

SpringBoot默认采Logback进行日志记录,并且默认输出info级别的日志到控制台。

日志级别分为以下几个,ERRORWARNINFODEBUGFATALOFFTRACE.日志等级越高,输出的日志越多,比如在info级别下,会同时输出info、warn、error三个级别的日志。切换等级只需要在application.xml配置文件指定logging.level.root = xxx即可(yaml同理)。

for example:

logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
1
2
3

TRACE级别可以追踪到web容器的日志

Logback没有FATAL等级

文件日志默认是没有打开的(默认输出到控制台),需要手动在配置文件指定logging.file.name,它可以是一个绝对路径,也可以是相对路径。

默认情况下只会保存7天日志,相关的配置为logging.file.max-history

默认情况下日志文件的大小限制为10MB,相关的配置为logging.file.max-size

logging.file.total-size-cap:该属性用于限制压缩文件的总大小。(隔天启动发现前一天的日志被放进.gz压缩包。)

logging.file.clean-history-on-start:清除历史日志在应用启动时。

日志的配置文件与实际的日志基础结构无关。因此,Spring Boot不会管理特定的配置文件(例如Logback的logback.configurationFile)。

日志分组

举个例子,将tomcat的日志归到一组,包名需要自己配置,设置日志级别为TRACE.

logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
logging.level.tomcat=TRACE
1
2

SpringBoot默认包含了web和sql组,他们包含的包如下所示:

Name Loggers
web org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

日志关闭钩子(Log Shutdown Hook),可以让你在启动程序结束立即关闭日志记录。

logging.register-shutdown-hook=true
1

自定义日志配置文件,Logback的自定义配置文件名为logback-spring.xml,更多参考boot-features-custom-log-configuration (opens new window)

仿springboot默认日志格式
  1. 添加pid转换器

ProcessIdConverter

public class ProcessIdConverter extends ClassicConverter {

    @Override
    public String convert(ILoggingEvent iLoggingEvent) {
        return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
    }
}

1
2
3
4
5
6
7
8
  1. 注册pid key
<conversionRule conversionWord="pid"
                    converterClass="xyz.ayay.servicebase.config.ProcessIdConverter" />
1
2
  1. 修改pattern
%yellow(%date{yyyy-MM-dd HH:mm:ss.SSS})  %highlight(%-5level) %magenta(%pid) --- [%15.15thread]  %-60.60cyan(%logger{40}) : %m%n
1

同时Spring默认提供了大量的Logback扩展,详情参考boot-features-logback-extensions (opens new window)

具体在SpringBoot中,只需要在类头使用@Slf4j注解,就可以在类中使用log.xxx记录日志了