SpringBoot入门
1.1 Spring Boot简介
Spring作为一个轻量级的容器,在Java EE开发中得到了广泛的应用,但是Spring的配置烦琐臃肿,在和各种第三方框架进行整合时代码量都非常大,并且整合的代码大多是重复的,为了使开发者能够快速上手Spring,利用Spring框架快速搭建Java EE项目,Spring Boot应运而生。
Spring Boot带来了全新的自动化配置解决方案,使用Spring Boot可以快速创建基于Spring生产级的独立应用程序。Spring Boot中对一些常用的第三方库提供了默认的自动化配置方案,使得开发者只需要很少的Spring配置就能运行一个完整的Java EE应用。Spring Boot项目可以采用传统的方案打成war包,然后部署到Tomcat中运行。也可以直接打成可执行jar包,这样通过java -jar命令就可以启动一个Spring Boot项目。总体来说,Spring Boot主要有如下优势:
- 提供一个快速的Spring项目搭建渠道。
- 开箱即用,很少的Spring配置就能运行一个Java EE项目,有自己自定义的配置就是用自己的,没有就使用官方提供的默认的配置。
- 提供了生产级的服务监控方案。
- 内嵌服务器,可以快速部署。
- 提供了一系列非功能性的通用配置。
- 纯Java配置,没有代码生成,也不需要XML配置。
Spring Boot 的出现让 Java 开发又回归简单,因为确确实实解决了开发中的痛点,因此这个技术得到了非常广泛的使用,现在面试Spring Boot基本就是必问,现在流行的 Spring Cloud 微服务也是基于 Spring Boot,因此,所有的 Java 工程师都有必要掌握好 Spring Boot。
1.2 环境描述
Name | Version |
---|---|
SpringBoot | 2.3.3 |
Maven | 3.6 |
IDEA | 2018 |
JDK | 1.8 |
1.3 三种创建方式
Spring Boot 工程本质上就是一个 Maven 工程 ,我们学一下下面这三种创建方式
1.3.1 在线创建
在线创建是官方提供的一个创建方式,实际上,如果我们使用开发工具去创建 Spring Boot 项目的话(即第二种方案),也是从这个网站上创建的,只不过这个过程开发工具帮助我们完成了,我们只需要在开发工具中进行简单的配置即可。
1.3.2 使用开发工具创建
在挂网提供的地址里面创建感觉很繁琐,不太方便,一般情况我们也不会在这上面创建然后在导入到IDEA中。我们一般直接在开发工具中创建,比如IDEA就有很好的支持。
IntelliJ IDEA
IntelliJ IDEA 只有 ultimate 版才有直接创建 Spring Boot 项目的功能,社区版是没有此项功能。
- 首先在创建项目时选择 Spring Initializr 如图:
- 然后点击 Next ,填入 Maven 项目的基本信息,如下:
- 再接下来选择需要添加的依赖,如下图:
勾选完成后,点击 Next 完成项目的创建。
- STS
这里我再介绍下 Eclipse 派系的 STS 给大家参考, STS 创建 Spring Boot 项目,官网提供那个网站上来的,步骤如下:
- 首先右键单击,选择 New -> Spring Starter Project ,如下图:
- 然后在打开的页面中填入项目的相关信息,如下图:
这里的信息和前面提到的都一样,不再赘述。最后一路点击 Next ,完成项目的创建。
1.3.3 Maven创建
上面提到的几种方式,实际上都借助了 https://start.spring.io/ 这个网站,假如网站不是很稳定,经常发生项目创建失败的情况,这时候怎么办嘞,我们可以直接使用 Maven 来创建项目。
步骤如下:首先创建一个普通的 Maven 项目,以 IntelliJ IDEA 为例,创建步骤如下:
注意这里不用选择项目骨架(如果大伙是做练习的话,也可以去尝试选择一下,这里大概有十来个
Spring Boot 相关的项目骨架),直接点击 Next ,下一步中填入一个 Maven 项目的基本信息,如下
图:
然后点击 Next 完成项目的创建。
创建完成后,在 pom.xml 文件中,添加如下依赖 :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
添加成功后,再在 java 目录下创建包,包中创建一个名为 App 的启动类,如下:
@SpringBootApplication
@RestController
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
@RequestMapping("hello")
public String hello(){
return "Hello Spring Boot !";
}
}
然后执行这里的 main 方法就可以启动一个 Spring Boot 工程了。访问http://localhost:8080/hello可以看到页面有正常的显示。
1.4 SpringBoot 第一个程序
上面介绍了创建SpringBoot项目的方式,下面完成我们的第一程序
1.4.1 工具创建的项目结构
对于我们来说,src 是最熟悉的, Java 代码和配置文件写在这里,test 目录用来做测试,pom.xml 是Maven 的坐标文件,就这几个。
创建一个Controller
@RestController
@RequestMapping("/hello")
public class AppController {
@RequestMapping("/one")
public String helloTest(){
return "hello,SpringBoot";
}
}
启动项目
1.4.2 打包启动
当然,Spring Boot应用也可以直接打成jar包运行。在生产环境中,也可以通过这样的方式来运行一个Spring Boot应用。要将Spring Boot打成jar包运行,首先需要添加一个plugin到pom.xml文件中,代码如下:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后运行mvn命令进行打包,代码如下
mvn package
打包完成后,在项目的target目录下会生成一个jar文件,通过java -jar命令直接启动这个jar文件,如图
一个Spring Boot项目就构建好并成功启动了。
SpringBoot基础配置
2.1 parent详解
前面聊了聊Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
当我们创建一个 Spring Boot 工程时,可以继承自一个 spring-boot-starter-parent ,也可以不继承自它,我们先来看第一种情况。
继承parent
org.springframework.boot
他的父项目是spring-boot-dependencies
他来真正管理SpringBoot应用里面的所有依赖版本;Spring Boot的版本仲裁中心;以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号)
不继承parent
有时公司里边会有自己定义的 parent ,我们的Spring Boot 项目要继承自公司内部的 parent ,这个时候该怎么办呢?简单的办法就是我们自行定义 dependencyManagement 节点,然后在里边定义好版本号,再接下来在引用依赖时也就不用写版本号了,如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
这样写之后,依赖的版本号问题虽然解决了,但是关于打包的插件、编译的 JDK 版本、文件的编码格式等等这些配置,在没有 parent 的时候,这些统统要自己去配置。
2.2 @SpringBootApplication
在前文了解到@SpringBootApplication
注解是加在项目的启动类上的。@SpringBootApplication
实际上是一个组合注解
2.3 定制banner
下面学习一个有趣的知识,Spring Boot项目在启动时打印一个banner
这个banner是可以定制的,在resources目录下创建一个banner.txt文件,在这个文件中写入的文本将在项目启动时打印出来。如果想将TXT文本设置成艺术字体,有以下几个在线网站可供参考:
- http://www.network-science.de/ascii/
- http://www.kammerl.de/ascii/AsciiSignature.php
- http://patorjk.com/software/taag
- 下面按照第一个网址为例。打开后输入要设置的文本,单击“do it!”按钮,将生成的文本复制到banner.txt文件中(文件名一定为banner.txt)。
- 复制完成后再启动项目,就可以看到banner发生了变化
- 想关闭banner也是可以的,在配置文件中配置
spring.main.banner-mode=off
2.4 SpringBoot配置文件
在 Spring Boot 中,配置文件有两种不同的格式,一个是 properties ,另一个是 yaml 。 虽然 properties 文件比较常见,但是相对于 properties 而言,yaml 更加简洁明了,而且使用的场景也更多,很多开源项目都是使用 yaml 进行配置。
除了简洁,yaml 还有另外一个特点,就是 yaml 中的数据是有序的,properties 中的数据是无序的,在一些需要路径匹配的配置中,顺序就显得尤为重要(例如我们在 Spring Cloud Zuul 中的配置),此时我们一般采用 yaml。
2.4.1 application.properties
位置问题
当我们创建一个 Spring Boot 工程时,默认 resources 目录下就有一个 application.properties
文件,可以在 application.properties
文件中进行项目配置,但是这个文件并非唯一的配置文件,在Spring Boo
t 中,一共有 4 个地方可以存放 application.properties 文件。
- 当前项目根目录下的 config 目录下
- 当前项目的根目录下
- resources 目录下的 config 目录下
- resources 目录下
按如上顺序,四个配置文件的优先级依次降低。如下:
:::info 这四个位置是默认位置,即 Spring Boot 启动,默认会从这四个位置按顺序去查找相关属性并加载。但也不是绝对的,我们也可以在项目启动时自定义配置文件位置。
例如,现在在 resources 目录下创建一个 bootconfig目录,目录中存放一个application.properties 文件,那么正常情况下,当我们启动 Spring Boot 项目时,这个配置文件是不会被自动加载的我们可以通过 spring.config.location
属性来手动的指定配置文件位置,指定完成后,系统就会自去指定目录下查找 application.properties 文件
此时启动项目,就会发现,项目以 classpath:/bootconfig/application.propertie 配置文件启动。
:::
上方是在开发工具中配置了启动位置,如果项目已经打包成 jar ,在启动命令中加入位置参数即可:
java -jar properties-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:/bootconfig/
文件名问题
对于 application.properties
而言,它不一定非要叫 application
,但是项目默认是去加载名为application
的配置文件,如果我们的配置文件不叫application
,就需要明确指定配置文件的文件名。方式和指定路径一致,只不过此时的 key 是 spring.config.name
。首先我们在 resources
目录下创建一个 app.properties
文件,然后在 IDEA 中指定配置文件的文件名:
指定完配置文件名之后,再次启动项目,此时系统会自动去默认的四个位置下面分别查找名为
.properties 的配置文件。当然,允许自定义文件名的配置文件不放在四个默认位置,而是放在自定义目录下,此时就需要明确指定 spring.config.location 。配置文件位置和文件名称可以同时自定义。
2.4.2 application.yaml
存放位置
首先 application.yaml 在 Spring Boot 中可以写在四个不同的位置,分别是如下位置:
- 项目根目录下的 config 目录中
- 项目根目录下
- classpath 下的 config 目录中
- classpath 目录下
四个位置中的 application.yaml 文件的优先级按照上面列出的顺序依次降低。即如果有同一个属性在四个文件中都出现了,以优先级高的为准。当然这四个位置也是可以自己定义,有两种方式,一个是使用 spring.config.location
属性,另一个则是使用 spring.config.additional-location
这个属性。
- 第一种方式,表示自己重新定义配置文件的位置,项目启动时就按照定义的位置去查找配置文件,这种定义方式会覆盖掉默认的四个位置
- 第二种方式,表示在四个位置的基础上,再添加几个位置,新添加的位置的优先级大于原本的位置。
自定义配置文件的名字
那么 application.yaml 是不是必须叫 application.yaml 这个名字呢?当然不是必须的。开发者可以自己定义 yaml 名字,自己定义的话,需要在项目启动时指定配置文件的名字,像下面这样:
当然这是在 IntelliJ IDEA 中直接配置的,如果项目已经打成 jar 包了,则在项目启动时加入如下参数:
java -jar xxx.jar --spring.config.name=app
这样配置之后,在项目启动时,就会按照上面所说的四个位置按顺序去查找一个名为 app.yaml 的文件。
2.5 Yaml语法
Yaml 以空格的缩进程度来控制层级关系。空格的个数并不重要,只要左边空格对齐则视为同一个层级。注意不能用tab代替空格。且大小写敏感。支持字面值,对象,数组三种数据结构,也支持复合结构。
k:
(空格)v:表示一对键值对(空格必须有);
以 空格 的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
server:
port: 8081
path: /hello
123
基本格式要求
- YAML大小写敏感;
- 使用缩进代表层级关系;
- 缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)
- 注释和properties相同,使用
#
作为注释,YAML中只有行注释。
值的写法
- 字面值:字符串,布尔类型,数值,日期。字符串默认不加引号,单引号会转义特殊字符,双引号不会。日期格式支持yyyy/MM/dd HH:mm:ss
- 对象:由键值对组成,形如 key:(空格)value 的数据组成。冒号后面的空格是必须要有的,每组键值对占用一行,且缩进的程度要一致,也可以使用行内写法:{k1: v1, …kn: vn}
- 数组:由形如 -(空格)value 的数据组成。短横线后面的空格是必须要有的,每组数据占用一行,且缩进的程度要一致,也可以使用行内写法: [1,2,…n]
- 复合结构:上面三种数据结构任意组合
2.6 Profile
开发者在项目发布之前,一般需要频繁地在开发环境、测试环境以及生产环境之间进行切换,这个时候大量的配置需要频繁更改,例如数据库配置、redis配置等。频繁修改带来了巨大的工作量,Spring对此提供了解决方案(@Profile注解),Spring Boot则更进一步提供了更加简洁的解决方案,Spring Boot中约定的不同环境下配置文件名称规则为 application-{profile}.properties
,profile占位符表示当前环境的名称,具体配置步骤如下:
2.6.1 创建配置文件
首先在resources目录下创建两个配置文件:application-dev.properties
和application-prod.properties
,分别表示开发环境中的配置和生产环境中的配置。其中,application-dev.properties
文件的内容如下:
server.port=8081
application-prod.properties文件的内容如下:
server.port=8082
这里为了简化问题并且容易看到效果,两个配置文件中主要修改了一下项目端口号。
2.6.2 配置application.properties
上面操作完后,我们在 application.properties
中进行配置:
spring.profiles.active=dev
这个表示使用application-dev.properties配置文件启动项目,若将dev改为prod,则表示使用application-prod.properties启动项目。项目启动成功后,就可以通过相应的端口进行访问了。
2.6.3 项目启动时配置
对于第2步提到的配置方式,也可以在将项目打成jar包后启动时,在命令行动态指定当前环境,示例命令如下:
java -jar xxx.jar --spring.profiles.active=dev
SpringBoot整合视图层
在目前的企业级应用开发中,前后端分离是趋势,但是视图层技术还占有一席之地。Spring Boot对视图层技术提供了很好的支持,官方推荐使用的模板引擎是Thymeleaf,不过像FreeMarker也支持,JSP技术在这里并不推荐使用。下面学习Spring Boot整合Thymeleaf和FreeMarker两种视图层技术。
3.1 Thymeleaf 简介
Thymeleaf 是新一代 Java 模板引擎,它类似于 Velocity、FreeMarker 等传统 Java 模板引擎,但是与传统 Java 模板引擎不同的是,Thymeleaf 支持 HTML 原型。
它既可以让前端工程师在浏览器中直接打开查看样式,也可以让后端工程师结合真实数据查看显示效果,同时,SpringBoot 提供了 Thymeleaf 自动化配置解决方案,因此在 SpringBoot 中使用Thymeleaf 非常方便。
Thymeleaf 除了展示基本的 HTML ,进行页面渲染之外,也可以作为一个 HTML 片段进行渲染,例如我们在做邮件发送时,可以使用 Thymeleaf 作为邮件发送模板。
另外,由于 Thymeleaf 模板后缀为 .html ,可以直接被浏览器打开,因此,预览时非常方便。
3.2 SpringBoot中使用Thymeleaf
Spring Boot 中整合 Thymeleaf 非常容易,只需要创建项目时添加 Thymeleaf 即可:
创建完成后,pom.xml 依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建Controller,实际上引入 Thymeleaf 依赖之后,我们可以不做任何配置。
@Controller
public class HomeController {
@GetMapping("/home")
public String index(Model model) {
List<User> users = new ArrayList<>();
//创建假数据
for (int i = 0; i < 10; i++) {
User u = new User();
u.setId(i);
u.setName("测试 - " + i);
u.setAge(i+10);
u.setAddress("上海 - " + i);
users.add(u);
}
model.addAttribute("users", users);
return "home";
}
}
public class User {
private Integer id;
private String name;
private Integer age;
private String address;
//get 和 set 省略
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td>编号</td>
<td>用户名</td>
<td>年龄</td>
<td>地址</td>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.address}"></td>
</tr>
</table>
</body>
</html>
在 Thymeleaf 中,通过 th:each 指令来遍历一个集合,数据的展示通过 th:text 指令来实现,注意 index.html 最上面要引入 thymeleaf 名称空间。配置完成后,就可以启动项目了,访问 /index 接口,就能看到集合中的数据了
Thymeleaf 支持在 js 中直接获取 Model 中的变量。例如,在 homeController 添加一个请求方法。有一个变量 name:
@GetMapping("/getname")
public String getName(Model model){
model.addAttribute("name","宋祖儿");
return "home";
}
<script th:inline="javascript">
var name = [[${name}]]
console.log(name)
</script>
3.3 Freemarker 简介
老牌的开源的免费的模版引擎。通过 Freemarker 模版,我们可以将数据渲染成 HTML 网页、电子邮件、配置文件以及源代码等。Freemarker 不是面向最终用户的,而是一个 Java 类库,我们可以将之作为一个普通的组件嵌入到我们的产品中。
来看一张来自 Freemarker 官网的图片:
可以看到,Freemarker 可以将模版和数据渲染成 HTML 。
Freemarker 模版后缀为.ftl (FreeMarker Template Language)
。FTL 是一种简单的、专用的语言,它不是像 Java 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
接下来我们来看看 Freemarker 和 Spring Boot 的一个整合操作。
3.4 SpringBoot整合Freemarker
Spring Boot 中整合 Freemarker 非常容易,只需要创建项目时添加 Freemarker 即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
工程创建完成后,在 FreeMarkerAutoConfiguration
.类中,可以看到关于 Freemarker 的自动化配置:
- 创建实体类
public class User {
private Integer id;
private String name;
private Integer age;
private String address;
//get 和 set 省略
}
- 创建Controller
@Controller
public class HomeController {
@GetMapping("/home")
public String index(Model model) {
List<User> users = new ArrayList<>();
//创建假数据
for (int i = 0; i < 10; i++) {
User u = new User();
u.setId(i);
u.setName("测试 - " + i);
u.setAge(i+10);
u.setAddress("上海 - " + i);
users.add(u);
}
model.addAttribute("users", users);
return "home";
}
}
- 创建视图
home.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td>编号</td>
<td>用户名</td>
<td>年龄</td>
<td>地址</td>
</tr>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.address}</td>
</tr>
</#list>
</table>
</body>
</html>
效果:
SpringBoot整合Web开发
4.1 JSON数据
4.2 SpringBoot 中静态资源配置
4.2.1 SSM中的配置方式
我们使用 SpringMVC 框架时,静态资源会被拦截,需要添加额外配置。在学习SpringBoot中处理之前,我们先回顾SSM 环境中怎样操作的。一般我们通过<mvc:resources />
节点来配置不拦截静态资源,如下:
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/html/**" location="/html/"/>
这种配置是在 XML 中的配置,大家知道,SpringMVC 的配置除了在XML中配置,也可以在 Java 代码中配置,如果在 Java 代码中配置的话,我们只需要自定义一个类,继承自WebMvcConfigurationSupport 即可:
@Configuration
@ComponentScan(basePackages = "com.ssm.test")
public class SpringMVCConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/");
}
}
重写 WebMvcConfigurationSupport 类中的 addResourceHandlers 方法,在该方法中配置静态资源
位置即可,这里的含义和上面 xml 配置的含义一致。
这是我们传统的解决方案,在 Spring Boot 中,其实配置方式和这个一脉相承,只是有一些自动化的配置。
4.2.2 SpringBoot 中的配置方式
Spring Boot 初始化工具创建的项目,静态资源默认都会存在 resources/static 目录,只要放到这个目录下,就可以直接访问,除了这里还有没有其他可以放静态资源的位置呢?为什么放在这里就能直接访问了呢?我们一起探究一下。
首先,在 Spring Boot 中,默认情况下,一共有 5 个位置可以放静态资源,五个路径分别是如下 5 个:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/
- 前四个目录对应了 resources 目录下不同的目录,第 5 个
/
是啥意思呢?
在Spring Boot 项目中,默认是没有 webapp 这个目录的,当然我们也可以自己添加(例如在需要使用JSP的时候),这里第 5 个 / 其实就是表示 webapp 目录中的静态资源也不被拦截。如果同一个文件分别出现在五个目录下,那么优先级也是按照上面列出的顺序
虽然有 5 个存储目录,除了第 5 个用的比较少之外,其他四个,系统默认创建了classpath:/static/ , 正常情况下,我们只需要将我们的静态资源放到这个目录下即可,也不需要额外去创建其他静态资源目录,例如我在 classpath:/static/ 目录下放了一张名为one.png
的图片,那么我的访问路径是:
http://localhost:8080/one.png
这里大家注意,请求地址中并不需要 static,如果加上了 static 反而多此一举会报 404 错误。很多人会觉得奇怪,为什么不需要添加 static 呢?资源明明放在 static 目录下。其实这个效果很好实现,例如在SSM 配置中,我们的静态资源拦截配置如果是下面这样:
<mvc:resources mapping="/**" location="/static/"/>
如果我们是这样配置的话,请求地址如果是 http://localhost:8080/1.png 实际上系统去/static/one.png
目录下查找相关的文件。在 Spring Boot 中可能也是类似的配置
4.2.3 自定义配置
当然假如说我就是不想放在默认路径下,那怎么办嘞,别怕,下面我们学习一下如何自定义配置。自定义的方式也有两种,可以通过 application.properties 来定义,也可以在 Java 代码中来定义配置类,下面分别来看。
application.properties
spring.resources.static-locations=classpath:/ # 第一行配置表示定义资源位置,
spring.mvc.static-path-pattern=/** # 第二行配置表示定义请求 URL 规则
以上文的配置为例,如果我们这样定义了,表示可以将静态资源放在 resources 目录下的任意地方,我们访问的时候当然也需要写完整的路径,例如在 resources/static 目录下有一张名为 one.png 的图片,那么访问路径就是http://localhost:8080/static/one.png ,注意此时的 static 不能省略
- Java 代码定义配置类
我们也可以通过Java编码方式来定义,此时只需要实现WebMvcConfigurer接口即可,然后实现该接口的addResourceHandlers方法,代码如下:
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/test/");
}
}
4.3 @ControllerAdvice 三种使用场景
严格来说,@ControllerAdvice不是 Spring Boot 中的知识点,而是SpringMVC中的知识,但对于
@ControllerAdvice 并很多童鞋不熟悉,Spring Boot 和 SpringMVC 一脉相承,@ControllerAdvice 在 SpringBoot 中也有广泛的使用场景。
@ControllerAdvice 是一个非常有用的注解,顾名思义,这是一个增强的 Controller。一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用。使用它可以实现三个方面的功能:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
4.3.1 全局异常处理
使用 @ControllerAdvice 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", e.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
@ExceptionHandler(NullPointerException.class)
public ModelAndView custoNullException(Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", e.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。
4.3.2 全局数据绑定
全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了
@ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数
据。使用步骤,首先定义全局数据,如下:
@ModelAttribute(name = "test")
public Map<String,Object> date(){
Map map = new HashMap();
map.put("title","一寸光阴一寸金");
return map;
}
使用 @ModelAttribute 注解标记该方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key就是返回的变量名,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据:
@GetMapping("/test")
@ResponseBody
public String test(Model model){
Map map = model.asMap();
System.out.println(map);
return "test";
}
测试结果
{test={title=一寸光阴一寸金}}
4.3.3 全局数据预处理
@ControllerAdvice结合@InitBinder还能实现请求参数预处理,即将表单中的数据绑定到实体类上时进行一些额外处理
例如有两个实体类Book和Author,代码如下:
public class Book {
private String name;
private Long price;
//getter/setter
}
public class Author {
private String name;
private Integer age;
//getter/setter
}
Controller上需要接收两个实体类的数据,Controller中的方法定义如下:
@GetMapping("/book")
public void addBook(Book book, Author author) {
System.out.println(book);
System.out.println(author);
}
这个时候,因为两个实体类都有一个 name 属性,从前端传递时 ,无法区分。此时,通过 @ControllerAdvice 的全局数据预处理可以解决这个问题,解决步骤如下:
- 给接口中的变量取别名
@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a")Author author) {
System.out.println(book);
System.out.println(author);
}
- 进行请求数据预处理
在 @ControllerAdvice 标记的类中添加如下代码:
- 发送请求
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分.
4.4 全局异常处理 - 自定义错误页
在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,我们分别来看。
4.4.1 默认情况
默认情况下,Spring Boot 中的异常页面 是这样的:
之所以用户看到这个页面,是因为开发者没有明确提供一个 /error
路径,如果开发者提供了/error
路径 ,这个页面就不会展示出来,不过在 Spring Boot 中,提供/error
路径实际上是下下策,Spring Boot 本身在处理异常时,也是当所有条件都不满足时,才会去找 /error
路径。那么我们就来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种。
- 一种是静态页面处理
- 另一种是动态页面处理
4.4.2 静态异常界面
自定义静态异常页面,又分为两种
- 第一种:是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html ….
- 另一种:就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。
默认是在
classpath:/static/error/
路径下定义相关页面:截图中在
classpath:/static/error/
路径下定义了404.html
和500.html
。启动项目,如果项目抛出 500 请求错误,就会自动展示 500.html 这个页面,发生 404 就会展示404.html 页面。如果异常展示页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展示 500.html 页面。400同理
4.4.3 动态异常页面
动态的异常页面定义方式和静态的基本一致,动态异常页面,也支持 404.html、4xx.html 或者 500.html、5xx.html,但是一般来说,由于动态异常页面可以直接展示异常详细信息,所以就没有必要挨个枚举错误了 ,直接定义 4xx.html或者5xx.html 即可。 (这里使用thymeleaf模板)
注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带
的异常处理器会自动查找到异常页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>4xx</h1>
<table border="1">
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
</table>
</body>
</html>
如果动态页面和静态页面同时定义了异常处理页面,例如 classpath:/static/error/404.html
和classpath:/templates/error/404.html
同时存在时,默认使用动态页面。即完整的错误页面查找方式应该是这样:
发生了 500 错误 —> 查找动态 500.html 页面 —> 查找静态 500.html —> 查找动态 5xx.html—>查找静态5xx.html
4.4.4 自定义异常数据
自定义 ErrorAttributes 有两种方式 :
- 直接实现 ErrorAttributes 接口
- 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完
成,开发者可以直接使用。
4.5 CORS 解决跨域问题
很多人一提跨域就以为这是前端的事,和后端没关系,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略。
4.5.1 同源策略
同源策略是由 Netscape
提出的一个著名的安全策略,它是浏览器最核心也最基本的安全功能,现在所有支持 JavaScript
的浏览器都会使用这个策略。所谓同源是指协议、域名以及端口要相同。同源策略是基于安全方的考虑提出来的,这个策略本身没问题,但是我们在实际开发中,由于各种原因又经常有跨域的需求,传统的跨域方案是 JSONP,JSONP 虽然能解决跨域但是有一个很大的局限性,那就是只支持 GET 请求,不支持其他类型的求,而今天我们说的 CORS(跨域源资源共享)(CORS,Crossorigin resource sharing)是一个 W3C 标准,它是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是 JSONP 模式的现代版。
在 Spring 框架中,对于 CORS 也提供了相应的解决方案,下面我们就来看看 SpringBoot 中如何实现CORS。
4.5.2 做个Demo仔细研究
首先创建两个普通的 Spring Boot 项目。
- 第一个命名为
provider
提供服务,配置端口为 8080,然后在 provider上提供两个 hello 接口,一个 get,一个 post 如下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "get hello";
}
@PostMapping("/hello")
public String hello2() {
return "post hello";
}
}
- 第二个命名为
consumer
消费服务,二个配置配置为 8081
在 consumer 的 resources/static 目录下创建一个 html 文件,发送一个简单的 ajax 请求,如下:
<div id="app"></div>
<input type="button" onclick="btnClick1()" value="get_button">
<input type="button" onclick="btnClick2()" value="post_button">
<script>
function btnClick() {
$.get('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
function btnClick2() {
$.post('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
</script>
然后分别启动两个项目,发送请求按钮,观察浏览器控制台如下:
- 可以看到,由于同源策略的限制,请求无法发送成功。
- 使用 CORS 可以在前端代码不做任何修改的情况下,实现跨域,那么接下来看看在 provider 中如何配置。首先可以通过 @CrossOrigin 注解配置某一个方法接受某一个域的请求,如下:
这个注解表示这两个接口接受来自 http://localhost:8081 地址的请求,配置完成后,就可以直接访问了,不会在出现跨域问题。
- 此时观察浏览器请求网络控制台,可以看到响应头中多了如下信息:
这个表示服务端愿意接收来自 http://localhost:8081 的请求,拿到这个信息后,浏览器就不会再去限制本次请求的跨域了。 >
Access to XMLHttpRequest at 'http://localhost:8080/hello' from origin
'http://localhost:8081' has been blocked by CORS policy: No 'Access-ControlAllow-Origin' header is present on the requested resource.
@RestController
public class HelloController {
@GetMapping("/hello")
@CrossOrigin(value = "http://localhost:8081")
public String hello() {
return "get hello";
}
@PostMapping("/hello")
@CrossOrigin(value = "http://localhost:8081")
public String hello2() {
return "post hello";
}
}
- 抛弃注解使用全局配置
provider 上,每一个方法上都去加注解未免太麻烦了,有的小伙伴想到可以讲注解直接加在 Controller上,不过每个 Controller 都要加还是麻烦,在 Spring Boot 中,还可以通过全局配置一次性解决这个问题,全局配置只需要在 SpringMVC 的配置类中重写 addCorsMappings 方法即可,如下:
/**
表示本应用的所有方法都会去处理跨域请求,allowedMethods 表示允许通过的请求数,
allowedHeaders 则表示允许的请求头。经过这样的配置之后,就不必在每个方法上单独配置跨域了
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*")
.allowedHeaders("*");
}
}
4.5.3 存在的漏洞
了解了整个 CORS 的工作过程之后,我们通过 Ajax 发送跨域请求,虽然用户体验提高了,但是也有潜在的威胁存在,常见的就是 CSRF(Cross-site request forgery)跨站请求伪造。跨站请求伪造也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录 的 Web 应用程序上执行非本意的操作的攻击方法,举个例子:
基于此,浏览器在实际操作中,会对请求进行分类,分为简单请求,预先请求,带凭证的请求等,预先请求会首先发送一个 options 探测请求,和浏览器进行协商是否接受请求。默认情况下跨域请求是不需要凭证的,但是服务端可以配置要求客户端提供凭证,这样就可以有效避免 csrf 攻击。
4.6 配置类与XML配置
Spring Boot推荐使用Java来完成相关的配置工作。在项目中,不建议将所有的配置放在一个配置类中,可以根据不同的需求提供不同的配置类,例如专门处理Spring Security的配置类、提供Bean的配置类、Spring MVC相关的配置类。这些配置类上都需要添加@Configuration注解。
@ComponentScan注解会扫描所有的Spring组件,也包括@Configuration。@ComponentScan注解在项目入口类的@SpringBootApplication注解中已经提供,因此在实际项目中只需要按需提供相关配置类即可。
Spring Boot中并不推荐使用XML配置,建议尽量用Java配置代替XML配置,如果开发者需要使用XML配置,只需在resources目录下提供配置文件,然后通过@ImportResource加载配置文件即可。例如,有一个Hello类如下:
4.7 SpringBoot自定义SpringMVC配置
使用Spring Boot 只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比较复杂,系统自带的配置不一定满足我们的需求,往往我们还需要结合实际情况自定义配置。
自定义配置就有讲究了,Spring Boot 的不同版本有几个不同写法,很多小伙伴在这里容易搞混,来学习一下。
自定义 SpringMVC 相关的类和注解主要有如下四个:
WebMvcConfigurerAdapter
WebMvcConfigurer
WebMvcConfigurationSupport
@EnableWebMvc
除了第四个是注解,另外三个两个类一个接口,里边的方法看起来好像都类似,但是实际使用效果却大不相同。
4.7.1 WebMvcConfigurerAdapter
WebMvcConfigurerAdapter,这个是在 Spring Boot 1.x 中我们自定义 SpringMVC 时继承的一个抽象类,这个抽象类本身是实现了 WebMvcConfigurer 接口,然后抽象类里边都是空方法,我们来看一下这个类的声明
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
//各种 SpringMVC 配置的方法
}
从 Spring5 开始,由于我们要使用 Java8,而Java8 中的接口允许存在 default 方法,因此官方建议我们直接实现 WebMvcConfigurer 接口,而不是继承 WebMvcConfigurerAdapter 。
也就是说,在 Spring Boot 1.x 的时代,如果我们需要自定义 SpringMVC 配置,直接继承WebMvcConfigurerAdapter 类即可。
4.7.2 WebMvcConfigurer
了解了上面的WebMvcConfigurerAdapter,这个我们应该也明白了, WebMvcConfigurer 是我们在 Spring Boot 2.x 中实现自定义配置的方案。
WebMvcConfigurer 是一个接口,接口中的方法和 WebMvcConfigurerAdapter 中定义的空方法其实一样,所以用法上来说,基本上没有差别,从 Spring Boot 1.x 切换到 Spring Boot 2.x ,只需要把继承类改成实现接口即可。
4.7.3 WebMvcConfigurationSupport
SSM中假如我们放弃 Spring 和 SpringMVC 的 xml 配置文件,而用 Java配置类代替这两个 xml 配置。那么在
这里我自定义 SpringMVC 配置的时候,就是通过继承 WebMvcConfigurationSupport 类来实现的。在WebMvcConfigurationSupport 类中,提供了用 Java 配置 SpringMVC 所需要的所有方法。
WebMvcConfigurationSupport 里面的方法其实和前面两个类中的方法基本是一样的。我们自定义SpringMVC 的配置是可以通过继承 WebMvcConfigurationSupport 来实现的。但是继承WebMvcConfigurationSupport 这种操作我们一般只在 Java 配置的 SSM 项目中使用,Spring Boot 中基本上不会这么写,为什么呢?
因为Spring Boot 中,SpringMVC 相关的自动化配置是在 WebMvcAutoConfiguration 配置类中实现的,那么我们来看看这个配置类的生效条件
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
}
从这个类的注解中可以看到,它的生效条件有一条,就是当不存在WebMvcConfigurationSupport 的实例时,这个自动化配置才会生生效。因此我们在 SpringBoot 中自定义 SpringMVC 配置时选择了继WebMvcConfigurationSupport,就会导致 Spring Boot中 SpringMVC 的自动化配置失效。
Spring Boot 给我们提供了很多自动化配置,很多时候当我们修改这些配置的时候,并不是要全盘否定Spring Boot 提供的自动化配置,我们可能只是针对某一个配置做出修改,其他的配置还是按照Spring Boot 默认的自动化配置来,而继承 WebMvcConfigurationSupport 来实现对 SpringMVC的配置会导致所有的 SpringMVC 自动化配置失效,因此,一般情况下我们不选择这种方案。
4.7.4 @EnableWebMvc
@EnableWebMvc 注解,这个注解很好理解,它的作用就是启用WebMvcConfigurationSupport。下面是源码介绍
/**
* Adding this annotation to an {@code @Configuration} class imports the Spring MVC
* configuration from {@link WebMvcConfigurationSupport}, e.g.:
*/
可以看到,加了这个注解,就会自动导入 WebMvcConfigurationSupport,所以在 Spring Boot 中,我们也不建议使用 @EnableWebMvc 注解,因为它一样会导致 Spring Boot 中的 SpringMVC 自动化配置失效。
4.7.5 小结
- Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现。
- Spring Boot 2.x 中,自定义 SpringMVC 配置可以通过实现 WebMvcConfigurer 接口来完成。
- 如果在 Spring Boot 中使用继承 WebMvcConfigurationSupport 来实现自定义 SpringMVC 配置,或者在 Spring Boot 中使用了 @EnableWebMvc 注解,都会导致 Spring Boot 中默认的SpringMVC 自动化配置失效。
- 在纯 Java 配置的 SSM 环境中,如果我们要自定义 SpringMVC 配置,有两种办法,第一种就是直接继承自 WebMvcConfigurationSupport 来完成 SpringMVC 配置,还有一种方案就是实现WebMvcConfigurer 接口来完成自定义 SpringMVC 配置,如果使用第二种方式,则需要给SpringMVC 的配置类上额外添加 @EnableWebMvc 注解,表示启用WebMvcConfigurationSupport,这样配置才会生效。换句话说,在纯 Java 配置的 SSM 中,如 果你需要自定义 SpringMVC 配置,你离不开 WebMvcConfigurationSupport ,所以在这种情况下建议通过继承 WebMvcConfigurationSupport 来实现自动化配置。
4.9 其他小功能
4.9.1 自定义欢迎页
Spring Boot项目在启动后,首先会去静态资源路径下查找index.html作为首页文件,若查找不到,返回以下页面
如果想使用静态的index.html页面作为项目首页,那么只需在resources/static目录下创建index.html文件即可。若想使用动态页面作为项目首页,则需在resources/templates目录下创建index.html(使用Thymeleaf模板)或者index.ftl(使用FreeMarker模板),然后在Controller中返回逻辑视图名。
最后启动项目,输入“http://localhost:8080/”就可以看到项目首页的内容了。
4.9.2 自定义favicon
favicon.ico是浏览器选项卡左上角的图标,可以放在静态资源路径下或者类路径下,静态资源路径下的favicon.ico优先级高于类路径下的favicon.ico。
最后启动项目,就可以在浏览器选项卡中看到效果了。
SpringBoot整合持久层框架
这里整合的持久层框架是MyBatis,MyBatis是我最常用的一个持久层框架,简单易学,并且功能强大。在 Spring+SpringMVC 中整合 MyBatis 步骤还是有点复杂的,要配置多个 Bean,Spring Boot 中对此做了进一步的简化,使 MyBatis 基本上可以做到开箱即用。
5.1 整合MyBatis
5.1.1 创建工程
首先创建一个基本的 Spring Boot 工程,添加 Web 依赖,MyBatis 依赖以及 MySQL 驱动依赖,如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
</dependencies>
MyBatis 和 Druid 依赖的命名和其他库的命名不太一样,是属于
xxx-spring-boot-stater
模式的,这表示该 starter 是由第三方提供的。
多数据源整合