SpringBoot教程——检视阅读

参考

SpringBoot教程——一点——蓝本——springboot2.1.1

SpringBoot教程——易百——springboo2.0.5.RELEASE

SpringBoot教程——w3c——springboot1.3.3.RELEASE

SpringBoot教程——C语言网——springboot2.1.6

SpringBoot教程博客——纯洁的微笑

SpringBoot教程——javaschool

SpringBoot官网

Spring Boot参考指南——翻译

SpringBoot教程汇总——博客项目索引

SpringBoot教程汇总——博客项目中文索引

Spring Boot 基础——IBM

SpringBoot教程——极客——杂乱讲不好——springboo2.1.5.RELEASE

SpringBoot教程——jc2182——参考——springboo2.3.0.BUILD-SNAPSHOT

  • 基于Spring Boot框架:Spring Boot 2.1.11.RELEASE

略读

一点

基于spring的知识点基础上讲springboot,只说了最简单的使用和常用的与其他框架如redis、mybatis的整合。缺点是没有对springboot与spring原来的细节对比。

C语言中午网

讲得比较详细,有深入到springboot最重要的两点COC和spring-boot-starter 自动配置依赖模块常用的操作,以及springboot与dubbo构建微服务的操作。

W3C

不是很好,学习起来操作不够清晰。

javaschool

只有这个好点

spring-boot-starter 起步依赖模块枚举

SpringBoot2.02官方参考指南

没翻译完,质量一般,还不如直接看官网

易百

仔细讲解了springboot里的一些使用,还结合了些springcloud的东西,但比较少说与其他框架如redis、mybatis的整合。总体来说这个教程并不好,有点杂乱无章。

IBM

最简单的hello world。

Spring Boot starter 参考页面  :列出了其他许多 starter。

spring-boot-starter-web。基于这个 starter,Spring Boot 形成了该应用程序的以下依赖:

  • 使用 Tomcat 作为嵌入式 Web 服务器容器
  • 使用 Hibernate 进行对象-关系映射 (ORM)
  • 使用 Apache Jackson 绑定 JSON
  • 使用 Spring MVC 作为 REST 框架

如果我们不想用tomcat,可以更改 POM 来使用 Jetty 代替 Tomcat。如果不想用hibernate,改用mybatis,也可以这样操作。

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>

Spring Boot基础入门

什么是Spring Boot

Spring Boot概述

Spring Boot 是所有基于 Spring Framework 5.0 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。

简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

Spring Boot的优点

  • 使用 Spring 项目引导页面可以在几秒构建一个项目
  • 方便对外输出各种形式的服务,如 REST API、WebSocket、Web、Streaming、Tasks
  • 非常简洁的安全策略集成
  • 支持关系数据库和非关系数据库
  • 支持运行期内嵌容器,如 Tomcat、Jetty
  • 强大的开发包,支持热启动
  • 自动管理依赖自带应用监控
  • 支持各种 IDE,如 IntelliJ IDEA 、NetBeans

Spring Boot核心功能

起步依赖

起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

自动配置

Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

Spring Boot快速入门

步骤:

  1. 创建一个普通的maven项目。
  2. pom.xml导入起步依赖 。
  3. 编写引导类

示例:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.self</groupId>
<artifactId>hellospringboot</artifactId>
<version>1.0-SNAPSHOT</version> <!-- 导入springboot父工程. 注意:任何的SpringBoot工程都必须有的!!! -->
<!-- 父工程的作用:锁定起步的依赖的版本号,并没有真正到依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.11.RELEASE</version>
</parent> <dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies> </project>

引导类,或者说叫启动类

@SpringBootApplication
public class MyBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class,args);
}
} @Controller
public class HelloController { @RequestMapping("/hello")
@ResponseBody
public String sayHello(){
return "Hello Spring Boot!";
}
}

请求:http://localhost:8080/hello

输出:

Spring Boot配置文件

Spring Boot的核心是自动配置(或者叫默认配置),通过自动配置大大减少Spring项目的配置编写。但是在实际开发中,我们仍然需要根据需求来适当修改某些必要的参数配置,这时Spring Boot提供了两种格式的配置方便开发者进行修改。

  • applicaiton*.properties
  • application.yml(或者application.yaml)

application*.properties

Spring Boot使用了一个全局的配置文件application.properties,放在src/main/resources目录下或者src/main/resources/config下。在 src/main/resources/config 下的优先级高 。Sping Boot的全局配置文件的作用是对一些默认配置的配置值进行修改。

Spring Boot内置属性

示例:

编写修改Tomcat端口属性 :

application.properties

server.port=9000

Spring Boot内置属性参考

自定义属性

application.properties

server.port=9000

#自定义类型
#基本类型
name=Amy
age=21 #JavaBean类型
user.name=Amy
user.age=21 #数组/List集合
user.list=Amy,Jack,Roy
#或者
user.list[0]=Amy
user.list[1]=Jack
user.list[2]=Roy #Map集合
user.map={name:"Amy",age:21}
#或者
user.map.name=Amy
user.map.age=21

自定义配置怎么取值使用,什么场景使用?

Profile多环境配置

当应用程序需要部署到不同运行环境时,一些配置细节通常会有所不同,最简单的比如日志,生产日志会将日志级别设置为WARN或更高级别,并将日志写入日志文件,而开发的时候需要日志级别为DEBUG,日志输出到控制台即可。如果按照以前的做法,就是每次发布的时候替换掉配置文件,这样太麻烦了,Spring Boot的Profile就给我们提供了解决方案,命令带上参数就搞定。

步骤:

  • 建立不同环境的application.properties文件
  • 每个文件里面的环境配置变量不同

示例:

application.properties

#启用prod生产环境
spring.profiles.active= prod
#当profiles不同环境变量文件里有配置值时,application.properties里配置的变量是会被启用的环境如application-prod.properties里的值所覆盖的。
server.port=9000

application-prod.properties

server.port=9004

输出:

application*.yml

YAML(/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达数据序列化的格式。

yml或yaml所表示的YAML Ain’t Markup Language,YAML是一种简洁的非标记语言,文件名后缀为yml,java中经常用它描述配置文件application.yml。YAML以数据为中心,比json/xml等更适合做配置文件。使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。

在yml之前使用最多的配置文件形式是xml和properties文件。xml文件太过繁琐,看过的人都知道,想要新加一个配置节点的话还需要包含在<>标签里;而properties配置文件没有了标签,不过当你的配置有很多层级的时候,写完之后你会发现会有大量重复的代码。而yml/yaml文件结合了两者的优势,当你新增节点配置的时候,不需要标签,在写多层级配置的时候也不会产生重复代码。

yml格式书写规则

  1. 大小写敏感
  2. 使用缩进表示层级关系
  3. 禁止使用tab缩进,只能使用空格键
  4. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
  5. 使用#表示注释
  6. 字符串可以不用引号标注

Spring Boot内置属性

注意:

当application.properties和application.yml同时存在时,生效的是application.properties。

#yml文件在写时有提示,友好,优先选择使用
#修改端口
server:
port: 9090
#基本类型 注意:属性值大小写敏感
name: Bruce Wayne
age: 29
#JavaBean类型
user:
name: Bruce Wayne
age: 29
#数组/List集合
#user:层级只能指定一个,后面的如果是在user层级下的只需要tab空格就可以了,再写user层级则会报错
#user:
list: eric,jack,rose
#下面这种写法用@Value注解解析不了
list:
- Jack
- Rose
- Jerry
#Map集合
#user:
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
map: {name: Bruce Wayne,age: 29}

Profile多环境配置

application.yml

#启用test测试环境
#当profiles不同环境变量文件里有配置值时,application.yml里配置的变量是会被启用的环境如application-test.yml里的值所覆盖的。
#还有一点需要特别注意的是当存在application-test.properties与application-test.yml两个并行时,生效的是application-test.properties
spring:
profiles:
active: test

application-test.yml

server:
port: 9092

Spring Boot读取配置文件(properties和yml处理是一样的)

Spring Boot里面有两个注解可以读取application.properties或application.yml文件的属性值。

  1. @Value
  2. @ConfigurationProperties

注意:

1、不能配置user.name=Amy属性配置,因为取不到Amy的值,取到的是计算机的用户名,在这台电脑里我的用户名是Castamere。应该是个系统默认保留的取值配置,这里没有深入去研究。

2、不能配置userName=Amy这个key为userName或者username的基本类型配置,否则取到的是还是计算机的用户名。

@Value

基本类型

application.yml

#基本类型
firstName: Bruce Wayne1111
age: 29

读取:

@Controller
public class ConfigController { @Value("${firstName}")
private String name;
@Value("${age}")
private Integer age; @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return name + " : " + age;
}
}

JavaBean类型

#JavaBean类型
user:
first: Bruce Wayne
age: 31

