前言
什么是Spring Boot?为什么要学Spring Boot?
Spring 的诞⽣是为了简化Java 程序的开发的,⽽Spring Boot 的诞⽣是为了简化Spring 程序开发
的。Spring就像汽车,相比以前人只能其自行车走路,汽车可以帮助人们更快,更省力地去出行。而Springboot就像是现在的智能汽车,让汽车的驾驶更加的方便,智能,省心。智能汽车并没有改变其是个汽车本身,正如Spring Boot也没有脱离Spring。
Spring Boot 优点
- 快速集成框架,Spring Boot 提供了启动添加依赖的功能,⽤于秒级集成各种框架。
- 内置运⾏容器,⽆需配置Tomcat 等Web 容器,直接运⾏和部署程序。
- 快速部署项⽬,⽆需外部容器即可启动并运⾏项⽬。
- 可以完全抛弃繁琐的XML,使⽤注解和配置的⽅式进⾏开发。
- ⽀持更多的监控的指标,可以更好的了解项⽬的运⾏情况。
Spring相较于Spring最核心的升级在于,可以快速添加依赖,内置了web容器,而且Spring提供了自动装配。
一、Sprng Boot插件的安装
首先我们仍然需要安装一个插件 Spring Boot Helper ,这个软件安装后 变为Spring Initialize and Assistant
我们可以从IDEA的插件页面去选择安装,也可以直接去这个网页上去下载
https://plugins.jetbrains.com/plugin/18622-spring-boot-helper
我们下载好这个包后,会发现文件中有一个Spring assistant.jar的jar包,我们把他导入到我们的IDEA中即可
二、 Spring Boot 项⽬创建
1 使⽤Idea 创建
我们会发现这里已经和我们学Spring的时候一样创建了很多文件了
这里灾说一下,如果我们在一开始创建项目的时候如果没有引入lombook依赖,后面又想引入,可以直接去修改xml文件,也可以
安装一个插件EditStarters
然后在xml文件里点生成,选择editStarter,后进行引用依赖。
但是此时并没有初始化好,我们还需要对这个项目设置框架,我们选择maven项目框架
然后就是等待初始化完成(等待的过程是漫长的)
创建了main函数的时候就创建好了
2.Spring目录构成
蓝色图标的java文件夹是用于存放用户的java源代码的
resource文件用来存放资源文件(给前端使用的文件(HTML,CSS,JS)比如说着这里的static 和templates就是前端的文件夹;后端的配置文件)
绿色的java的文件,是所有的单元测试的根路径,也就是这个文件里面的文件就都是测试用的。(代码开发者的测试)
.idea文件是我们IDEA的本地配置文件,这个只和我们I电脑上的装得这个IDEA有关,实际在git上传的时候是没有这个文件的。
.mvn文件是mavend的配置文件,这个文件用不到了,删除掉都可以
.gitignore记录了当前git的设置
HELP.md这是一个说明文档,没啥用
mvnw.cmd;mvnw都是maven的文件,都可以删除
3.创建第一个sayHi程序
首先说一下demo文件下的第一个类DemoApplication
这是我们程序的启动类,main函数的所在,我们将这个类添加@SpringBootApplication修饰,就表示这是一个我们程序的入口,mian函数会调用这个类的run方法,启动程序。
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
//这个类是Spring项目的启动类
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
我们在demo文件夹下创建TestController类,和我们的启动类平级,因为Controller层是访问控制层,主要用于用户信息的校验,所以我们实现基础功能就是用户名默认设置为“默认值”然后打印一个你好默认值。
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller//表示这是一个Controller,controller是直接与前端打交道的层次,主要用于访问检测
@ResponseBody//这表明这个类返回(响应)的是一个数据(body),而不是一个页面。
public class TestController {@RequestMapping("/hi")//url路由注册//在servlet中我们是通过WebServlet注解修饰一个类实现的,一个类唯一对应一个HTTP请求。而在Spring Boot中一个RequestMapping修饰的是方法。public String sayHi(String name ){//如果登录名是空的,默认采用默认值//1。可以用为空或者为null来判断
// if(name == null || name.equals("")){
// name = "默认值";
// }//也可以用haslength来判断if(!StringUtils.hasLength(name)){name = "默认值";}return "你好:"+name;}
}
所以我们这里运行Mian函数,但是我这里第一次运行的时候报错了,显示Maven错误:模块的Maven项目配置不可用。但是很明显,我对这个项目是配置了maven的。这里需要找到pom 文件,然后去连接一下maven就好了
当我们点击运行的时候,控制台出现下面的情况的时候
就证明编译成功了。
我们访问127.0.0.1:8080/hi的时候就会出现下面的页面
同时输入键值对得到下面的页面。
我们也拿Fideller抓包看一下这两次的请求与响应
用网页版去创建Spring Boot 项目
不使⽤Idea 也可以创建Spring Boot 项⽬,我们可以使⽤Spring 官⽅提供的⽹⻚版来创建Spring Boot 项⽬。
⽹⻚版创建项⽬先访问:https://start.spring.io,如下图所示:
设置和之前是一样的
点击之后我们浏览器就会下载一个jar包的压缩包
我们把这个jar解压并且放入一个路径中(本质上什么路径都可以,但是还是建议放到常用的代码路径底下),然后容iead打开,我这里将这个文件改为demo3,然后打开的
有小伙伴可能会报这样的错误,这是因为我们的idea版本和我们创建的maven项目的maven版本不匹配导致的,我们直接直接用idea自带的maven来打开。
这样应该就没有问题了。
这里还要一些问题要说明
如果我把TestController这个类不放在demo目录下(比如说放到com目录下),那么我运行的时候,项目还是可以正常启动,但是我通过127.0.0.1:8080/hi这个url是没有办法访问的了。
这就是Spring的特性:Spring Boot中所有的类如果要加载(存储到)到Spring Boot框架下,就必须与我们main函数所在的类平级或者在其之下(也就是Spring Boot通main函数启动的话,只会去扫描启动类所在的文件夹及其子文件夹)
以上情况反应了Spring Boot 项⽬的另⼀个特点:约定⼤于配置。
对⽐Spring 的项⽬我们也可以看到这⼀特点,⽐如在Spring 中也是要配置Bean 的扫描路径的,⽽ Spring Boot 则不需要
三、 SpringBoot配置文件
Spring的配置文件只有两种:一种系统的配置文件,比如说设置端口、连接数据库的配置(这是Spring设置好的,用户必须按照固定的根式要求去写)、还有一种用户自定义的配置文件
3.1配置⽂件的格式 propeties and yml
Spring 配置文件的格式主要有两种:properties,和 yml
在我们的SpringBoot项目中就有配置文件,在resouce目录底下的application.properties
比如说我们在配置文件里修改这个程序的默认端口号为8888
我们运行程序会发现,日志中确实端口号变成了8888,并且此时访问的url要改成127.0.0.1:8888/hi才可以访问
同时我们也可以这样
我们在resource下新建一个yml文件,注意这个文件也必须命名为application.yml文件,我们在这个文件里去修改一个端口号,yml修改端口号与properities文件语言并不相同,这个文件用的是json格式的文件。
点击执行后发现,端口号也修改了
那么问题就在于为毛需要两种配置文件
这就好像连锁店⾥⾯的统⼀服装⼀样,有两种不同的款式,properties 类型的配置⽂件就属于⽼款“服 饰”,也是创建Spring Boot 项⽬时默认的⽂件格式(主要是由于仓库⾥还有库存),⽽yml 属于新版 款式,如果⽤户了解情况直接指定要新款服饰,那么就直接发给他。
特殊说明
- 理论上讲properties 可以和yml ⼀起存在于⼀个项⽬当中,当properties 和yml ⼀起存在⼀个项 ⽬中时,如果配置⽂件中出现了同样的配置,⽐如properties 和yml 中都配置了“server.port”, 那么这个时候会以properties 中的配置为主,也是.properties 配置⽂件的优先级最⾼,但加载 完.properties ⽂件之后,也会加载.yml ⽂件的配置信息。
- 虽然理论上来讲.properties 可以和.yml 共存,但实际的业务当中,我们通常会采取⼀种统⼀的配 置⽂件格式,这样可以更好的维护(降低故障率)。这就好像连锁店的服饰⼀样,不管是⽼款的服 装还是新款的服装,⼀定要统⼀了才好看。
3.2 properties 基本语法
1.设置系统配置
properties 是以键值的形式配置的,key 和value 之间是以“=”连接的,如:
# 配置项⽬端⼝号和数据库接口
server.port=8084
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/base1?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=120198087zbh
2.设置自定义配置
除了设置系统配置之外,我们还可以设置自己的自定义配置,在application.propeties中我们可以设置变量mtTest = 你好,这是json格式的变量
3.获取配置
Spring中使用注解value来获取配置文件的值
我们在测试类下创建String类型的myTest,用于接收读取配置的结果。
package com.example.demo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller//表示这是一个Controller,controller是直接与前端打交道的层次,主要用于访问检测
@ResponseBody//这表明这个类返回(响应)的是一个数据(body),而不是一个页面。
public class TestController {//@Value("${myTest}")private String myTest;@RequestMapping("/getconfig")public String getCinfig(){return myTest;}
}
@Value 注解使⽤“${}”的格式读取
我们启动服务器,访问http://127.0.0.1:8088/getconfig但是发现页面显示的并不是你好,而是
但是很遗憾的是这里是乱码,这是因为SprinBoot在获取变量的时候没有设置编码格式
3.3properties 缺点分析
properties 配置⽂件中会有很多的冗余的信息,相比之下yml要简单很多
3.4 YML
yml 是YAML 是缩写,它的全称Yet Another Markup Language 翻译成中⽂就是“另⼀种标记语⾔”。
yml 优点分析
● yml 是⼀个可读性⾼,写法简单、易于理解,它的语法和JSON 语⾔类似。
● yml ⽀持更多的数据类型,它可以简单表达清单(数组)、散列表,标量等数据形态。它使⽤空⽩ 符号缩进和⼤量依赖外观的特⾊,特别适合⽤来表达或编辑数据结构、各种配置⽂件等。
● yml ⽀持更多的编程语⾔,它不⽌是Java 中可以使⽤在Golang、PHP、Python、Ruby、 JavaScript、Perl 中。这可能是yml现在盛行的原因。
3.5 yml基本语法
yml 是树形结构的配置⽂件,它的基础语法是“key: value”,注意key 和value 之间使⽤英⽂冒汗加空 格的⽅式组成的,其中的空格不可省略。
ym获取配置文件依旧可以使用“${}”
package com.example.demo;
import com.oracle.webservices.internal.api.message.PropertySet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
public class TestController {//@Value("${mytest}")private String mytest;@RequestMapping("/getconfig")public String getConfig(){return mytest;}}
注意yml书写配置的时候是有明显的缩进要求的,比如说配置数据库链接的时候是这样书写的
#自定义配置项
mytest: 李四#系统配置项
Spring:datasource:url: jdbc:mysql://127.0.0.0:3306/base1?characterEncoding=utf8username: rootpassword: root
yml的优点是代码的可读性会强一点,但是需要缩进,缩写书写可能不太方便,此外yml获取中文字符值是没有propeties的乱码问题的。
3.6解决propeties中文乱码问题(没解决)
那么如果我们非要用propeties,那么怎么解决中文乱码问题呢?
我们打开application.propeties文件,会发现他的下面编码用的并不是UTF-8而是ISO8856
我们先将propetites文件设置成UTF-8格式
这样修改过后编译一下会发现
居然还是乱码
哭唧唧,有可能是代码缓存问题,我们删除掉目录里面的target文件再去编译一次看看。
但是还是不行。。。。。
所以这边建议用yml吧,解决问题最好的方式就是把问题放在一遍。
3.6yml使用进阶
1.yml中使用单引号和双引号
yml中单引号,双引号,不加引号是不同的。我们先来看案例
yml文件中有
#字符串
myString1: 你好,世界
myString2: "你好,世界"
myString3: '你好,\n世界'
myString4: "你好,\n世界"
myString5: 你好,\n世界
在TestController文件中我们获取yml中五个配置的值,并在这个类构造的时候(初始化的时候打印到控制台,结合之前知识点,是先注入属性再初始化)。
package com.example.demo;
import com.oracle.webservices.internal.api.message.PropertySet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.PostConstruct;@Controller//表示这是一个Controller,controller是直接与前端打交道的层次,主要用于访问检测
@ResponseBody//这表明这个类返回(响应)的是一个数据(body),而不是一个页面。
//@PropertySource(value = "application.properties",encoding = "utf8")
public class TestController {@Value("${myString1}")private String myString1;@Value("${myString2}")private String myString2;@Value("${myString3}")private String myString3;@Value("${myString4}")private String myString4;@Value("${myString5}")private String myString5;//调用构造函数的时候发出通知@PostConstructpublic void PostConstruct(){System.out.println("myString1"+myString1);System.out.println("myString2"+myString2);System.out.println("myString3"+myString3);System.out.println("myString4"+myString4);System.out.println("myString5"+myString5);}
}
从上述结果可以看出:
● 字符串默认不⽤加上单引号或者双引号。
● 单引号会转义特殊字符,特殊字符最终只是⼀个普通的字符串数据。
● 双引号不会转义字符串⾥⾯的特殊字符;特殊字符会作为本身想表示的意思。
2.yml配置对象
yml 配置对象有两种写法,一种是采用缩进的方式进行,一种是采用行内块的形式
#配置对象
#写法1
student1:name: 李四age: 18
#写法2
student2: {name: 张三,age: 18}
这个时候就不能⽤@Value 来读取配置中的对象了,此时要使⽤另⼀个注解 @ConfigurationProperties 来读取
首先我们要想从Spring中获取这个类,就一定需要先创建一个类用于作为这个类的容器
所以我们定义这样一个Student类(注意类名没有必要和对象名一摸一样,只需要ConfigurationProperties()里面的值和对象名一样就可以)。
package com.example.demo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
//主要要想读取到配置文件的对象,要构建一个实体类,用于接收配置文件里的类,
//所以 这个类的类名必须与我们在配置文件里定义的对象名一致
@Component//用component注解表明这可以随着项目的启动而注Spring
@ConfigurationProperties("student1")//表示这是properties文件里面的对象student1;
@Data//用Data修饰的类就自动重写了get,set 构造方法,tostring方法
public class Student {private String name;private int age;
}
那么我们TestController类中使用一下
package com.example.demo;
import com.oracle.webservices.internal.api.message.PropertySet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.PostConstruct;
@Controller//表示这是一个Controller,controller是直接与前端打交道的层次,主要用于访问检测
@ResponseBody//这表明这个类返回(响应)的是一个数据(body),而不是一个页面。public class TestController {@Autowiredprivate Student student;@PostConstructpublic void PostConstruct(){System.out.println(student);}
}
我们点击运行就会的得到我们想要的结果
注意要想在别的类(TestController)里调用配置文件里面的对象(student1),首先这个对象对应的容器类(我自己取得名字Stiudent)一定需要:
- 被ConfigurationProperties修饰
- 此实体类属性名要和配置中的key保持一致,并且提供setter和getter方法(可以用@Data修饰)
3.yml配置集合
#配置数组(集合)
dbtypes:name:-mysql-oracle-sqlserver-postgresql#行内写法dbtypes:{name:[mysql,oracle,sqlserver,postgresql]}
这个代码的意思是key值是dbtyoes value是一个数组,数组的名字是name,数组里面的元素有 mysql oracle sqlsever postgresql。
集合的读取和对象⼀样,也是使⽤@ConfigurationProperties 来读取的,具体实现如下:
@Component
@ConfigurationProperties("dbtypes")
@Data
ublic class ListConfig {
private List<String> name;
}
4.yml配置环境
我们在实际开发中,比如说开发环境和测试环境肯定是不一样的,所以测试环境需要一个配置文件,开发环境也需要一个配置环境,一般程序开发到上线需要三个环境:
开发环境(dev):开发环境是专门用于开发的服务器,配置可以比较随意,为了开发调试方便。
测试环境(test):一般是克隆一份生产环境的配置,一个程序在测试环境工作不正常。
生产环境(prod):是值正式提供对外服务的,一般会关掉错误报告,打开错误日志。
比如我们创建application-dev.yml这就是开发时需要用的配置文件;application-prod.yml配置文件这就是生产环境的测试文件,我们在application.yml中去设置使用哪一个配置文件
一般在设置application.properties中spring.profiles.active=dev时,则此时启动连接的是dev环境。
5.properties 与yml的对比
properties 是以key=value 的形式配置的键值类型的配置⽂件,⽽yml 使⽤的是类似json 格式的 树形配置⽅式进⾏配置的,yml 层级之间使⽤换⾏缩进的⽅式配置,key 和value 之间使⽤“: ”英⽂冒号加空格的⽅式设置,并且空格不可省略。
● properties 为早期并且默认的配置⽂件格式,但其配置存在⼀定的冗余数据,使⽤yml 可以很好的 解决数据冗余的问题。
● yml 通⽤性更好,⽀持更多语⾔,如Java、Go、Python 等,如果是云服务器开发,可以使⽤⼀份
配置⽂件作为Java 和Go 的共同配置⽂件。
● yml ⽀持更多的数据类型。
四、SpringBoot 的日志文件
日志有多重要,相比这个不用我多说把!
4.1⾃定义⽇志打印
开发者⾃定义打印⽇志的实现步骤:
● 在程序中得到⽇志对象。
● 使⽤⽇志对象的相关语法输出要打印的内容。
在程序中获取⽇志对象需要使⽤⽇志⼯⼚LoggerFactory
private static Logger logger ==LoggerFactory.getLogger(UserController.class );
这里要注意:Logger 对象是属于org.slf4j 包下的,不要导⼊错包。因为Spring Boot 中内置了⽇志框架slf4j,所以咱们可以直接在程序中调⽤slf4j 来输出⽇志。
我们先来看一个例子
我们先定义一个类UserController
package com.example.demo.UserController;
//
import org.slf4j.Logger;//这个包一定不能引用错了,Logger很多包里都有,我们这里一定要引用slf4的
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@ResponseBody//用来设置当前类中所有的方法返回的是数据而非页面
public class UserController {//1.得到日志对象private static final Logger logger = LoggerFactory.getLogger(UserController.class);//2.使用日志对象提供的方法区打印日志@RequestMapping("/logger")///用来作为URLpublic String sayHi(){//写日志logger.trace("我是痕迹日志");logger.debug("我是调试日志");logger.info("我是信息日志");logger.warn("我是警告日志");logger.error("我是错误日志");return "hi";}
}
结果
4.2 ⽇志级别
1. ⽇志级别有什么⽤?
● ⽇志级别可以帮你筛选出重要的信息,⽐如设置⽇志级别为error,那么就可以只看程序的报错⽇志了,对于普通的调试⽇志和业务⽇志就可以忽略了,从⽽节省开发者信息筛选的时间。
● ⽇志级别可以控制不同环境下,⼀个程序是否需要打印⽇志,如开发环境我们需要很详细的信息, ⽽⽣产环境为了保证性能和安全性就会输⼊尽量少的⽇志,⽽通过⽇志的级别就可以实现此需求。
2. ⽇志级别的分类与使⽤
⽇志的级别分为:
● trace:微量,少许的意思,级别最低;
● debug:需要调试时候的关键信息打印;
● info:普通的打印信息(默认⽇志级别);
● warn:警告,不影响使⽤,但需要注意的问题;
● error:错误信息,级别较⾼的错误⽇志信息;
● fatal:致命的,因为代码异常导致程序退出执⾏的事件。
4.3⽇志格式说明
日志的设置
越往上接收到的消息就越少,当程序中设置了日志级别之后,那么程序就会只打印和设置相同和大于当前日志级别的日志,小于当前级别的日志不会输出。如设置了warn 就只能收到warn、error、fatal 级别的⽇志了。
4.4日志框架
这就是SpringBoot内置的日志框架。
4.5日志级别的设置
日志级别的设置是非常灵活的,在实际开发中我们需要根据不同的环境(生产环境,开发环境,测试环境使用不同的日志级别设置)。
比如说我现在进入开发环境,我需要debug即以上的所有日志,那么我就在
applicaion.yml中设置
spring.profiles.active: dev
在application-dev.yml中设置
logging:level:root: debug
root:debug是全局根目录,换句话说所有的文件的日志级别都设置为debug.但是在实际开发中我可能每一个文件都有一个单独的日志级别,那么就可以这样设置
logging:level:root: errorcom:example:demo:UserContrller: trce
4.6日志的持久化(保存日志到磁盘)
1.设置日志的保存目录
我在application-dev.propeties文件下写入,日志保存在D盘的java_code文件下
logging:level:root: errorcom:example:demo:UserContrller: tracefile:path: D:\java_code
我们会发现D盘java_code文件下确实有一个log文件
打开里面就记录着日志
实际上一个日志文件并不是无限大的,因为如果是一个运行着的程序,产生的配置文件是非常多的,如果只用一个文件存,这个文件将非常大,同时也非常不利于程序员寻找日志,配置文件中一个日志文件的大小只会有10MB。超过10 MB 就创建另外一个日志文件了。具体的我们可以Spring配置官网去看一下
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties
2.设置日志的保存文件名
正常情况下除了可以设置文件的保存路径,还可以设置文件的日志名称
logging:level:root: errorcom:example:demo:UserContrller: tracefile:name: spring-log.log
而在不设置保存路径的前提下,日志文件会被保存在当前文件夹下
当然这个日志文件如果超过10MB,也会创建第二个文件。
4.7更简单的⽇志输出—lombok
每次都使⽤LoggerFactory.getLogger(xxx.class) 很繁琐,且每个类都添加⼀遍,也很麻烦,这⾥讲⼀
种更好⽤的⽇志输出⽅式,使⽤lombok 来更简单的输出。
- 添加lombok 框架⽀持。
- 使⽤@slf4j 注解输出⽇志。
package com.example.demo.UserController;
//
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;//这个包一定不能引用错了,Logger很多包里都有,我们这里一定要引用slf4的
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@ResponseBody//用来设置当前类中所有的方法返回的是数据而非页面
@RequestMapping("/UserController")
@Slf4j//添加lombook下的slf4j注解
public class UserController {//1.得到日志对象private static final Logger logger = LoggerFactory.getLogger(UserController.class);//2.使用日志对象提供的方法区打印日志@RequestMapping("/logger")///用来作为URLpublic String sayHi(){log.trace("这是slf4j的trace");log.debug("这是slf4j的debug");log.info("这是slf4j的info");//写日志return "hi";}
}
lombook的执行原理
lombok 更多注解说明