读取:

@Controller
public class ConfigController {
@Value("${user.firstName}")
private String firstName;
@Value("${user.age}")
private Integer age; @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return firstName + " : " + age;
}
}

数组/List集合

user:
list: Jack,Rose,Jerry

读取:

@Value("#{'${user.list}'.split(',')}")
private List<String> list; @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return JSON.toJSONString(list);
}

Map集合

user:
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
#读取的时候要加引号""是给@Value注解用的么?
# map: {nickname: erci,age: 20}
map: "{name: 'SuperMan',age: 28}"

读取:

@Value("#{${user.map}}")
private Map<String,Object> map; @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return JSON.toJSONString(map);
}

注意,不加引号会报错.

#报错:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
user:
map: {name: 'SuperMan',age: 28}

@ConfigurationProperties

注意以下几点:

  • prefix:代表属性的前缀,如果user.nickname前缀就是user

  • 属性名称必须和properties文件的属性名保持一致

  • 属性必须提供setter方法来注入文件的属性值

    基本类型

    firstName: Bruce Wayne

    age: 30

读取:

@Controller
@ConfigurationProperties
public class ConfigurationController { private String firstName;
private Integer age; public void setFirstName(String firstName) {
this.firstName = firstName;
} public void setAge(Integer age) {
this.age = age;
} @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return firstName + " : " + age;
}
}

输出:

//成功
Bruce Wayne : 30
//没有setter方法来注入文件的属性值,不会报错,但是没有赋值
null : null

JavaBean类型

#JavaBean类型
user:
firstName: Bruce
age: 31

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController { private String firstName;
private Integer age; public void setFirstName(String firstName) {
this.firstName = firstName;
} public void setAge(Integer age) {
this.age = age;
} @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return firstName + " : " + age;
//return JSON.toJSONString(list);
//return JSON.toJSONString(map);
}
} Bruce : 31

数组/List集合

#数组/List集合
#user:层级只能指定一个,后面的如果是在user层级下的只需要tab空格就可以了,再写user层级则会报错
#user:
#两种list表达方式都可以,倾下第一种
list: Jack,Rose,Jerry
list2:
- Jack
- Morty
- Jerry

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController { private List<String> list; public void setList(List<String> list) {
this.list = list;
} @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return JSON.toJSONString(list);
}
}

Map集合

#Map集合
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
#读取的时候要加引号""是给@Value注解用的么?@ConfigurationProperties("user")读取map不需要加引号,否则报错,说明两种读取方式不同
#报错:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
map: {name: 'SuperMan',age: 28}
# map: "{name: 'SuperMan',age: 28}"

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {
private Map<String,Object> map; public void setMap(Map<String, Object> map) {
this.map = map;
} @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return JSON.toJSONString(map);
}
} {"name":"SuperMan","age":28}

Spring Boot热部署

什么是热部署

无法热部署的缺点:

  • 在实际开发过程中,每次修改代码就得将项目重启,重新部署,对于一些大型应用来说,重启时间需要花费大量的时间成本。
  • 程序员开发过程中重启缓慢影响开发。在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作。

热部署原理

深层原理是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。

Spring Boot热部署实现方式

Spring Boot有3种热部署方式:

  1. 使用springloaded配置pom.xml文件,使用mvn spring-boot:run启动
  2. 使用springloaded本地加载启动,配置jvm参数
  3. 使用devtools工具包,操作简单,但是每次需要重新部署

Spring Boot使用devtools工具包实现热部署

需要说明以下4点:

  1. devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现)
  2. 实现类文件热部署(类文件修改后不会立即生效,过会儿生效)
  3. 实现对属性文件的热部署。即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存的时候)。这里可能有疑问,为什么还要重启?这样就不是热部署啦!注意:因为其采用的虚拟机机制,该项重启比正常重启会快非常多!
  4. scope配置为true,在修改java文件后就立即热启动,而且会清空Session中的数据。如果有用户登陆的话,项目重启后需要重新登陆。

示例:

pom.xml添加依赖:

<!--devtools热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>

application.yml添加devtools的配置 :

spring:
devtools:
restart:
enabled: true #设置开启热部署
additional-paths: src/main/java #重启目录
exclude: WEB-INF/**
freemarker:
cache: false #页面不加载缓存,修改即时生效

修改了类文件后,IDEA不会自动编译,必须修改IDEA设置。

1、
File-Settings-Compiler-Build Project automatically
2、
ctrl + shift + alt + / ,选择Registry,勾上 Compiler autoMake allow when app running

Spring Boot访问静态资源

Spring Boot默认静态资源目录

在Spring Boot应用启动过程中,会读取加载一个静态资源文件加载路径这个属性

# 默认值为
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

这个属性的默认值代表静态资源扫描目录:

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/:当前项目的根路径

这意味着我们可以只要把静态资源文件存放在以上目录,即可以被访问到!

需注意优先级问题:根据前后关系确定优先级,配置路径在前面会优先访问前面的静态资源。也就是说如果classpath:/resources/目录和classpath:/public/都有一个test.html,那么根据默认的优先级,会去访问classpath:/resources/下的资源。

示例:分别建立了public、resources、static目录,在目录下建立html静态页面。项目启动后,我们都可以直接访问这些页面 。

//请求
http://localhost:8080/index.html
http://localhost:8080/img/test.gif
http://localhost:8080/hello.html //在public和resources文件夹下都有,优先访问resources下的静态资源
http://localhost:8080/success.jsp//jsp文件访问会直接下载,而不会去解析。

修改Spring Boot静态资源路径

我们可以在application.yml文件中修改静态资源路径,如:

# 修改静态资源加载路径
spring:
resources:
static-locations: classpath:/download,classpath:/static/,classpath:/public/

注意:

如果按照以上写法会覆盖Spring Boot的默认路径。如果希望保留默认路径,那就要先写上之前所有值,再最后加上新的路径。

Spring Boot进阶

Spring Boot异常处理

有5种处理方式:

  1. Spring Boot默认异常提示。
  2. 自定义error的错误页面。
  3. @ExceptionHandler注解(Controller中自定义)。
  4. @ControllerAdvice注解 加 @ExceptionHandler注解 抽取所以共用的异常处理方法。
  5. 实现HandlerExceptionResovler。

Spring Boot默认异常提示

在Spring Boot应用执行过程中难免会出现各种错误,默认情况下,只要出现异常,就会跳转到Spring Boot默认错误提示页面,如下:

为了给用户更好的体验,我们可以使用以下四种手段来优化异常捕获的情况。

自定义error的错误页面

SpringBoot应用默认已经提供一套错误处理机制:就是把所有后台错误统一交给error请求,然后跳转到了本身自己的错误提示页面。这时,我们利用springboot的错误处理机制,重新建立了一个新的error.html,该页面必须放在resources的templates目录下 。

示例:

pom.xml ——页面用到了Thymeleaf,所以项目中需要导入Thymeleaf的依赖。如果没有加依赖则页面加载失败继续调整到第一种springboot默认异常提示页面上。

error.html

<head>
<meta charset="UTF-8">
<title th:text="${title}"></title>
</head>
<body>
<div >
<div>
<div>
<p><span>页面出现</span><span class="code" th:text="${status}"></span>错误,非常抱歉!</p>
<a href="/" class="btn-back common-button">您可以点击返回首页</a>
<div >
<div th:text="${#dates.format(timestamp,'yyyy-MM-dd HH:mm:ss')}"></div>
<div>错误原因:</div>
<div th:text="${message}"></div>
<div th:text="${error}"></div>
</div>
</div>
</div>
</div>
</body>

异常展示:

@ExceptionHandler注解

 /**@ExceptionHandler 注解只能作用为对象的方法上,并且在运行时有效,value() 可以指定异常类。由该注解注*释的方法可以具有灵活的输入参数。
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}

示例:

@Controller
public class HelloController { @Autowired
private User user; @RequestMapping("/hello")
@ResponseBody
public String sayHello() {
throw new NullPointerException();
//return "Hello Spring Boot!";
} @RequestMapping("/user")
@ResponseBody
public String helloUser() {
int i = 10 / 0;
return JSON.toJSONString(user);
} // 处理java.lang.ArithmeticException
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public String handlerArithmeticException(Exception e) {
return "数学运算错误:" + e.getMessage();
} // 处理java.lang.NullPointerException
@ExceptionHandler(value = {NullPointerException.class})
@ResponseBody
public String handlerNullPointerException(Exception e) {
// e:该对象包含错误信息
return "空指针错误:" + e;
}
}

输出:

@ControllerAdvice注解

刚才的@ExceptionHandler注解是用在控制器类里面的,这样每个控制器都需要定义相关方法,比较繁琐。这时可以使用@ControllerAdvice来抽取所有共同的@ExceptionHandler方法,从而简化异常方法的定义。

注意:

当业务Controller有自己的@ExceptionHandler注解处理方法时,生效的是Controller上的异常处理方法,@ControllerAdvice里的不会生效。

示例:

@ControllerAdvice
public class CommonExceptionHandler { // 处理java.lang.ArithmeticException
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public String handlerArithmeticException(Exception e) {
return "数学运算错误1:" + e.getMessage();
} // 处理java.lang.NullPointerException
@ExceptionHandler(value = {NullPointerException.class})
@ResponseBody
public String handlerNullPointerException(Exception e) {
// e:该对象包含错误信息
return "空指针错误1:" + e;
}
}

输出:

HandlerExceptionResovler

注意:

异常处理优先级顺序:Controller层异常 > @ControllerAdvice > HandlerExceptionResovler

示例:

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver { @Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mv = new ModelAndView();
//判断不同异常类型,做不同处理
if(e instanceof ArithmeticException){
mv.setViewName("error1");
}
if(e instanceof NullPointerException){
mv.setViewName("error2");
}
mv.addObject("error", e.toString());
return mv;
}
}

输出:

Spring Boot表单数据验证

在Spring Boot中我们经常需要对表单数据进行合法性验证。下面讲解如何在Spring Boot中进行表单验证。

更多校验规则参考Spring,两者是一样的。

示例:

pom.xml

<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 导入thymeleaf坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

Pojo,添加验证注解

public class User {

    private Integer id;

    @NotEmpty(message = "姓名不能为空")
private String name;
@Min(1)
private Integer age;
@Email(message = "邮箱地址不正确")
private String email;
@NotEmpty(message = "描述不能为空")
@Length(min = 5, max = 100, message = "描述必须在5-100个字之间")
private String desc;
//...} @Controller
@RequestMapping("/user")
public class UserController {
/**
* 跳转到add.html
* @return
*/
@RequestMapping("/toAdd")
public String toAdd() {
return "add";
}
/**
* 用户添加
* BindingResult: 用于封装验证对象(user)里面的验证结果
*/
@RequestMapping("/add")
public String add(@Valid User user, BindingResult result) {
if (result.hasErrors()) {
return "add";
}
//save
System.out.println("保存用户:" + JSON.toJSONString(user));
return "success";
}
}

设计页面,回显错误信息

add.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户添加</title>
</head>
<body>
<h3>用户添加</h3>
<form action="/user/add" method="post">
用户名:<input type="text" name="name"/><font color="red" th:errors="${user.name}"></font><br/>
描述:<input type="text" name="desc"/><font color="red" th:errors="${user.desc}"></font><br/>
年龄:<input type="text" name="age"/><font color="red" th:errors="${user.age}"></font><br/>
邮箱:<input type="text" name="email"/><font color="red" th:errors="${user.email}"></font><br/>
<input type="submit" value="保存"/>
</form>
</body>
</html>

success.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>提示页面</title>
</head>
<body>
保存成功
</body>
</html> 保存用户:{"age":25,"desc":"黄金脑殿下","email":"roy@inc.com","name":"艾米"}

Spring Boot文件上传

示例:

\resources\static\upload.html——在静态资源文件夹下这样我们就可以直接访问静态资源。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传页面</title>
</head>
<body>
文件上传页面 <hr/>
<form action="/upload" method="post" enctype="multipart/form-data">
请选择文件:<input type="file" name="fileName"/><br/>
<input type="submit" value="开始上传"/>
</form>
</body>
</html> @Controller
public class UploadController { @RequestMapping("/upload")
public String upload(MultipartFile fileName, HttpServletRequest request){
//处理文件
System.out.println("文件原名称:"+fileName.getOriginalFilename());
System.out.println("文件类型:"+fileName.getContentType());
String upload = UploadController.class.getResource("/").getFile()+"/upload";
File file = new File(upload);
if (!file.exists()) {
file.mkdir();
}
//目标文件传入地址路径+名称
try {
fileName.transferTo(new File(upload + "/" + fileName.getOriginalFilename()));
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
}

Spring Boot文件下载

示例:

\resources\static\download.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<h3>文件下载</h3>
<a href="/download">下载</a>
</body>
</html> @Controller
public class DownloadController { @RequestMapping("/download")
public void download(HttpServletResponse response) throws IOException {
InputStream inputStream = new FileInputStream(DownloadController.class.getResource("/").getFile()+"/static/img/test.gif");
//2.输出文件
//设置响应头
response.setHeader("Content-Disposition","attachment;filename=export.gif");
OutputStream outputStream = response.getOutputStream();
byte[] buff = new byte[1024];
int lenth = 0;
while ((lenth= inputStream.read(buff))!= -1){
outputStream.write(buff,0,lenth);
}
//3.关闭资源
outputStream.close();
inputStream.close();
}
}

Spring Boot原理分析

@SpringBootApplication

首先,我从引导类开始:

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

引导类代码很简单,但可以看出最关键的是@SpringBootApplication注解以及在main方法中运行的SpringAppliation.run()了,我们进去@SpringBootApplication的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
......
}

我们看到@SpringBootApplication其实是一个复合的注解,它就是由@SpringBootConfiguration、@EnableAutoConfiguration以及@ComponentScan 三个注解组成,所以如果我们把SpringBoot启动类改写成如下方式,整个SpringBoot应用依然可以与之前的启动类功能一样:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class MyBootApplication {
public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class, args);
}
}

因为我们每次新建项目时都要写上三个注解来完成配置,这显然太繁琐了,SpringBoot就为我们提供了@SpringBootApplication这样注解来简化我们的操作。接着,我们重点分析这三个注解的作用。

@SpringBootConfiguration

我们来看@SpringBootConfiguration注解的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

我们可以看到,SpringBoot为了区别@Configuration而新提供的专属于SpringBoot的注解,功能其实和@Configuration一模一样。而这里的@Configuration注解对于我们来说并不陌生,它就是是个IoC容器的配置类。看到这里,我们其实可以把SpringBoot的启动类这样来看就清楚了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyBootApplication {
public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class, args);
}
}

启动类MyBootApplication其实就是一个标准的Spring纯注解下的启动类,也并没有什么特殊。

@EnableAutoConfiguration

看到这个注解,我们不禁联想出Spring 中很多以“@Enable”开头的注解,比如:@EnableScheduling、@EnableCaching以及@EnableMBeanExport等,@EnableAutoConfiguration注解的理念和工作原理和它们其实一脉相承。简单的来说,就是该注解借助@Import注解的支持,Spring的IoC容器收集和注册特定场景相关的Bean定义:

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean都加载到IoC容器。
  • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。

而@EnableAutoConfiguration注解也是借助@Import将所有复合配置条件的bean定义加载到IoC容器,仅此而已!@EnableAutoConfiguration注解的源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

这其中最关键的就是@Import(AutoConfigurationImportSelector.class)了,它借助AutoConfigurationImportSelector.class可以帮助SpringBoot应用将所有符合条件的@Configuration配置类都加载到当前SpringBoot创建并使用的IoC容器,就像下图一样。

下面我们给出AutoConfigurationImportSelector.java的部分源码,来解释和验证上图:

public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
this.beanClassLoader);
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
this.beanClassLoader);
}
}

以上源码可以看出,@EnableAutoConfiguration正是借助SpringFactoriesLoader的支持,才能完成所有配置类的加载!

SpringFactoriesLoader

SpringFactoriesLoader属于Spring框架专属的一种扩展方案(其功能和使用方式类似于Java的SPI方案:java.util.ServiceLoader),它的主要功能就是从指定的配置文件META-INF/spring.factories中加载配置,spring.factories是一个非常经典的java properties文件,内容格式是Key=Value形式,只不过这Key以及Value都非常特殊,为Java类的完整类名(Fully Qualified Name),比如:

org.springframework.context.ApplicationListener=org.springframework.boot.autoconfigure.BackgroundPreinitializer

然后Spring框架就可以根据某个类型作为Key来查找对应的类型名称列表了,SpringFactories源码如下:

public abstract class SpringFactoriesLoader {

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
...
} public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
...
}
// ...
}

对于@EnableAutoConfiguraion来说,SpringFactoriesLoader的用途和其本意稍微不同,它本意是为了提供SPI扩展,而在@EnableAutoConfiguration这个场景下,它更多的是提供了一种配置查找的功能的支持,也就是根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为Key来获取一组对应的@Configuration类:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

在SpringBoot的autoconfigure依赖包中的META-INF文件下的spring.factories文件中,我们可以找到以上内容。

总结来说,@EnableAutoConfiguration能实现自动配置的原理就是:SpringFactoriesLoader从classpath中搜寻所有META-INF/spring.fatories文件,并将其中Key[org.springframework.boot.autoconfigure.EnableAutoConfiguration]对应的Value配置项通过反射的方式实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总到当前使用的IoC容器中。

@ComponentScan

@ComponentScan注解在Spring Boot启动的时候其实不是必需的!因为我们知道作为Spring框架里的老成员,@ComponentScan的功能就是自动扫描并加载复合条件的组件或Bean定义,最终将这些Bean定义加载到当前使用的容器中。这个过程,我们可以手工单个进行注册,不是一定要通过这个注解批量扫描和注册,所以说@ComponentScan是非必需的。

所以,如果我们当前应用没有任何Bean定义需要通过@ComponentScan加载到当前SpringBoot应用对应的IoC容器,那么,去掉@ComponentScan注解,当前的SpringBoot应用依旧可以完美运行!

Spring Boot整合其他技术

Spring Boot整合Servlet

在Spring Boot应用如何我们需要编写Servlet相关组件(包括Servlet、Filter、Listener),需要怎么做呢?Spring Boot提供了两种使用Servlet组件的方式:

  1. 使用@ServletComponentScan注解注册
  2. 使用@Bean注解注解

@ServletComponentScan

注意:在引导类类必须添加@ServletComponentScan注解,该注解用于扫描应用中Servlet相关组件。

示例:

/**
* 等同于web.xml配置
* <servlet>
* <servlet-name>helloServlet</servlet-name>
* <servlet-class>com.yiidian.controller.HelloServlet</servlet-class>
* </servlet>
* <servlet-mapping>
* <servlet-name>helloServlet</servlet-name>
* <url-pattern>/helloServlet</url-pattern>
* </servlet-mapping>
*
*/
// @WebServlet:声明该类为Servlet程序
@WebServlet("/hi")
//@WebServlet(name="helloServlet",urlPatterns="/hi")
public class HelloServlet extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HelloServlet");
String docType = "<!DOCTYPE html> \n";
String top = "Hello SpringBoot";
resp.getWriter().println(docType +
"<html>\n" +
"<head><title>" + top + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + top + "</h1>\n" +
"</body></html>");
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
} //表示对所有servlet都执行过滤操作
//@WebFilter
//表示对路径为/ 的servlet执行过滤操作
//@WebFilter("/")
//表示对路径为/hi 的servlet执行过滤操作,两种写法都可以
@WebFilter("/hi")
//@WebFilter(filterName="HiFilter",urlPatterns="/hi")
//定义Filter过滤器
public class HiFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HiFilter init"); } @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("do HiFilter before");
//放行执行目标servlet资源
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("do HiFilter after");
} @Override
public void destroy() {
System.out.println("HiFilter destroy");
}
} //定义Listener监听器
@WebListener
public class HiListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象创建了");
} @Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象消耗了");
}
}

引导类

@SpringBootApplication
@ServletComponentScan //注册Servlet组件
public class MyBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class,args);
} }

输出:

ServletContext对象创建了
HiFilter init
do HiFilter before
HelloServlet
do HiFilter after

注意:在测试时发现按照正确配置去写代码但是一直访问不成功。可能的原因有两个:

  • IDEA的问题,一直重启却识别不了@ServletComponentScan去扫描servlet,导致失败。
  • 启动类的@ServletComponentScan扫描默认是扫描与该启动类同包以及其子包下的类。

@Bean

第二种方式的代码和第一种几乎一样,就是引导类的差别,引导类改为使用@Bean注解来注解Servlet、Filter和Listener。

示例:

@SpringBootApplication
public class MyBeanBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBeanBootApplication.class,args);
}
//注册Servlet程序
@Bean
public ServletRegistrationBean getServletRegistrationBean(){
ServletRegistrationBean bean = new ServletRegistrationBean(new HelloServlet());
bean.addUrlMappings("/hi");
return bean;
}
//注册Filter
@Bean
public FilterRegistrationBean getFilterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean(new HiFilter());
bean.addUrlPatterns("/hi");
return bean;
}
//注册Listener
@Bean
public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
return new ServletListenerRegistrationBean(new HiListener());
}
}

注意:如果两个都配置则filter和listener会执行两遍。当使用@Bean整合Servlet时,Servlet,Filter,Listener不需要加对应的注解,因为我们在@Bean中已经把new HelloServlet()创建出来了。

Spring Boot整合JSP

示例:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.self</groupId>
<artifactId>hellospringboot</artifactId>
<version>1.0-SNAPSHOT</version> <!-- 导入springboot父工程. 注意:任何的SpringBoot工程都必须有的!!! -->
<!-- 父工程的作用:锁定起步的依赖的版本号,并没有真正到依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.11.RELEASE</version>
</parent> <dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<!--devtools热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>-->
</dependencies>
</project>

注意:Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers.这是因为整合jsp就不能依赖thymeleaf。

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE] <!-- 导入thymeleaf坐标 整合jsp就不能依赖thymeleaf,否则会因为请求不到页面而报错-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>-->

起步依赖tomcat。因为spring-boot-starter-web已经有spring-boot-starter-tomcat依赖了,如下,因此不再需要再依赖tomcat。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.11.RELEASE</version>
<scope>compile</scope>
</dependency>

扩展:

pom中<scope></scope>一些理解
compile:默认值,表示当前依赖包,要参与当前项目的编译,后续测试,运行时,打包
provided:代表在编译和测试的时候用,运行,打包的时候不会打包进去
test:表示当前依赖包只参与测试时的工作:比如Junit
runtime:表示当前依赖包只参与运行周期,其他跳过了
system:从参与度和provided一致,不过被依赖项不会从maven远程仓库下载,而是从本地的系统拿。需要
systemPath属性来定义路径

配置application.yml

#springmvc视图解析器配置
spring:
mvc:
view:
prefix: /WEB-INF/jsp/ # 前缀,注意最后的文件夹jsp后面要加斜杠/
suffix: .jsp # 后缀 @Controller
@RequestMapping("/user")
public class UserController { @RequestMapping("/list")
public String list(Model model) {
//模拟用户数据
List<User> list = new ArrayList<User>();
for (int i = 0; i < 3; i++) {
User user = new User();
user.setId(ThreadLocalRandom.current().nextInt());
user.setAge(ThreadLocalRandom.current().nextInt(100));
user.setName(UUID.randomUUID().toString());
list.add(user);
}
//把数据存入model
model.addAttribute("list", list);
//跳转到jsp页面: userlist.jsp
return "userlist";
}
}

编写userlist.jsp页面

注意:Spring Boot项目并不会到templates目录查找JSP页面,它是到/src/main/webapp目录下查找。所以我们需要创建webapp目录,然后在里面创建我们需要的目录和JSP文件。

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>用户列表展示</title>
</head>
<body> <h3>用户列表展示</h3>
<table border="1">
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<c:forEach items="${list}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
</table>
</body>
</html> @SpringBootApplication
public class MyBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class,args);
}
}

修改项目运行目录,经过测试,不修改也行,估计和IDEA版本有关。

Spring Boot应用默认情况下,会到应用的classes目录下加载内容,因为JSP页面并不在classes下面,所以需要把运行目录手动改为应用的根目录下,这样才能加载到JSP页面。 如下:

请求:http://localhost:8080/user/list

输出:

报错:

如下图,是因为application.yml配置jsp页面解析器路径名称弄错。

Spring Boot整合Thymeleaf

thymeleaf怎么读?英 [taim li:f] 美 [taɪm lif] 。百里香叶。

关于模板引擎

  1. 市面上主流的 Java 模板引擎有:JSP、Velocity、Freemarker、Thymeleaf。
  2. JSP本质也是模板引擎,Spring Boot官方推荐使用“Thymeleaf”模板引擎。

模板引擎的原理

模板引擎原理图如下,模板引擎的作用都是将模板(页面)和数据进行整合然后输出显示,区别在于不同的模板使用不同的语法,如 JSP 的JSTL表达式,以及J SP 自己的表达式和语法,同理 Thymeleaf 也有自己的语法。

Spring Boot整合Thymeleaf

示例:

pom.xml导入Thymeleaf的依赖.

<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies> @Controller
@RequestMapping("/user")
public class UserController { @RequestMapping("/thyme")
public String thymeLeaf(Model model){
model.addAttribute("message","thymeLeaf show");
//跳转到templates/show.html
return "show";
}
}

注意以下几点:

  • 模板文件(即页面)必须放在/resources/templates目录下,否则无法渲染。
  • Thymeleaf标签都是以th开头的。

show.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SpringBoot整合Thymeleaf</title>
</head>
<body>
<span th:text="${message}"></span>
</body>
</html>

输出:

Thymeleaf基本语法

变量输出

方法代码

//变量输出
@RequestMapping("/demo2")
public String demo2(Model model){
model.addAttribute("name", "张三");
return "demo2";
}

页面代码

<h3>变量输出</h3>
<h4 th:text="${name}"></h4>
<h4 th:text="李四"></h4>

条件判断及迭代遍历、域对象使用、超链接

show.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SpringBoot整合Thymeleaf</title>
</head>
<body>
<span th:text="${message}"></span> <h3>条件判断</h3>
<div th:if="${gender} == '男'">
这是一位男性朋友
</div>
<div th:if="${gender} == '女'">
这是一位女性朋友
</div>
<br/>
<div th:switch="${grade}">
<span th:case="1">这是1的情况</span>
<span th:case="2">这是2的情况</span>
<span th:case="3">这是3的情况</span>
</div>
<h3>迭代遍历</h3>
<table border="1">
<tr>
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
<tr th:each="user : ${list}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
</tr>
</table>
<h3>域对象数据的获取</h3>
request: <span th:text="${#httpServletRequest.getAttribute('request')}"></span><br/>
session: <span th:text="${session.session}"></span><br/>
application: <span th:text="${application.application}"></span><br/>
<h3>超链接的语法</h3>
<a th:href="@{~/user/thyme}">访问demo1</a><br/> <a th:href="@{~/user/thyme(id=1,name=eric)}">访问demo1,传递参数</a>
</body>
</html> @Controller
@RequestMapping("/user")
public class UserController { @RequestMapping("/thyme")
public String thymeLeaf(Model model, HttpServletRequest request){
model.addAttribute("message","thymeLeaf show");
model.addAttribute("gender","男");
model.addAttribute("grade",2);
List<User> list = new ArrayList<User>();
for (int i = 0; i < 3; i++) {
User user = new User();
user.setId(ThreadLocalRandom.current().nextInt());
user.setAge(ThreadLocalRandom.current().nextInt(100));
user.setName(UUID.randomUUID().toString());
list.add(user);
}
//把数据存入model
model.addAttribute("list", list);
//request
request.setAttribute("request", "request's data");
//session
request.getSession().setAttribute("session", "session's data");
//application
request.getSession().getServletContext().setAttribute("application", "application's data");
//跳转到templates/show.html
return "show";
}
}

输出:

Spring Boot整合FreeMarker

//跳过

Spring Boot整合MyBatis

前置条件:

CREATE TABLE `t_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) NOT NULL COMMENT '姓名',
`dept` varchar(254) NOT NULL COMMENT '部门',
`phone` varchar(16) NOT NULL COMMENT '电话',
`height` decimal(10,2) DEFAULT NULL COMMENT '身高',
`create_emp` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='用户表';

示例:

pom.xml——导入mybatis和mysql驱动程序

<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--mybatis 起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- MySQL 连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>

application.yml——配置数据源连接参数,及mybatis相关配置。

spring:
datasource: #修改数据库连接配置
url: jdbc:mysql://localhost:3306/hello_mybatis?characterEncoding=UTF8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456 # mybatis配置
mybatis:
type-aliases-package: com.self.pojo # 别名目录 public class User { private Integer id; @NotEmpty(message = "姓名不能为空")
private String name;
@Min(1)
private Integer age;
@Email(message = "邮箱地址不正确")
private String email;
@NotEmpty(message = "描述不能为空")
@Length(min = 5, max = 100, message = "描述必须在5-100个字之间")
private String desc; /**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime;
//...
} //必须给Dao接口加上@Mapper,这样Spring Boot在启动时才能扫描到Dao接口,并为其生成代理对象。
@Mapper
//装饰用,告诉spring这是个dao注册bean,不加@Repository的话IDEA会提示这个bean无法@Autowired自动依赖
@Repository
public interface UserDao { public List<User> getUsers();
}

UserDao.xml——Dao映射配置,在Dao接口相同目录下建立同名的XML文件。

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.self.dao.UserDao"> <!-- 查询所有用户 -->
<select id="getUsers" resultType="User">
select * from t_user where 1=1
</select>
</mapper> @Controller
@RequestMapping("/user")
public class UserController { @Autowired
private UserDao userDao; @RequestMapping("/showAll")
@ResponseBody
public List<User> list(){
//模拟用户数据
List<User> list = userDao.getUsers();
return list;
}
}

启动类不变,输出:

整合Spring Data JPA

示例:

pom.xml——添加Spring Data JPA的依赖

<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- springBoot JPA 的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>

application.yml

spring:
datasource: #修改数据库连接配置
url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# jpa配置
jpa:
show-sql: true #控制台输出生成的SQL语句
generate-ddl: true # 自动建表

项目使用Spring Data JPA,所以在Pojo实体中必须添加Jpa的映射注解,和数据库表进行一一映射。

如果pojo字段不是一一映射,比如比数据库多了字段,则会导致执行失败。

import javax.persistence.*;
@Entity
@Table(name="t_user")
public class User { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; @NotEmpty(message = "姓名不能为空")
private String name;
/**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime;
//...
}

Spring Data JPA提供了两个核心接口,我们项目中一般选择继承它们:

  • JpaRepository接口:拥有CRUD,分页,排序等方法

  • JpaSpecificationExecutor接口:拥有组合条件搜索方法

    public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor {

    }

    @Controller

    @RequestMapping("/user")

    public class UserController {

      @Autowired
    private UserDao userDao; @RequestMapping("/showAll")
    @ResponseBody
    public List<User> list(){
    //模拟用户数据
    List<User> list = userDao.findAll();
    return list;
    }
    }

输出:

Spring Boot整合Redis

前提: 需要Windows启动Redis Server 。

解压redis压缩包。

打开一个 cmd 窗口 使用 cd 命令切换安装目录如 E:\redis 运行:

#启动redis服务器
redis-server.exe redis.windows.conf

验证redis是否启动成功,另外打开一个 cmd 窗口 使用 cd 命令切换安装目录如 E:\redis 运行:

#启动redis客户端
redis-cli.exe -h 127.0.0.1 -p 6379
#执行命令
set name mike
get name

示例:

pom.xml——导入Spring Data Redis依赖!

 <dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- 配置使用 redis 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

application.yml——Spring Boot的Redis配置

#host:代表Redis服务端地址
#port:Java连接Redis的端口
#database:操作的Redis的数据库索引
spring:
redis:
host: localhost # 默认localhost,需要远程服务器需要修改
port: 6379 # 默认6379,如果不一致需要修改
database: 0 # 代表连接的数据库索引,默认为0,

在Controller注入RedisTemplate模板对象,利用它来操作Redis数据库,这里写一个put方法,用于往Redis存入数据,一个get方法,从Redis获取数据。但需要注意的时候,如果操作的Pojo对象,该Pojo必须实现java.io.Serializable接口 。

@Controller
@RequestMapping("/user")
public class UserController { @Autowired
private RedisTemplate redisTemplate; @RequestMapping("/load")
@ResponseBody
public String loadUsers(){
List<User> list = userDao.findAll();
for (User user : list) {
redisTemplate.opsForValue().set(user.getName(),user);
}
return "success";
} @RequestMapping(value = "/get",method = RequestMethod.GET)
@ResponseBody
public User getUser(String name){
return (User) redisTemplate.opsForValue().get(name);
}
} public class User implements Serializable {
//...
} 请求:
先缓存redis
http://localhost:8080/user/load
从redis获取缓存数据
http://localhost:8080/user/get?name=艾米哈珀

输出:

报错:Failed to serialize object using DefaultSerializer。这是因为传输对象没有实现序列号接口,无法序列号。

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.self.pojo.User]

Spring Boot整合EhCache

EhCache简介

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

配置ehcache.xml——参数说明

参数名 说明

name 缓存名称

maxElementsInMemory 缓存最大个数

eternal 对象是否永久有效,一但设置了,timeout将不起作用

timeToIdleSeconds 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大

timeToLiveSeconds 设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大

overflowToDisk 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中

diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区

maxElementsOnDisk 硬盘最大缓存个数

diskPersistent 是否缓存虚拟机重启期数据

diskExpiryThreadIntervalSeconds 磁盘失效线程运行时间间隔,默认是120秒。

memoryStoreEvictionPolicy 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)

clearOnFlush 内存数量最大时是否清除

示例:

pom.xml

 <!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--springboot 集成 junit 起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.6.RELEASE</version>
<scope>test</scope>
</dependency> <!-- 缓存坐标 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
<!-- Ehcache支持 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>

ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="java.io.tmpdir"/>

    <!-- defaultCache: 默认配置 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache> <!-- 缓存名称为user的配置 -->
<cache name="user"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>

application.yml

#配置EhCache的配置spring:
cache:
ehcache:
config: ehcache.xml

引导类中需要添加@EnableCaching注解,开启缓存功能 。

@SpringBootApplication
@EnableCaching // 开启缓存
public class MyBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class,args);
}
} @Service
public class UserService { @Cacheable(value = "user",key = "#id")
public User findById(Integer id){
System.out.println("执行了UserService获取User");
User user = new User();
user.setId(5);
user.setName("林雨裳");
user.setDept("艾米帝国");
user.setPhone("911119");
return user;
}
}

@Cacheable的属性:

  • value:对应ehcache.xml的缓存配置名称(name属性值)

  • key:给缓存值起个key,便于Spring内部检索不同的缓存数据。#id这个语法代表把方法形参作为key。

    @RunWith(SpringJUnit4ClassRunner.class)

    @SpringBootTest(classes = MyBootApplication.class)

    public class EhCacheTest {

      @Autowired
    private UserService userService; @Test
    public void testCache(){
    //第一次
    System.out.println(JSON.toJSONString(userService.findById(5)));
    //第二次
    System.out.println(JSON.toJSONString(userService.findById(5)));
    }

    }

输出:

执行了UserService获取User
{"dept":"艾米帝国","id":5,"name":"林雨裳","phone":"911119"}
{"dept":"艾米帝国","id":5,"name":"林雨裳","phone":"911119"}

从结果可以看出,第一次调用Service的时候,到Service内部获取数据。但是第二次调用Service时已经不需要从Service获取数据,证明第一次查询的时候已经把Customer对象缓存到EhCache中。

EhCache常用注解

注解 说明

@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存

@CacheConfig 统一配置本类的缓存注解的属性

@CachePut 保证方法被调用,又希望结果被缓存。与@Cacheable区别在于是否每次都调用方法,常用于更新

@CacheEvict 清空缓存

@Cacheable/@CachePut/@CacheEvict 主要的参数:

属性名 说明

value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}

key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”)

condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

unless 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)

allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)

beforeInvocation (@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

具体参考一点

Spring Boot整合Junit

示例:

pom.xml

 <!--springboot 集成 junit 起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.6.RELEASE</version>
<scope>test</scope>
</dependency> @RunWith(SpringJUnit4ClassRunner.class)
//重点是加入@SpringBootTest注解,属性classes用于加载引导类
@SpringBootTest(classes = MyBootApplication.class)
public class JUnitTest { @Test
public void test() {
System.out.println("Hello JUnit");
}
}

输出:

Hello JUnit

Spring Boot整合Quartz

Quartz(石英)简介

Quartz 是一个完全由Java 编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。Quartz 定时器作用很多,比如,定时发送信息和定时生成报表等。 Quartz 框架主要核心组件包括调度器、触发器和作业。调度器作为作业的总指挥,触发器 作为作业的操作者,作业为应用的功能模块。其关系如图:

示例:

pom.xml

  <!-- sping对schedule的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!--Quartz运行必须依赖到spring-tx包 注意:spring-boot-starter-web已经依赖了,所以不需要再依赖,只需要知道Quartz运行必须依赖到spring-tx包 -->
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>-->
<!-- Quartz支持 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

Job任务类——我们想实现的定时任务业务代码写在这里。

public class CheckJob {

    public void task() {
System.out.println("校验任务被触发,当前时间为:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
} } public class ReminderJob { //具体定时任务
public void task(){
System.out.println("呼吸提醒任务被触发,当前时间为:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}

Quartz配置类——每个调度任务都对应一个配置类

@Configuration
public class CheckQuartzConfig { @Bean
public CheckJob createCheckJob() {
return new CheckJob();
} /**
* 创建任务
*/
@Bean("checkJobDetail")
public MethodInvokingJobDetailFactoryBean checkJobDetailFactoryBean(CheckJob job) {
MethodInvokingJobDetailFactoryBean detailFactoryBean = new MethodInvokingJobDetailFactoryBean();
//设置任务对象
detailFactoryBean.setTargetObject(job);
//设置任务方法
detailFactoryBean.setTargetMethod("task");
return detailFactoryBean;
}
/**
* 触发器
*/
@Bean("checkTrigger")
public CronTriggerFactoryBean createTrigger(@Qualifier("checkJobDetail") MethodInvokingJobDetailFactoryBean bean) {
CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
triggerFactoryBean.setJobDetail(bean.getObject());
// 每天11点30分触发执行一次
triggerFactoryBean.setCronExpression("0 30 11 * * ? *");
return triggerFactoryBean;
}
/**
* 创建Schduler
*/
@Bean("checkScheduler")
public SchedulerFactoryBean createSchedulerFactoryBean(@Qualifier("checkTrigger") CronTriggerFactoryBean bean){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
//关联trigger
schedulerFactoryBean.setTriggers(bean.getObject());
return schedulerFactoryBean;
}
} @Configuration
public class QuartzConfig { @Bean
public ReminderJob createReminderJob() {
return new ReminderJob();
} @Bean("reminderJobDetail")
public MethodInvokingJobDetailFactoryBean reminderJobDetailFactoryBean(ReminderJob job) {
MethodInvokingJobDetailFactoryBean detailFactoryBean = new MethodInvokingJobDetailFactoryBean();
detailFactoryBean.setTargetObject(job);
detailFactoryBean.setTargetMethod("task");
return detailFactoryBean;
} @Bean("reminderTrigger")
public CronTriggerFactoryBean createTrigger(@Qualifier("reminderJobDetail") MethodInvokingJobDetailFactoryBean bean) {
CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
triggerFactoryBean.setJobDetail(bean.getObject());
//定时任务3秒执行一次
triggerFactoryBean.setCronExpression("0/3 * * * * ? *");
return triggerFactoryBean;
} @Bean("reminderScheduler")
public SchedulerFactoryBean createSchedulerFactoryBean(@Qualifier("reminderTrigger") CronTriggerFactoryBean bean){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setTriggers(bean.getObject());
return schedulerFactoryBean;
}
}

运行引导类。

输出:

呼吸提醒任务被触发,当前时间为:2020-05-21 11:29:51
呼吸提醒任务被触发,当前时间为:2020-05-21 11:29:54
呼吸提醒任务被触发,当前时间为:2020-05-21 11:29:57
呼吸提醒任务被触发,当前时间为:2020-05-21 11:30:00
校验任务被触发,当前时间为:2020-05-21 11:30:00
呼吸提醒任务被触发,当前时间为:2020-05-21 11:30:03
呼吸提醒任务被触发,当前时间为:2020-05-21 11:30:06
呼吸提醒任务被触发,当前时间为:2020-05-21 11:30:09

Cron表达式扩展

Cron表达式在线工具

Cron表达式教程

CronTrigger

CronTriggers往往比SimpleTrigger更有用,如果您需要基于日历的概念,而非SimpleTrigger完全指定的时间间隔,复发的发射工作的时间表。 CronTrigger,你可以指定触发的时间表如“每星期五中午”,或“每个工作日9:30时”,甚至“每5分钟一班9:00和10:00逢星期一上午,星期三星期五“。 即便如此,SimpleTrigger一样,CronTrigger拥有的startTime指定的时间表时生效,指定的时间表时,应停止(可选)结束时间。

Cron表达式

cron的表达式被用来配置CronTrigger实例。 cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。这些子表达式是分开的空白,代表:

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. Year (可选字段)

例 "0 0 12 ? * WED" 在每星期三下午12:00 执行,

个别子表达式可以包含范围, 例如,在前面的例子里("WED")可以替换成 "MON-FRI", "MON, WED, FRI"甚至"MON-WED,SAT". “*” 代表整个时间段.

每一个字段都有一套可以指定有效值,如

Seconds (秒) :可以用数字0-59 表示,

Minutes(分) :可以用数字0-59 表示,

Hours(时) :可以用数字0-23表示,

Day-of-Month(天) :可以用数字1-31 中的任一一个值,但要注意一些特别的月份

Month(月) :可以用0-11 或用字符串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示

Day-of-Week(每周):可以用数字1-7表示(1 = 星期日)或用字符口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示

“/”:为特别单位,表示为“每”如“0/15”表示每隔15分钟执行一次,“0”表示为从“0”分开始, “3/20”表示表示每隔20分钟执行一次,“3”表示从第3分钟开始执行

“?”:表示每月的某一天,或第周的某一天

“L”:用于每月,或每周,表示为每月的最后一天,或每个月的最后星期几如“6L”表示“每月的最后一个星期五”

“W”:表示为最近工作日,如“15W”放在每月(day-of-month)字段上表示为“到本月15日最近的工作日”

““#”:是用来指定“的”每月第n个工作日,例 在每周(day-of-week)这个字段中内容为"6#3" or "FRI#3" 则表示“每月第三个星期五”

常用Cron表达式

0 15 10 * * ? * 每天10点15分触发

0 15 10 * * ? 2017 2017年每天10点15分触发

0 * 14 * * ? 每天下午的 2点到2点59分每分触发

0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)

0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)

0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发

0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发

0 15 10 ? * 6#3 每月的第三周的星期五开始触发

Spring Boot整合Task

Spring自身有一个定时任务技术,叫Spring Task,本文讲解在Spring Boot应用中如何使用Spring Task。

cron表达式复习:

序号 说明 必填 允许值 通配符

1 秒 是 0-59 , - * /

2 分 是 0-59 , - * /

3 时 是 0-23 , - * /

4 日 是 1-31 , - * ? / L W

5 月 是 1-12 / JAN-DEC , - * /

6 周 是 1-7 or SUN-SAT , - * ? / L #

7 年 否 1970-2099 , - * /

  1. :表示匹配该域的任意值。假如在Minutes域使用, 即表示每分钟都会触发事件。
  2. ?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。
  3. -:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
  4. /:斜杠前面值表示起始时间开始触发,后面值表示每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着20分钟触发一次,从第5分钟开始,5,25,45等分别触发一次.
  5. ,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
  6. L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
  7. W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
  8. LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
  9. :用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

示例:

pom.xml

 <dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sping对schedule的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
</dependencies> //引导类必须加上@EnableScheding注解启动SpringTask
@SpringBootApplication
@EnableScheduling // 开启Spring Task
public class MyBootApplication { public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class,args);
} } @Component
public class WelcomeTask {
//“5/20”表示每隔20秒执行一次,“5”表示为从“5”秒开始
@Scheduled(cron = "5/20 * * * * ?")
public void task(){
System.out.println("欢迎任务被触发,当前时间为:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}

输出:

2020-05-21 14:04:33.166  INFO 17100 --- [  restartedMain] com.self.MyBootApplication               : Started MyBootApplication in 5.693 seconds (JVM running for 6.831)//启动成功时间33秒
欢迎任务被触发,当前时间为:2020-05-21 14:04:45
欢迎任务被触发,当前时间为:2020-05-21 14:05:05
欢迎任务被触发,当前时间为:2020-05-21 14:05:25
欢迎任务被触发,当前时间为:2020-05-21 14:05:45

spring-boot整合日志功能(logback、log4j2)

springboot为我们已经内置了log组件 。

springboot内置log组件

application.yml

#在application.yml文件中修改springboot内置日志级别,默认是info
#方式一:
#debug: true
#方式二:
logging:
level:
root: debug

日志级别从低到高为TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF,级别越高,打印的日志越少。举例说明:说将日志级别设定为Debug,那么DEBUG, INFO, WARN, ERROR, FATAL, OFF这几类日志都会打印。

application.properties文件中日志key的第三级的含义为"路径":

  • 填写root,能够指定整个项目(包含jdk源代码打印的日志)的日志级别;
  • 填写某个包名,能够指定该包下所有Java文件的日志级别,其余为被指定的Java文件的日志级别为默认级别:Info
  • 甚至可以指定任意Java文件

整合参考

日志框架选型——都log4J2或者logback都差不多

springboot整合logback

springboot整合log4J2


springboot整合logback

springboot默认依赖了logback,所以不需要添加依赖。

基本配置:

#官方文档中有提到, SpringBoot 的 Logging 配置的级别有7个:TRACE , DEBUG , INFO , WARN , ERROR , FATAL , OFF
#root日志以INFO级别输出
logging.level.root=INFO
#springframework.web日志以WARN级别输出
logging.level.org.springframework.web=WARN
#hibernate日志以ERROR级别输出
logging.level.org.hibernate=ERROR

在resources下创建logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan 配置文件如果发生改变,将会被重新加载 scanPeriod 检测间隔时间-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>spring-boot-log</contextName>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!-- 普通日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/spring-boot-log-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志命名:单个文件大于128MB 按照时间+自增i 生成log文件 -->
<fileNamePattern>log/spring-boot-log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>128MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 最大保存时间:30天-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 错误日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/spring-boot-log-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志命名:单个文件大于2MB 按照时间+自增i 生成log文件 -->
<fileNamePattern>log/spring-boot-log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 最大保存时间:180天-->
<maxHistory>180</maxHistory>
</rollingPolicy>
<append>true</append>
<!-- 日志格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 日志级别过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- additivity 避免执行2次 -->
<logger name="com.itstyle" level="INFO" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>

springboot整合log4J2

pom.xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="logs" />
<property name="FILE_NAME" value="hellospringboot-log" />
</Properties> <appenders> <console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger> <root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers> </configuration>

配置参数详解

日志级别

机制:如果一条日志信息的级别大于等于配置文件的级别,就记录。

  • trace:追踪,就是程序推进一下,可以写个trace输出
  • debug:调试,一般作为最低级别,trace基本不用。
  • info:输出重要的信息,使用较多
  • warn:警告,有些信息不是错误信息,但也要给程序员一些提示。
  • error:错误信息。用的也很多。
  • fatal:致命错误。

输出源

  • CONSOLE(输出到控制台)
  • FILE(输出到文件)

格式

  • SimpleLayout:以简单的形式显示
  • HTMLLayout:以HTML表格显示
  • PatternLayout:自定义形式显示

PatternLayout自定义日志布局:

%d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间,输出到毫秒的时间
%-5level : 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%c : logger的名称(%logger)
%t : 输出当前线程名称
%p : 日志输出格式
%m : 日志内容,即 logger.info("message")
%n : 换行符
%C : Java类名(%F)
%L : 行号
%M : 方法名
%l : 输出语句所在的行数, 包括类名、方法名、文件名、行数
hostName : 本地机器名
hostAddress : 本地ip地址

Log4j2配置详解

1、根节点Configuration

有两个属性:

  • status
  • monitorinterval

有两个子节点:

  • Appenders
  • Loggers(表明可以定义多个Appender和Logger).

status用来指定log4j本身的打印日志的级别.

monitorinterval用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s.

2、Appenders节点

常见的有三种子节点:Console、RollingFile、File

Console节点用来定义输出到控制台的Appender.

  • name:指定Appender的名字.
  • target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.
  • PatternLayout:输出格式,不设置默认为:%m%n.

File节点用来定义输出到指定位置的文件的Appender.

  • name:指定Appender的名字.
  • fileName:指定输出日志的目的文件带全路径的文件名.
  • PatternLayout:输出格式,不设置默认为:%m%n.

RollingFile节点用来定义超过指定条件自动删除旧的创建新的Appender.

  • name:指定Appender的名字.
  • fileName:指定输出日志的目的文件带全路径的文件名.
  • PatternLayout:输出格式,不设置默认为:%m%n.
  • filePattern : 指定当发生Rolling时,文件的转移和重命名规则.
  • Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.
  • TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.
  • SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.
  • DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。

Loggers节点,常见的有两种:Root和Logger.

Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出

  • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender.
  • Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
  • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
  • name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
  • AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。

实例:

LoggerFactory创建Logger类。

@Component
public class WelcomeTask {
private static final Logger logger = LoggerFactory.getLogger(UserController.class); //“5/20”表示每隔20秒执行一次,“5”表示为从“5”秒开始
@Scheduled(cron = "5/5 * * * * ?")
public void task(){
logger.debug("进入WelcomeTask 定时任务!");
System.out.println("欢迎任务被触发,当前时间为:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
logger.error("完成WelcomeTask 定时任务!");
}
}

lombok工具简化创建Logger类。

@Component
@Slf4j
public class WelcomeTask {
//“5/20”表示每隔20秒执行一次,“5”表示为从“5”秒开始
@Scheduled(cron = "5/5 * * * * ?")
public void task(){
log.debug("进入WelcomeTask 定时任务!");
System.out.println("欢迎任务被触发,当前时间为:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
log.error("完成WelcomeTask 定时任务!");
}
}

lombok使用参考教程。

lombok就是一个注解工具jar包,能帮助我们省略一繁杂的代码。

lombok使用实践

pom.xml依赖

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>

IDEA插件里搜索lombok插件安装,重启 。

常用注解

  1. @Setter 注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
  2. @Getter 使用方法同上,区别在于生成的是getter方法。
  3. @ToString 注解在类,添加toString方法。
  4. @EqualsAndHashCode 注解在类,生成hashCode和equals方法。
  5. @NoArgsConstructor 注解在类,生成无参的构造方法。
  6. @RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
  7. @AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。
  8. @Data 注解在类,为类的所有字段注解@ToString、@EqualsAndHashCode、@Getter的便捷方法,同时为所有非final字段注解@Setter。

疑问:

1、Q:软件版本的GA 代表什么意思?

A:GA:General Availability,正式发布的版本,在国外都是用GA来说明release版本的 .

引申:

参考

Alpha:是内部测试版,一般不向外部发布,会有很多Bug.一般只有测试人员使用。

Beta:也是测试版,这个阶段的版本会一直加入新的功能。在Alpha版之后推出。

RC:(Release Candidate) 顾名思义么 ! 用在软件上就是候选版本。系统平台上就是发行候选版本。RC版不会再加入新的功能了,主要着重于除错。

GA:General Availability,正式发布的版本,在国外都是用GA来说明release版本的。

RTM:(Release to Manufacture)是给工厂大量压片的版本,内容跟正式版是一样的,不过RTM版也有出限制、评估版的。但是和正式版本的主要程序代码都是一样的。

OEM:是给计算机厂商随着计算机贩卖的,也就是随机版。只能随机器出货,不能零售。只能全新安装,不能从旧有操作系统升级。包装不像零售版精美,通常只有一面CD和说明书(授权书)。

RVL:号称是正式版,其实RVL根本不是版本的名称。它是中文版/英文版文档破解出来的。

EVAL:而流通在网络上的EVAL版,与“评估版”类似,功能上和零售版没有区别。

RTL:Retail(零售版)是真正的正式版,正式上架零售版。在安装盘的i386文件夹里有一个eula.txt,最后有一行EULAID,就是你的版本。比如简体中文正式版是EULAID:WX.4_PRO_RTL_CN,繁体中文正式版是WX.4_PRO_RTL_TW。其中:如果是WX.开头是正式版,WB.开头是测试版。PRE,代表家庭版;PRO,代表专业版。

α、β、λ常用来表示软件测试过程中的三个阶段,α是第一阶段,一般只供内部测试使用;β是第二个阶段,已经消除了软件中大部分的不完善之处,但仍有可能还存在缺陷和漏洞,一般只提供给特定的用户群来测试使用;λ是第三个阶段,此时产品已经相当成熟,只需在个别地方再做进一步的优化处理即可上市发行。

2、Q:springboot的核心功能起步依赖和自动配置详解?

3、Q: 自定义配置怎么取值使用,什么场景使用?

4、Q: 每次发布的时候替换掉配置文件,这样太麻烦了,Spring Boot的Profile就给我们提供了解决方案,命令带上参数就搞定。 是指打包命令带上参数就能自动加载不同的环境变量配置么?我们一般是如何打生产包或测试包部署的?怎么通过jekeins实现不同环境的打包部署?

5、Q: yml配置文件属性值是大小写敏感的么?教程里说是,可实际测试中并不是?怎么理解?

#基本类型 注意:属性值大小写敏感
firstName: Bruce Wayne1111
age: 29 @Controller
public class ConfigController { @Value("${firstname}")
private String name;
@Value("${age}")
private Integer age; @RequestMapping("/show")
@ResponseBody
public String showConfig() {
return name + " : " + age;
}
}

输出:

Bruce Wayne1111 : 29

6、Q: 下面@Value取值的表达式怎么理解符号#的作用?已经什么时候可以用split()方法?

 @Value("#{'${user.list}'.split(',')}")
private List<String> list;

7、Q: Spring Boot3种热部署方式前两种如何实现?

Spring Boot有3种热部署方式:

  1. 使用springloaded配置pom.xml文件,使用mvn spring-boot:run启动
  2. 使用springloaded本地加载启动,配置jvm参数
  3. 使用devtools工具包,操作简单,但是每次需要重新部署

8、Q: 什么是Thymeleaf ?

9、Q: @Configuration注解的作用?对于下面这个异常处理类的作用?

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver {
}

10、Q: java注解和spring注解需要系统研究一遍。

11、Q: 启动类SpringbootDemoApplication其实就是一个标准的Spring纯注解下的启动类 。怎么理解Spring纯注解下的启动类?

12、Q: SPI 方案?

A:SPI 全称为 (Service Provider Interface),即服务提供商接口,是JDK内置的一种服务提供发现机制。目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现服务实现者的机制。

Java扩展方法之SPI

其他:

1、COC:Convention over Configuration,即约定大于配置。

72法则

72法则指以1%的复利计息,72年后(72是约数,准确值是ln2/ln1.01),本金翻倍的规律。

SpringBoot教程——检视阅读的更多相关文章

  1. Spring MVC教程——检视阅读

    Spring MVC教程--检视阅读 参考 Spring MVC教程--一点--蓝本 Spring MVC教程--c语言中午网--3.0版本太老了 Spring MVC教程--易百--4.0版本不是通 ...

  2. Redis教程——检视阅读

    Redis教程--检视阅读 参考 Redis教程--菜鸟--蓝本--3.2.100 Redis教程--w3c--3.2.100 Redis教程--w3c--Redis开发运维实践指南 Redis教程- ...

  3. Spring教程检视阅读

    Spring教程检视阅读 地址 可供参考的教程 <菜鸟学 SSH> <Spring Boot 那些事> <初识 Spring Security> <Sprin ...

  4. MySQL 教程--检视阅读

    MySQL 教程--检视阅读 准备:Windows 上安装 MySQL 教程地址,PHP语言基础 教程地址2 教程地址3,有讲数据库的备份和恢复 教程地址4,w3c.china,php基础,扩展阅读 ...

  5. MyBatis 教程 ——检视阅读

    MyBatis 教程 --检视阅读 准备 官网文档-中文 教程地址yiibai,质量很差 教程地址w3cschool,纯理论,还不如直接看官网文档 教程地址Mybatis框架入门教程,Oracle M ...

  6. NIO教程 ——检视阅读

    NIO教程 --检视阅读 参考 BIO,NIO,AIO 总结 Java NIO浅析 Java NIO 教程--极客,蓝本 Java NIO 系列教程 --并发编程网 BIO,NIO--知乎 NIO 入 ...

  7. JDBC教程——检视阅读

    JDBC教程--检视阅读 参考 JDBC教程--W3Cschool JDBC教程--一点教程,有高级部分 JDBC教程--易百 JDBC入门教程 – 终极指南 略读 三层架构详解,JDBC在数据访问层 ...

  8. AJAX教程——检视阅读

    AJAX教程--检视阅读 参考 AJAX 教程--菜鸟 AJAX 教程--w3cschool AJAX 教程--w3school.cn AJAX 教程--易百 AJAX = Asynchronous ...

  9. Java Web教程——检视阅读

    Java Web教程--检视阅读 参考 java web入门--概念理解.名词解释 Java Web 教程--w3school 蓝本 JavaWeb学习总结(一)--JavaWeb开发入门 小猴子mo ...

随机推荐

  1. 2019-2020-1 20199326《Linux内核原理与分析》第二周作业

    本周总结:本周的学习内容主要是庖丁解牛Linux的第一章,然后看完书后,又跟着云班课加深学习了一下第一章的内容.第一章主要讲述了linux里的汇编指令的一些指令,比如movl,pushl,popl等等 ...

  2. Shutdown SpringBoot App

    文章目录 Shutdown Endpoint close Application Context 退出SpringApplication 从外部程序kill App Shutdown SpringBo ...

  3. 使用Spring Boot搭建你的第一个应用程序

    文章目录 依赖配置 main程序配置 MVC配置 安全配置 存储 Web 页面和Controller 异常处理 测试 结论 Spring Boot是Spring平台的约定式的应用框架,使用Spring ...

  4. zoj_2511 Design T-Shirt 贪心

    Design T-Shirt Time Limit: 2 Seconds      Memory Limit: 32768 KB Soon after he decided to design a T ...

  5. linux rpm包

    rpm包,软件包,程序包,以.rpm结尾的包 我们刚开始安装的Linux系统是最小化安装(minimol),只安装系统,不安装不必要的软件包 刚开始vim,ifconfig,tree等命令都没有,当然 ...

  6. 配置IIS5.5/6.0 支持 Silverlight

    在安装完Silverlight1.1 Alpha后,要使自己的IIS服务器支持Silverlight的浏览还需要配置一下IIS网站的 Http头->MIME映射添加内容如下:扩展名        ...

  7. spark下dataframe转为rdd格式

    dataframe可以实现很多操作,但是存储到本地的时候,只能存 parquest格式 需要存储源格式,需要转换为rdd类型 将dataframe中的每一行都map成有逗号相连的string,就变为了 ...

  8. 如何理解Java的值传递

    结论 为了加深印象,先把结论放在文章开头. ++Java中只有值传递++. 形参与实参 在理解Java的值传递 实参Argument 实际参数,主调用函数传递给调用函数的参数 形参Parameter ...

  9. C++编程入门题目--No.5

    题目: 输入三个整数x,y,z,请把这三个数由小到大输出. 程序分析: 我们想办法把最小的数放到x上,先将x与y进行比较,如果x>y则将x与y的值进行交换, 然后再用x与z进行比较,如果x> ...

  10. Codeforces Round 623(Div. 2,based on VK Cup 2019-2020 - Elimination Round,Engine)D. Recommendations

    VK news recommendation system daily selects interesting publications of one of n disjoint categories ...