导读:课程概览
1. Spring Boot是什么
我们知道,从 2002 年开始,Spring 一直在飞速的发展,如今已经成为了在Java EE(Java Enterprise Edition)开发中真正意义上的标准,但是随着技术的发展,Java EE使用 Spring 逐渐变得笨重起来,大量的 XML 文件存在于项目之中。繁琐的配置,整合第三方框架的配置问题,导致了开发和部署效率的降低。
2012 年 10 月,Mike Youngstrom 在 Spring jira 中创建了一个功能请求,要求在 Spring 框架中支持无容器 Web 应用程序体系结构。他谈到了在主容器引导 spring 容器内配置 Web 容器服务。这是 jira 请求的摘录:
我认为 Spring 的 Web 应用体系结构可以大大简化,如果它提供了从上到下利用 Spring 组件和配置模型的工具和参考体系结构。在简单的 main()方法引导的 Spring 容器内嵌入和统一这些常用Web 容器服务的配置。
这一要求促使了 2013 年初开始的 Spring Boot 项目的研发,到今天,Spring Boot 的版本已经到了 2.0.3 RELEASE。Spring Boot 并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。
它集成了大量常用的第三方库配置,Spring Boot应用中这些第三方库几乎可以是零配置的开箱即用(out-of-the-box),大部分的 Spring Boot 应用都只需要非常少量的配置代码(基于 Java 的配置),开发者能够更加专注于业务逻辑。
2. 为什么学习Spring Boot
2.1 从Spring官方来看
我们打开 Spring 的官方网站,可以看到下图:
我们可以看到图中官方对 Spring Boot 的定位:Build Anything, Build任何东西。Spring Boot旨在尽可能快地启动和运行,并且只需最少的 Spring 前期配置。 同时我们也来看一下官方对后面两个的定位:
SpringCloud:Coordinate Anything,协调任何事情;SpringCloud Data Flow:Connect everything,连接任何东西。
仔细品味一下,Spring 官网对 Spring Boot、SpringCloud 和 SpringCloud Data Flow三者定位的措辞非常有味道,同时也可以看出,Spring 官方对这三个技术非常重视,是现在以及今后学习的重点(SpringCloud 相关达人课课程届时也会上线)。
2.2 从Spring Boot的优点来看
Spring Boot 有哪些优点?主要给我们解决了哪些问题呢?我们以下图来说明:
2.2.1 良好的基因
Spring Boot 是伴随着 Spring 4.0 诞生的,从字面理解,Boot是引导的意思,因此 Spring Boot 旨在帮助开发者快速搭建 Spring 框架。Spring Boot 继承了原有 Spring 框架的优秀基因,使 Spring 在使用中更加方便快捷。
2.2.2 简化编码
举个例子,比如我们要创建一个 web 项目,使用 Spring 的朋友都知道,在使用 Spring 的时候,需要在 pom 文件中添加多个依赖,而 Spring Boot 则会帮助开发着快速启动一个 web 容器,在 Spring Boot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我们点击进入该依赖后可以看到,Spring Boot 这个 starter-web 已经包含了多个依赖,包括之前在 Spring 工程中需要导入的依赖,我们看一下其中的一部分,如下:
<!-- .....省略其他依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.7.RELEASE</version>
<scope>compile</scope>
</dependency>
由此可以看出,Spring Boot 大大简化了我们的编码,我们不用一个个导入依赖,直接一个依赖即可。
2.2.3 简化配置
Spring 虽然使Java EE轻量级框架,但由于其繁琐的配置,一度被人认为是“配置地狱”。各种XML、Annotation配置会让人眼花缭乱,而且配置多的话,如果出错了也很难找出原因。Spring Boot更多的是采用 Java Config 的方式,对 Spring 进行配置。举个例子:
我新建一个类,但是我不用 @Service注解,也就是说,它是个普通的类,那么我们如何使它也成为一个 Bean 让 Spring 去管理呢?只需要@Configuration 和@Bean两个注解即可,如下:
public class TestService {
public String sayHello () {
return "Hello Spring Boot!";
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JavaConfig {
@Bean
public TestService getTestService() {
return new TestService();
}
}
@Configuration表示该类是个配置类,@Bean表示该方法返回一个 Bean。这样就把TestService作为 Bean 让 Spring 去管理了,在其他地方,我们如果需要使用该 Bean,和原来一样,直接使用@Resource注解注入进来即可使用,非常方便。
@Resource
private TestService testService;
另外,部署配置方面,原来 Spring 有多个 xml 和 properties配置,在 Spring Boot 中只需要个 application.yml即可。
2.2.4 简化部署
在使用 Spring 时,项目部署时需要我们在服务器上部署 tomcat,然后把项目打成 war 包扔到 tomcat里,在使用 Spring Boot 后,我们不需要在服务器上去部署 tomcat,因为 Spring Boot 内嵌了 tomcat,我们只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目。
另外,也降低对运行环境的基本要求,环境变量中有JDK即可。
2.2.5 简化监控
我们可以引入 spring-boot-start-actuator 依赖,直接使用 REST 方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。但是 Spring Boot 只是个微框架,没有提供相应的服务发现与注册的配套功能,没有外围监控集成方案,没有外围安全管理方案,所以在微服务架构中,还需要 Spring Cloud 来配合一起使用。
2.3 从未来发展的趋势来看
微服务是未来发展的趋势,项目会从传统架构慢慢转向微服务架构,因为微服务可以使不同的团队专注于更小范围的工作职责、使用独立的技术、更安全更频繁地部署。而 继承了 Spring 的优良特性,与 Spring 一脉相承,而且 支持各种REST API 的实现方式。Spring Boot 也是官方大力推荐的技术,可以看出,Spring Boot 是未来发展的一个大趋势。
3. 本课程能学到什么
本课程使用目前 Spring Boot 最新版本2.0.3 RELEASE,课程文章均为作者在实际项目中剥离出来的场景和demo,目标是带领学习者快速上手 Spring Boot,将 Spring Boot 相关技术点快速运用在微服务项目中。全篇分为两部分:基础篇和进阶篇。
基础篇(01—10课)主要介绍 Spring Boot 在项目中最常使用的一些功能点,旨在带领学习者快速掌握 Spring Boot 在开发时需要的知识点,能够把 Spring Boot 相关技术运用到实际项目架构中去。该部分以 Spring Boot 框架为主线,内容包括Json数据封装、日志记录、属性配置、MVC支持、在线文档、模板引擎、异常处理、AOP 处理、持久层集成等等。
进阶篇(11—17课)主要是介绍 Spring Boot 在项目中拔高一些的技术点,包括集成的一些组件,旨在带领学习者在项目中遇到具体的场景时能够快速集成,完成对应的功能。该部分以 Spring Boot 框架为主线,内容包括拦截器、监听器、缓存、安全认证、分词插件、消息队列等等。
认真读完该系列文章之后,学习者会快速了解并掌握 Spring Boot 在项目中最常用的技术点,作者课程的最后,会基于课程内容搭建一个 Spring Boot 项目的空架构,该架构也是从实际项目中剥离出来,学习者可以运用该架构于实际项目中,具备使用 Spring Boot 进行实际项目开发的能力。
课程所有源码提供免费下载:下载地址。
4. 适合阅读的人群
本课程适合以下人群阅读:
- 有一定的Java语言基础,了解Spring、Maven的在校学生或自学者
- 有传统项目经验,想往微服务方向发展的工作人员
- 热衷于新技术并对 Spring Boot 感兴趣的人员
- 希望了解 Spring Boot 2.0.3 的研究人员
5. 本课程开发环境和插件
本课程的开发环境:
- 开发工具:IDEA 2017
- JDK版本: JDK 1.8
- Spring Boot版本:2.0.3 RELEASE
- Maven版本:3.5.2
涉及到的插件:
- FastJson
- Swagger2
- Thymeleaf
- MyBatis
- Redis
- ActiveMQ
- Shiro
- Lucence
6. 课程目录
- 导读:课程概览
- 第01课:Spring Boot开发环境搭建和项目启动
- 第02课:Spring Boot返回Json数据及数据封装
- 第03课:Spring Boot使用slf4j进行日志记录
- 第04课:Spring Boot中的项目属性配置
- 第05课:Spring Boot中的MVC支持
- 第06课:Spring Boot集成Swagger2展现在线接口文档
- 第07课:Spring Boot集成Thymeleaf模板引擎
- 第08课:Spring Boot中的全局异常处理
- 第09课:Spring Boot中的切面AOP处理
- 第10课:Spring Boot中集成MyBatis
- 第11课:Spring Boot事务配置管理
- 第12课:Spring Boot中使用监听器
- 第13课:Spring Boot中使用拦截器
- 第14课:Spring Boot中集成Redis
- 第15课:Spring Boot中集成ActiveMQ
- 第16课:Spring Boot中集成Shiro
- 第17课:Spring Boot中结成Lucence
- 第18课:Spring Boot搭建实际项目开发中的架构
第01课:Spring Boot开发环境搭建和项目启动
上一节对 SpringBoot 的特性做了一个介绍,本节主要对 jdk 的配置、Spring Boot工程的构建和项目的启动、Spring Boot 项目工程的结构做一下讲解和分析。
1. jdk 的配置
本课程是使用 IDEA 进行开发,在IDEA 中配置 jdk 的方式很简单,打开File->Project Structure,如下图所:
- 选择 SDKs
- 在 JDK home path 中选择本地 jdk 的安装目录
- 在 Name 中为 jdk 自定义名字
通过以上三步骤,即可导入本地安装的 jdk。如果是使用 STS 或者 eclipse 的朋友,可以通过两步骤添加:
window->preference->java->Instralled JRES来添加本地 jdk。window-->preference-->java-->Compiler选择 jre,和 jdk 保持一致。
2. Spring Boot 工程的构建
2.1 IDEA 快速构建
IDEA 中可以通过File->New->Project来快速构建 Spring Boot 工程。如下,选择 Spring Initializr,在 Project SDK 中选择刚刚我们导入的 jdk,点击 Next,到了项目的配置信息。
- Group:填企业域名,本课程使用com.itcodai
- Artifact:填项目名称,本课程中每一课的工程名以
course+课号命令,这里使用 course01 - Dependencies:可以添加我们项目中所需要的依赖信息,根据实际情况来添加,本课程只需要选择 Web 即可。
2.2 官方构建
第二种方式可以通过官方构建,步骤如下:
- 访问 http://start.spring.io/。
- 在页面上输入相应的 Spring Boot 版本、Group 和 Artifact 信息以及项目依赖,然后创建项目。
- 解压后,使用 IDEA 导入该 maven 工程:
File->New->Model from Existing Source,然后选择解压后的项目文件夹即可。如果是使用 eclipse 的朋友,可以通过Import->Existing Maven Projects->Next,然后选择解压后的项目文件夹即可。
2.3 maven配置
创建了 Spring Boot 项目之后,需要进行 maven 配置。打开File->settings,搜索 maven,配置一下本地的 maven 信息。如下:
在 Maven home directory 中选择本地 Maven 的安装路径;在 User settings file 中选择本地 Maven 的配置文件所在路径。在配置文件中,我们配置一下国内阿里的镜像,这样在下载 maven 依赖时,速度很快。
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
如果是使用 eclipse 的朋友,可以通过window-->preference-->Maven-->User Settings来配置,配置方式和上面一致。
2.4 编码配置
同样地,新建项目后,我们一般都需要配置编码,这点非常重要,很多初学者都会忘记这一步,所以要养成良好的习惯。
IDEA 中,仍然是打开File->settings,搜索 encoding,配置一下本地的编码信息。如下:
如果是使用 eclipse 的朋友,有两个地方需要设置一下编码:
- window--> perferences-->General-->Workspace,将Text file encoding改成utf-8
- window-->perferences-->General-->content types,选中Text,将Default encoding填入utf-8
OK,编码设置完成即可启动项目工程了。
3. Spring Boot 项目工程结构
Spring Boot 项目总共有三个模块,如下图所示:
- src/main/java路径:主要编写业务程序
- src/main/resources路径:存放静态文件和配置文件
- src/test/java路径:主要编写测试程序
默认情况下,如上图所示会创建一个启动类 Course01Application,该类上面有个@SpringBootApplication注解,该启动类中有个 main 方法,没错,Spring Boot 启动只要运行该 main 方法即可,非常方便。另外,Spring Boot 内部集成了 tomcat,不需要我们人为手动去配置 tomcat,开发者只需要关注具体的业务逻辑即可。
到此为止,Spring Boot 就启动成功了,为了比较清楚的看到效果,我们写一个 Controller 来测试一下,如下:
package com.itcodai.course01.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/start")
public class StartController {
@RequestMapping("/springboot")
public String startSpringBoot() {
return "Welcome to the world of Spring Boot!";
}
}
重新运行 main 方法启动项目,在浏览器中输入 localhost:8080/start/springboot,如果看到 “Welcome to the world of Spring Boot!”,那么恭喜你项目启动成功!Spring Boot 就是这么简单方便!端口号默认是8080,如果想要修改,可以在 application.yml 文件中使用 server.port 来人为指定端口,如8001端口:
server:
port: 8001
4. 总结
本节我们快速学习了如何在 IDEA 中导入 jdk,以及使用 IDEA 如何配置 maven 和编码,如何快速的创建和启动 Spring Boot 工程。IDEA 对 Spring Boot 的支持非常友好,建议大家使用 IDEA 进行 Spring Boot 的开发,从下一课开始,我们真正进入 Spring Boot 的学习中。课程源代码下载地址:戳我下载
第02课:Spring Boot返回Json数据及数据封装
在项目开发中,接口与接口之间,前后端之间数据的传输都使用 Json 格式,在 Spring Boot 中,接口返回 Json 格式的数据很简单,在 Controller 中使用@RestController注解即可返回 Json 格式的数据,@RestController也是 Spring Boot 新增的一个注解,我们点进去看一下该注解都包含了哪些东西。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}
可以看出, @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注解,使用过 Spring 的朋友对 @Controller 注解已经非常了解了,这里不再赘述, @ResponseBody 注解是将返回的数据结构转换为 Json 格式。所以在默认情况下,使用了 @RestController 注解即可将返回的数据结构转换成 Json 格式,Spring Boot 中默认使用的 Json 解析技术框架是 jackson。我们点开 pom.xml 中的 spring-boot-starter-web 依赖,可以看到一个 spring-boot-starter-json 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
Spring Boot 中对依赖都做了很好的封装,可以看到很多 spring-boot-starter-xxx 系列的依赖,这是 Spring Boot 的特点之一,不需要人为去引入很多相关的依赖了,starter-xxx 系列直接都包含了所必要的依赖,所以我们再次点进去上面这个 spring-boot-starter-json 依赖,可以看到:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.9.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.9.6</version>
<scope>compile</scope>
</dependency>
到此为止,我们知道了 Spring Boot 中默认使用的 json 解析框架是 jackson。下面我们看一下默认的 jackson 框架对常用数据类型的转 Json 处理。
1. Spring Boot 默认对Json的处理
在实际项目中,常用的数据结构无非有类对象、List对象、Map对象,我们看一下默认的 jackson 框架对这三个常用的数据结构转成 json 后的格式如何。
1.1 创建 User 实体类
为了测试,我们需要创建一个实体类,这里我们就用 User 来演示。
public class User {
private Long id;
private String username;
private String password;
/* 省略get、set和带参构造方法 */
}
1.2 创建Controller类
然后我们创建一个 Controller,分别返回 User对象、List<User> 和 Map<String, Object>。
import com.itcodai.course02.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/json")
public class JsonController {
@RequestMapping("/user")
public User getUser() {
return new User(1, "倪升武", "123456");
}
@RequestMapping("/list")
public List<User> getUserList() {
List<User> userList = new ArrayList<>();
User user1 = new User(1, "倪升武", "123456");
User user2 = new User(2, "达人课", "123456");
userList.add(user1);
userList.add(user2);
return userList;
}
@RequestMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>(3);
User user = new User(1, "倪升武", "123456");
map.put("作者信息", user);
map.put("博客地址", "http://blog.itcodai.com");
map.put("CSDN地址", "http://blog.csdn.net/eson_15");
map.put("粉丝数量", 4153);
return map;
}
}
1.3 测试不同数据类型返回的json
OK,写好了接口,分别返回了一个 User 对象、一个 List 集合和一个 Map 集合,其中 Map 集合中的 value 存的是不同的数据类型。接下来我们依次来测试一下效果。
在浏览器中输入:localhost:8080/json/user 返回 json 如下:
{"id":1,"username":"倪升武","password":"123456"}
在浏览器中输入:localhost:8080/json/list 返回 json 如下:
[{"id":1,"username":"倪升武","password":"123456"},{"id":2,"username":"达人课","password":"123456"}]
在浏览器中输入:localhost:8080/json/map 返回 json 如下:
{"作者信息":{"id":1,"username":"倪升武","password":"123456"},"CSDN地址":"http://blog.csdn.net/eson_15","粉丝数量":4153,"博客地址":"http://blog.itcodai.com"}
可以看出,map 中不管是什么数据类型,都可以转成相应的 json 格式,这样就非常方便。
1.4 jackson 中对null的处理
在实际项目中,我们难免会遇到一些 null 值出现,我们转 json 时,是不希望有这些 null 出现的,比如我们期望所有的 null 在转 json 时都变成 "" 这种空字符串,那怎么做呢?在 Spring Boot 中,我们做一下配置即可,新建一个 jackson 的配置类:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
});
return objectMapper;
}
}
然后我们修改一下上面返回 map 的接口,将几个值改成 null 测试一下:
@RequestMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>(3);
User user = new User(1, "倪升武", null);
map.put("作者信息", user);
map.put("博客地址", "http://blog.itcodai.com");
map.put("CSDN地址", null);
map.put("粉丝数量", 4153);
return map;
}
重启项目,再次输入:localhost:8080/json/map,可以看到 jackson 已经将所有 null 字段转成了空字符串了。
{"作者信息":{"id":1,"username":"倪升武","password":""},"CSDN地址":"","粉丝数量":4153,"博客地址":"http://blog.itcodai.com"}
2. 使用阿里巴巴FastJson的设置
2.1 jackson 和 fastJson 的对比
有很多朋友习惯于使用阿里巴巴的 fastJson 来做项目中 json 转换的相关工作,目前我们项目中使用的就是阿里的 fastJson,那么 jackson 和 fastJson 有哪些区别呢?根据网上公开的资料比较得到下表。
选项 |
fastJson |
jackson |
上手难易程度 |
容易 |
中等 |
高级特性支持 |
中等 |
丰富 |
官方文档、Example支持 |
中文 |
英文 |
处理json速度 |
略快 |
快 |
关于 fastJson 和 jackson 的对比,网上有很多资料可以查看,主要是根据自己实际项目情况来选择合适的框架。从扩展上来看,fastJson 没有 jackson 灵活,从速度或者上手难度来看,fastJson 可以考虑,我们项目中目前使用的是阿里的 fastJson,挺方便的。
2.2 fastJson依赖导入
使用 fastJson 需要导入依赖,本课程使用 1.2.35 版本,依赖如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
2.2 使用 fastJson 处理 null
使用 fastJson 时,对 null 的处理和 jackson 有些不同,需要继承 WebMvcConfigurationSupport 类,然后覆盖 configureMessageConverters 方法,在方法中,我们可以选择对要实现 null 转换的场景,配置好即可。如下:
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class fastJsonConfig extends WebMvcConfigurationSupport {
/**
* 使用阿里 FastJson 作为JSON MessageConverter
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
// 保留map空的字段
SerializerFeature.WriteMapNullValue,
// 将String类型的null转成""
SerializerFeature.WriteNullStringAsEmpty,
// 将Number类型的null转成0
SerializerFeature.WriteNullNumberAsZero,
// 将List类型的null转成[]
SerializerFeature.WriteNullListAsEmpty,
// 将Boolean类型的null转成false
SerializerFeature.WriteNullBooleanAsFalse,
// 避免循环引用
SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
converter.setDefaultCharset(Charset.forName("UTF-8"));
List<MediaType> mediaTypeList = new ArrayList<>();
// 解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json"
mediaTypeList.add(MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(mediaTypeList);
converters.add(converter);
}
}
3. 封装统一返回的数据结构
以上是 Spring Boot 返回 json 的几个代表的例子,但是在实际项目中,除了要封装数据之外,我们往往需要在返回的 json 中添加一些其他信息,比如返回一些状态码 code ,返回一些 msg 给调用者,这样调用者可以根据 code 或者 msg 做一些逻辑判断。所以在实际项目中,我们需要封装一个统一的 json 返回结构存储返回信息。
3.1 定义统一的 json 结构
由于封装的 json 数据的类型不确定,所以在定义统一的 json 结构时,我们需要用到泛型。统一的 json 结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实际业务需求做相应的添加即可,一般来说,应该有默认的返回结构,也应该有用户指定的返回结构。如下:
public class JsonResult<T> {
private T data;
private String code;
private String msg;
/**
* 若没有数据返回,默认状态码为0,提示信息为:操作成功!
*/
public JsonResult() {
this.code = "0";
this.msg = "操作成功!";
}
/**
* 若没有数据返回,可以人为指定状态码和提示信息
* @param code
* @param msg
*/
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 有数据返回时,状态码为0,默认提示信息为:操作成功!
* @param data
*/
public JsonResult(T data) {
this.data = data;
this.code = "0";
this.msg = "操作成功!";
}
/**
* 有数据返回,状态码为0,人为指定提示信息
* @param data
* @param msg
*/
public JsonResult(T data, String msg) {
this.data = data;
this.code = "0";
this.msg = msg;
}
// 省略get和set方法
}
3.2 修改 Controller 中的返回值类型及测试
由于 JsonResult 使用了泛型,所以所有的返回值类型都可以使用该统一结构,在具体的场景将泛型替换成具体的数据类型即可,非常方便,也便于维护。在实际项目中,还可以继续封装,比如状态码和提示信息可以定义一个枚举类型,以后我们只需要维护这个枚举类型中的数据即可(在本课程中就不展开了)。根据以上的 JsonResult,我们改写一下 Controller,如下:
@RestController
@RequestMapping("/jsonresult")
public class JsonResultController {
@RequestMapping("/user")
public JsonResult<User> getUser() {
User user = new User(1, "倪升武", "123456");
return new JsonResult<>(user);
}
@RequestMapping("/list")
public JsonResult<List> getUserList() {
List<User> userList = new ArrayList<>();
User user1 = new User(1, "倪升武", "123456");
User user2 = new User(2, "达人课", "123456");
userList.add(user1);
userList.add(user2);
return new JsonResult<>(userList, "获取用户列表成功");
}
@RequestMapping("/map")
public JsonResult<Map> getMap() {
Map<String, Object> map = new HashMap<>(3);
User user = new User(1, "倪升武", null);
map.put("作者信息", user);
map.put("博客地址", "http://blog.itcodai.com");
map.put("CSDN地址", null);
map.put("粉丝数量", 4153);
return new JsonResult<>(map);
}
}
我们重新在浏览器中输入:localhost:8080/jsonresult/user 返回 json 如下:
{"code":"0","data":{"id":1,"password":"123456","username":"倪升武"},"msg":"操作成功!"}
输入:localhost:8080/jsonresult/list,返回 json 如下:
{"code":"0","data":[{"id":1,"password":"123456","username":"倪升武"},{"id":2,"password":"123456","username":"达人课"}],"msg":"获取用户列表成功"}
输入:localhost:8080/jsonresult/map,返回 json 如下:
{"code":"0","data":{"作者信息":{"id":1,"password":"","username":"倪升武"},"CSDN地址":null,"粉丝数量":4153,"博客地址":"http://blog.itcodai.com"},"msg":"操作成功!"}
通过封装,我们不但将数据通过 json 传给前端或者其他接口,还带上了状态码和提示信息,这在实际项目场景中应用非常广泛。
4. 总结
本节主要对 Spring Boot 中 json 数据的返回做了详细的分析,从 Spring Boot 默认的 jackson 框架到阿里巴巴的 fastJson 框架,分别对它们的配置做了相应的讲解。另外,结合实际项目情况,总结了实际项目中使用的 json 封装结构体,加入了状态码和提示信息,使得返回的 json 数据信息更加完整。课程源代码下载地址:戳我下载
第03课:Spring Boot使用slf4j进行日志记录
在开发中,我们经常使用 System.out.println() 来打印一些信息,但是这样不好,因为大量的使用 System.out 会增加资源的消耗。我们实际项目中使用的是 slf4j 的 logback 来输出日志,效率挺高的,Spring Boot 提供了一套日志系统,logback 是最优的选择。
1. slf4j 介绍
引用百度百科里的一段话:
SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。
这段的大概意思是:你只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。例如,在项目中使用了 slf4j 记录日志,并且绑定了 log4j(即导入相应的依赖),则日志会以 log4j 的风格输出;后期需要改为以 logback 的风格输出日志,只需要将 log4j 替换成 logback 即可,不用修改项目中的代码。这对于第三方组件的引入的不同日志系统来说几乎零学习成本,况且它的优点不仅仅这一个而已,还有简洁的占位符的使用和日志级别的判断。
正因为 sfl4j 有如此多的优点,阿里巴巴已经将 slf4j 作为他们的日志框架了。在《阿里巴巴Java开发手册(正式版)》中,日志规约一项第一条就强制要求使用 slf4j:
1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
“强制”两个字体现出了 slf4j 的优势,所以建议在实际项目中,使用 slf4j 作为自己的日志框架。使用 slf4j 记录日志非常简单,直接使用 LoggerFactory 创建即可。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
// ……
}
2. application.yml 中对日志的配置
Spring Boot 对 slf4j 支持的很好,内部已经集成了 slf4j,一般我们在使用的时候,会对slf4j 做一下配置。application.yml 文件是 Spring Boot 中唯一一个需要配置的文件,一开始创建工程的时候是 application.properties 文件,个人比较细化用 yml 文件,因为 yml 文件的层次感特别好,看起来更直观,但是 yml 文件对格式要求比较高,比如英文冒号后面必须要有个空格,否则项目估计无法启动,而且也不报错。用 properties 还是 yml 视个人习惯而定,都可以。本课程使用 yml。
我们看一下 application.yml 文件中对日志的配置:
logging:
config: logback.xml
level:
com.itcodai.course03.dao: trace
logging.config 是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml 文件,关于日志的相关配置信息,都放在 logback.xml 文件中了。logging.level 是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.itcodai.course03.dao 包下的所有 mapper 日志输出级别为 trace,会将操作数据库的 sql 打印出来,开发时设置成 trace 方便定位问题,在生产环境上,将这个日志级别再设置成 error 级别即可(本节课不讨论 mapper 层,在后面 Spring Boot 集成 MyBatis 时再详细讨论)。
常用的日志级别按照从高到低依次为:ERROR、WARN、INFO、DEBUG。
3. logback.xml 配置文件解析
在上面 application.yml 文件中,我们指定了日志配置文件 logback.xml,logback.xml 文件中主要用来做日志的相关配置。在 logback.xml 中,我们可以定义日志输出的格式、路径、控制台输出格式、文件大小、保存时长等等。下面来分析一下:
3.1 定义日志输出格式和存储路径
<configuration>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="D:/logs/course03/demo.%d{yyyy-MM-dd}.%i.log" />
</configuration>
我们来看一下这个定义的含义:首先定义一个格式,命名为 “LOG_PATTERN”,该格式中 %date 表示日期,%thread 表示线程名,%-5level 表示级别从左显示5个字符宽度,%logger{36} 表示 logger 名字最长36个字符,%msg 表示日志消息,%n 是换行符。
然后再定义一下名为 “FILE_PATH” 文件路径,日志都会存储在该路径下。%i 表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置,下面会讲解。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。
3.2 定义控制台输出
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 按照上面配置的LOG_PATTERN来打印日志 -->
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
</configuration>
使用 <appender> 节点设置个控制台输出(class="ch.qos.logback.core.ConsoleAppender")的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${} 引用进来即可。
3.3 定义日志文件的相关参数
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按照上面配置的FILE_PATH路径来保存日志 -->
<fileNamePattern>${FILE_PATH}</fileNamePattern>
<!-- 日志保存15天 -->
<maxHistory>15</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 单个日志文件的最大,超过则新建日志文件存储 -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- 按照上面配置的LOG_PATTERN来打印日志 -->
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
</configuration>
使用 <appender> 定义一个名为 “FILE” 的文件配置,主要是配置日志文件保存的时间、单个日志文件存储的大小、以及文件保存的路径和日志的输出格式。
3.4 定义日志输出级别
<configuration>
<logger name="com.itcodai.course03" level="INFO" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
有了上面那些定义后,最后我们使用 <logger> 来定义一下项目中默认的日志输出级别,这里定义级别为 INFO,然后针对 INFO 级别的日志,使用 <root> 引用上面定义好的控制台日志输出和日志文件的参数。这样 logback.xml 文件中的配置就设置完了。
4. 使用Logger在项目中打印日志
在代码中,我们一般使用 Logger 对象来打印出一些 log 信息,可以指定打印出的日志级别,也支持占位符,很方便。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
private final static Logger logger = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/log")
public String testLog() {
logger.debug("=====测试日志debug级别打印====");
logger.info("======测试日志info级别打印=====");
logger.error("=====测试日志error级别打印====");
logger.warn("======测试日志warn级别打印=====");
// 可以使用占位符打印出一些参数信息
String str1 = "blog.itcodai.com";
String str2 = "blog.csdn.net/eson_15";
logger.info("======倪升武的个人博客:{};倪升武的CSDN博客:{}", str1, str2);
return "success";
}
}
启动该项目,在浏览器中输入 localhost:8080/test/log 后可以看到控制台的日志记录:
======测试日志info级别打印==========测试日志error级别打印==========测试日志warn级别打印===========倪升武的个人博客:blog.itcodai.com;倪升武的CSDN博客:blog.csdn.net/eson_15
因为 INFO 级别比 DEBUG 级别高,所以 debug 这条没有打印出来,如果将 logback.xml 中的日志级别设置成 DEBUG,那么四条语句都会打印出来,这个大家自己去测试了。同时可以打开 D:\logs\course03\ 目录,里面有刚刚项目启动,以后后面生成的所有日志记录。在项目部署后,我们大部分都是通过查看日志文件来定位问题。
5. 总结
本节课主要对 slf4j 做了一个简单的介绍,并且对 Spring Boot 中如何使用 slf4j 输出日志做了详细的说明,着重分析了 logback.xml 文件中对日志相关信息的配置,包括日志的不同级别。最后针对这些配置,在代码中使用 Logger 打印出一些进行测试。在实际项目中,这些日志都是排查问题的过程中非常重要的资料。课程源代码下载地址:戳我下载
第04课:Spring Boot中的项目属性配置
我们知道,在项目中,很多时候需要用到一些配置的信息,这些信息可能在测试环境和生产环境下会有不同的配置,后面根据实际业务情况有可能还会做修改,针对这种情况,我们不能将这些配置在代码中写死,最好就是写到配置文件中。比如可以把这些信息写到 application.yml 文件中。
1. 少量配置信息的情形
举个例子,在微服务架构中,最常见的就是某个服务需要调用其他服务来获取其提供的相关信息,那么在该服务的配置文件中需要配置被调用的服务地址,比如在当前服务里,我们需要调用订单微服务获取订单相关的信息,假设 订单服务的端口号是 8002,那我们可以做如下配置:
server:
port: 8001
# 配置微服务的地址
url:
# 订单微服务的地址
orderUrl: http://localhost:8002
然后在业务代码中如何获取到这个配置的订单服务地址呢?我们可以使用 @Value 注解来解决。在对应的类中加上一个属性,在属性上使用 @Value 注解即可获取到配置文件中的配置信息,如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class ConfigController {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigController.class);
@Value("${url.orderUrl}")
private String orderUrl;
@RequestMapping("/config")
public String testConfig() {
LOGGER.info("=====获取的订单服务地址为:{}", orderUrl);
return "success";
}
}
@Value 注解上通过 ${key} 即可获取配置文件中和 key 对应的 value 值。我们启动一下项目,在浏览器中输入 localhost:8080/test/config 请求服务后,可以看到控制台会打印出订单服务的地址:
=====获取的订单服务地址为:http://localhost:8002
说明我们成功获取到了配置文件中的订单微服务地址,在实际项目中也是这么用的,后面如果因为服务器部署的原因,需要修改某个服务的地址,那么只要在配置文件中修改即可。
2. 多个配置信息的情形
这里再引申一个问题,随着业务复杂度的增加,一个项目中可能会有越来越多的微服务,某个模块可能需要调用多个微服务获取不同的信息,那么就需要在配置文件中配置多个微服务的地址。可是,在需要调用这些微服务的代码中,如果这样一个个去使用 @Value 注解引入相应的微服务地址的话,太过于繁琐,也不科学。
所以,在实际项目中,业务繁琐,逻辑复杂的情况下,需要考虑封装一个或多个配置类。举个例子:假如在当前服务中,某个业务需要同时调用订单微服务、用户微服务和购物车微服务,分别获取订单、用户和购物车相关信息,然后对这些信息做一定的逻辑处理。那么在配置文件中,我们需要将这些微服务的地址都配置好:
# 配置多个微服务的地址
url:
# 订单微服务的地址
orderUrl: http://localhost:8002
# 用户微服务的地址
userUrl: http://localhost:8003
# 购物车微服务的地址
shoppingUrl: http://localhost:8004
也许实际业务中,远远不止这三个微服务,甚至十几个都有可能。对于这种情况,我们可以先定义一个 MicroServiceUrl 类来专门保存微服务的 url,如下:
@Component
@ConfigurationProperties(prefix = "url")
public class MicroServiceUrl {
private String orderUrl;
private String userUrl;
private String shoppingUrl;
// 省去get和set方法
}
细心的朋友应该可以看到,使用 @ConfigurationProperties 注解并且使用 prefix 来指定一个前缀,然后该类中的属性名就是配置中去掉前缀后的名字,一一对应即可。即:前缀名 + 属性名就是配置文件中定义的 key。同时,该类上面需要加上 @Component 注解,把该类作为组件放到Spring容器中,让 Spring 去管理,我们使用的时候直接注入即可。
需要注意的是,使用 @ConfigurationProperties 注解需要导入它的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
OK,到此为止,我们将配置写好了,接下来写个 Controller 来测试一下。此时,不需要在代码中一个个引入这些微服务的 url 了,直接通过 @Resource 注解将刚刚写好配置类注入进来即可使用了,非常方便。如下:
@RestController
@RequestMapping("/test")
public class TestController {
private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
@Resource
private MicroServiceUrl microServiceUrl;
@RequestMapping("/config")
public String testConfig() {
LOGGER.info("=====获取的订单服务地址为:{}", microServiceUrl.getOrderUrl());
LOGGER.info("=====获取的用户服务地址为:{}", microServiceUrl.getUserUrl());
LOGGER.info("=====获取的购物车服务地址为:{}", microServiceUrl.getShoppingUrl());
return "success";
}
}
再次启动项目,请求一下可以看到,控制台打印出如下信息,说明配置文件生效,同时正确获取配置文件内容:
=====获取的订单服务地址为:http://localhost:8002
=====获取的订单服务地址为:http://localhost:8002
=====获取的用户服务地址为:http://localhost:8003
=====获取的购物车服务地址为:http://localhost:8004
3. 指定项目配置文件
我们知道,在实际项目中,一般有两个环境:开发环境和生产环境。开发环境中的配置和生产环境中的配置往往不同,比如:环境、端口、数据库、相关地址等等。我们不可能在开发环境调试好之后,部署到生产环境后,又要将配置信息全部修改成生产环境上的配置,这样太麻烦,也不科学。
最好的解决方法就是开发环境和生产环境都有一套对用的配置信息,然后当我们在开发时,指定读取开发环境的配置,当我们将项目部署到服务器上之后,再指定去读取生产环境的配置。
我们新建两个配置文件: application-dev.yml 和 application-pro.yml,分别用来对开发环境和生产环境进行相关配置。这里为了方便,我们分别设置两个访问端口号,开发环境用 8001,生产环境用 8002.
# 开发环境配置文件
server:
port: 8001
# 开发环境配置文件
server:
port: 8002
然后在 application.yml 文件中指定读取哪个配置文件即可。比如我们在开发环境下,指定读取 applicationn-dev.yml 文件,如下:
spring:
profiles:
active:
- dev
这样就可以在开发的时候,指定读取 application-dev.yml 文件,访问的时候使用 8001 端口,部署到服务器后,只需要将 application.yml 中指定的文件改成 application-pro.yml 即可,然后使用 8002 端口访问,非常方便。
4. 总结
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。课程源代码下载地址:戳我下载
第05课:Spring Boot中的MVC支持
Spring Boot 的 MVC 支持主要来介绍实际项目中最常用的几个注解,包括 @RestController、 @RequestMapping、@PathVariable、@RequestParam 以及 @RequestBody。主要介绍这几个注解常用的使用方式和特点。
1. @RestController
@RestController 是 Spring Boot 新增的一个注解,我们看一下该注解都包含了哪些东西。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}
可以看出, @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注解,使用过 Spring 的朋友对 @Controller 注解已经非常了解了,这里不再赘述, @ResponseBody 注解是将返回的数据结构转换为 Json 格式。所以 @RestController 可以看作是 @Controller 和 @ResponseBody 的结合体,相当于偷个懒,我们使用 @RestController 之后就不用再使用 @Controller 了。但是需要注意一个问题:如果是前后端分离,不用模板渲染的话,比如 Thymeleaf,这种情况下是可以直接使用@RestController 将数据以 json 格式传给前端,前端拿到之后解析;但如果不是前后端分离,需要使用模板来渲染的话,一般 Controller 中都会返回到具体的页面,那么此时就不能使用@RestController了,比如:
public String getUser() {
return "user";
}
其实是需要返回到 user.html 页面的,如果使用 @RestController 的话,会将 user 作为字符串返回的,所以这时候我们需要使用 @Controller 注解。这在下一节 Spring Boot 集成 Thymeleaf 模板引擎中会再说明。
2. @RequestMapping
@RequestMapping 是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;在方法的级别表示进一步指定到处理方法的映射关系。
该注解有6个属性,一般在项目中比较常用的有三个属性:value、method 和 produces。
- value 属性:指定请求的实际地址,value 可以省略不写
- method 属性:指定请求的类型,主要有 GET、PUT、POST、DELETE,默认为 GET
- produces属性:指定返回内容类型,如 produces = "application/json; charset=UTF-8"
@RequestMapping 注解比较简单,举个例子:
@RestController
@RequestMapping(value = "/test", produces = "application/json; charset=UTF-8")
public class TestController {
@RequestMapping(value = "/get", method = RequestMethod.GET)
public String testGet() {
return "success";
}
}
这个很简单,启动项目在浏览器中输入 localhost:8080/test/get 测试一下即可。
针对四种不同的请求方式,是有相应注解的,不用每次在 @RequestMapping 注解中加 method 属性来指定,上面的 GET 方式请求可以直接使用 @GetMapping("/get") 注解,效果一样。相应地,PUT 方式、POST 方式和 DELETE 方式对应的注解分别为 @PutMapping、@PostMapping 和 DeleteMapping。
3. @PathVariable
@PathVariable 注解主要是用来获取 url 参数,Spring Boot 支持 restfull 风格的 url,比如一个 GET 请求携带一个参数 id 过来,我们将 id 作为参数接收,可以使用 @PathVariable 注解。如下:
@GetMapping("/user/{id}")
public String testPathVariable(@PathVariable Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
这里需要注意一个问题,如果想要 url 中占位符中的 id 值直接赋值到参数 id 中,需要保证 url 中的参数和方法接收参数一致,否则就无法接收。如果不一致的话,其实也可以解决,需要用 @PathVariable 中的 value 属性来指定对应关系。如下:
@RequestMapping("/user/{idd}")
public String testPathVariable(@PathVariable(value = "idd") Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
对于访问的 url,占位符的位置可以在任何位置,不一定非要在最后,比如这样也行:/xxx/{id}/user。另外,url 也支持多个占位符,方法参数使用同样数量的参数来接收,原理和一个参数是一样的,例如:
@GetMapping("/user/{idd}/{name}")
public String testPathVariable(@PathVariable(value = "idd") Integer id, @PathVariable String name) {
System.out.println("获取到的id为:" + id);
System.out.println("获取到的name为:" + name);
return "success";
}
运行项目,在浏览器中请求 localhost:8080/test/user/2/zhangsan 可以看到控制台输出如下信息:
获取到的id为:2
获取到的name为:zhangsan
所以支持多个参数的接收。同样地,如果 url 中的参数和方法中的参数名称不同的话,也需要使用 value 属性来绑定两个参数。
4. @RequestParam
@RequestParam 注解顾名思义,也是获取请求参数的,上面我们介绍了 @PathValiable 注解也是获取请求参数的,那么 @RequestParam 和 @PathVariable 有什么不同呢?主要区别在于: @PathValiable 是从 url 模板中获取参数值, 即这种风格的 url:http://localhost:8080/user/{id} ;而 @RequestParam 是从 request 里面获取参数值,即这种风格的 url:http://localhost:8080/user?id=1 。我们使用该 url 带上参数 id 来测试一下如下代码:
@GetMapping("/user")
public String testRequestParam(@RequestParam Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
可以正常从控制台打印出 id 信息。同样地,url 上面的参数和方法的参数需要一致,如果不一致,也需要使用 value 属性来说明,比如 url 为:http://localhost:8080/user?idd=1
@RequestMapping("/user")
public String testRequestParam(@RequestParam(value = "idd", required = false) Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
除了 value 属性外,还有个两个属性比较常用:
- required 属性:true 表示该参数必须要传,否则就会报 404 错误,false 表示可有可无。
- defaultValue 属性:默认值,表示如果请求中没有同名参数时的默认值。
从 url 中可以看出,@RequestParam 注解用于 GET 请求上时,接收拼接在 url 中的参数。除此之外,该注解还可以用于 POST 请求,接收前端表单提交的参数,假如前端通过表单提交 username 和 password 两个参数,那我们可以使用 @RequestParam 来接收,用法和上面一样。
@PostMapping("/form1")
public String testForm(@RequestParam String username, @RequestParam String password) {
System.out.println("获取到的username为:" + username);
System.out.println("获取到的password为:" + password);
return "success";
}
我们使用 postman 来模拟一下表单提交,测试一下接口:
那么问题来了,如果表单数据很多,我们不可能在后台方法中写上很多参数,每个参数还要 @RequestParam 注解。针对这种情况,我们需要封装一个实体类来接收这些参数,实体中的属性名和表单中的参数名一致即可。
public class User {
private String username;
private String password;
// set get
}
使用实体接收的话,我们不能在前面加 @RequestParam 注解了,直接使用即可。
@PostMapping("/form2")
public String testForm(User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}
使用 postman 再次测试一下表单提交,观察一下返回值和控制台打印出的日志即可。在实际项目中,一般都是封装一个实体类来接收表单数据,因为实际项目中表单数据一般都很多。
5. @RequestBody
@RequestBody 注解用于接收前端传来的实体,接收参数也是对应的实体,比如前端通过 json 提交传来两个参数 username 和 password,此时我们需要在后端封装一个实体来接收。在传递的参数比较多的情况下,使用 @RequestBody 接收会非常方便。例如:
public class User {
private String username;
private String password;
// set get
}
@PostMapping("/user")
public String testRequestBody(@RequestBody User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}
我们使用 postman 工具来测试一下效果,打开 postman,然后输入请求地址和参数,参数我们用 json 来模拟,如下图所有,调用之后返回 success。
同时看一下后台控制台输出的日志:
获取到的username为:倪升武
获取到的password为:123456
可以看出,@RequestBody 注解用于 POST 请求上,接收 json 实体参数。它和上面我们介绍的表单提交有点类似,只不过参数的格式不同,一个是 json 实体,一个是表单提交。在实际项目中根据具体场景和需要使用对应的注解即可。
6. 总结
本节课主要讲解了 Spring Boot 中对 MVC 的支持,分析了 @RestController、 @RequestMapping、@PathVariable、 @RequestParam 和 @RequestBody 四个注解的使用方式,由于 @RestController 中集成了 @ResponseBody 所以对返回 json 的注解不再赘述。以上四个注解是使用频率很高的注解,在所有的实际项目中基本都会遇到,要熟练掌握。
课程源代码下载地址:戳我下载
第06课:Spring Boot集成 Swagger2 展现在线接口文档
1. Swagger 简介
1.1 解决的问题
随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了前后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。前端和后端的唯一联系,变成了 API 接口,所以 API 文档变成了前后端开发人员联系的纽带,变得越来越重要。
那么问题来了,随着代码的不断更新,开发人员在开发新的接口或者更新旧的接口后,由于开发任务的繁重,往往文档很难持续跟着更新,Swagger 就是用来解决该问题的一款重要的工具,对使用接口的人来说,开发人员不需要给他们提供文档,只要告诉他们一个 Swagger 地址,即可展示在线的 API 接口文档,除此之外,调用接口的人员还可以在线测试接口数据,同样地,开发人员在开发接口时,同样也可以利用 Swagger 在线接口文档测试接口数据,这给开发人员提供了便利。
1.2 Swagger 官方
我们打开 Swagger 官网,官方对 Swagger 的定义为:
The Best APIs are Built with Swagger Tools
翻译成中文是:“最好的 API 是使用 Swagger 工具构建的”。由此可见,Swagger 官方对其功能和所处的地位非常自信,由于其非常好用,所以官方对其定位也合情合理。如下图所示:
本文主要讲解在 Spring Boot 中如何导入 Swagger2 工具来展现项目中的接口文档。本节课使用的 Swagger 版本为 2.2.2。下面开始进入 Swagger2 之旅。
2. Swagger2 的 maven 依赖
使用 Swagger2 工具,必须要导入 maven 依赖,当前官方最高版本是 2.8.0,我尝试了一下,个人感觉页面展示的效果不太好,而且不够紧凑,不利于操作。另外,最新版本并不一定是最稳定版本,当前我们实际项目中使用的是 2.2.2 版本,该版本稳定,界面友好,所以本节课主要围绕着 2.2.2 版本来展开,依赖如下:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
3. Swagger2 的配置
使用 Swagger2 需要进行配置,Spring Boot 中对 Swagger2 的配置非常方便,新建一个配置类,Swagger2 的配置类上除了添加必要的 @Configuration 注解外,还需要添加 @EnableSwagger2 注解。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author shengwu ni
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 指定构建api文档的详细信息的方法:apiInfo()
.apiInfo(apiInfo())
.select()
// 指定要生成api接口的包路径,这里把controller作为包路径,生成controller中的所有接口
.apis(RequestHandlerSelectors.basePackage("com.itcodai.course06.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* 构建api文档的详细信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 设置页面标题
.title("Spring Boot集成Swagger2接口总览")
// 设置接口描述
.description("一起学Spring Boot第06课")
// 设置联系方式
.contact("倪升武," + "CSDN:http://blog.csdn.net/eson_15")
// 设置版本
.version("1.0")
// 构建
.build();
}
}
在该配置类中,已经使用注释详细解释了每个方法的作用了,在此不再赘述。到此为止,我们已经配置好了 Swagger2 了。现在我们可以测试一下配置有没有生效,启动项目,在浏览器中输入 localhost:8080/swagger-ui.html,即可看到 swagger2 的接口页面,如下图所示,说明Swagger2 集成成功。
结合该图,对照上面的 Swagger2 配置文件中的配置,可以很明确的知道配置类中每个方法的作用。这样就很容易理解和掌握 Swagger2 中的配置了,也可以看出,其实 Swagger2 配置很简单。
【友情提示】可能有很多朋友在配置 Swagger 的时候会遇到下面的情况,而且还关不掉的,这是因为浏览器缓存引起的,清空一下浏览器缓存即可解决问题。
4. Swagger2 的使用
上面我们已经配置好了 Swagger2,并且也启动测试了一下,功能正常,下面我们开始使用 Swagger2,主要来介绍 Swagger2 中的几个常用的注解,分别在实体类上、 Controller 类上以及 Controller 中的方法上,最后我们看一下 Swagger2 是如何在页面上呈现在线接口文档的,并且结合 Controller 中的方法在接口中测试一下数据。
4.1 实体类注解
本节我们建一个 User 实体类,主要介绍一下 Swagger2 中的 @ApiModel 和 @ApiModelProperty 注解,同时为后面的测试做准备。
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "用户实体类")
public class User {
@ApiModelProperty(value = "用户唯一标识")
private Long id;
@ApiModelProperty(value = "用户姓名")
private String username;
@ApiModelProperty(value = "用户密码")
private String password;
// 省略set和get方法
}
解释下 @ApiModel 和 @ApiModelProperty 注解:
@ApiModel 注解用于实体类,表示对类进行说明,用于参数用实体类接收。@ApiModelProperty 注解用于类中属性,表示对 model 属性的说明或者数据操作更改。
该注解在在线 API 文档中的具体效果在下文说明。
4.2 Controller 类中相关注解
我们写一个 TestController,再写几个接口,然后学习一下 Controller 中和 Swagger2 相关的注解。
import com.itcodai.course06.entiy.JsonResult;
import com.itcodai.course06.entiy.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/swagger")
@Api(value = "Swagger2 在线接口文档")
public class TestController {
@GetMapping("/get/{id}")
@ApiOperation(value = "根据用户唯一标识获取用户信息")
public JsonResult<User> getUserInfo(@PathVariable @ApiParam(value = "用户唯一标识") Long id) {
// 模拟数据库中根据id获取User信息
User user = new User(id, "倪升武", "123456");
return new JsonResult(user);
}
}
我们来学习一下 @Api 、 @ApiOperation 和 @ApiParam 注解。
@Api 注解用于类上,表示标识这个类是 swagger 的资源。@ApiOperation 注解用于方法,表示一个 http 请求的操作。@ApiParam 注解用于参数上,用来标明参数信息。
这里返回的是 JsonResult,是第02课中学习返回 json 数据时封装的实体。以上是 Swagger 中最常用的 5 个注解,接下来运行一下项目工程,在浏览器中输入 localhost:8080/swagger-ui.html 看一下 Swagger 页面的接口状态。
可以看出,Swagger 页面对该接口的信息展示的非常全面,每个注解的作用以及展示的地方在上图中已经标明,通过页面即可知道该接口的所有信息,那么我们直接在线测试一下该接口返回的信息,输入id为1,看一下返回数据:
可以看出,直接在页面返回了 json 格式的数据,开发人员可以直接使用该在线接口来测试数据的正确与否,非常方便。上面是对于单个参数的输入,如果输入参数为某个对象这种情况,Swagger 是什么样子呢?我们再写一个接口。
@PostMapping("/insert")
@ApiOperation(value = "添加用户信息")
public JsonResult<Void> insertUser(@RequestBody @ApiParam(value = "用户信息") User user) {
// 处理添加逻辑
return new JsonResult<>();
}
重启项目,在浏览器中输入 localhost:8080/swagger-ui.html 看一下效果:
5. 总结
OK,本节课详细分析了 Swagger 的优点,以及 Spring Boot 如何集成 Swagger2,包括配置,相关注解的讲解,涉及到了实体类和接口类,以及如何使用。最后通过页面测试,体验了 Swagger 的强大之处,基本上是每个项目组中必备的工具之一,所以要掌握该工具的使用,也不难。
课程源代码下载地址:戳我下载
第07课:Spring Boot集成Thymeleaf模板引擎
1. Thymeleaf 介绍
Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 Thymeleaf 的主要目标是为您的开发工作流程带来优雅的自然模板 - 可以在浏览器中正确显示的HTML,也可以用作静态原型,从而在开发团队中实现更强大的协作。
以上翻译自 Thymeleaf 官方网站。传统的 JSP+JSTL 组合是已经过去了,Thymeleaf 是现代服务端的模板引擎,与传统的 JSP 不同,Thymeleaf 可以使用浏览器直接打开,因为可以忽略掉拓展属性,相当于打开原生页面,给前端人员也带来一定的便利。
什么意思呢?就是说在本地环境或者有网络的环境下,Thymeleaf 均可运行。由于 thymeleaf 支持 html 原型,也支持在 html 标签里增加额外的属性来达到 “模板+数据” 的展示方式,所以美工可以直接在浏览器中查看页面效果,当服务启动后,也可以让后台开发人员查看带数据的动态页面效果。比如:
<div class="ui right aligned basic segment">
<div class="ui orange basic label" th:text="${blog.flag}">静态原创信息</div>
</div>
<h2 class="ui center aligned header" th:text="${blog.title}">这是静态标题</h2>
类似与上面这样,在静态页面时,会展示静态信息,当服务启动后,动态获取数据库中的数据后,就可以展示动态数据,th:text 标签是用来动态替换文本的,这会在下文说明。该例子说明浏览器解释 html 时会忽略 html 中未定义的标签属性(比如 th:text),所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示数据。
2. 依赖导入
在 Spring Boot 中使用 thymeleaf 模板需要引入依赖,可以在创建项目工程时勾选 Thymeleaf,也可以创建之后再手动导入,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
另外,在 html 页面上如果要使用 thymeleaf 模板,需要在页面标签中引入:
<html xmlns:th="http://www.thymeleaf.org">
3. Thymeleaf相关配置
因为 Thymeleaf 中已经有默认的配置了,我们不需要再对其做过多的配置,有一个需要注意一下,Thymeleaf 默认是开启页面缓存的,所以在开发的时候,需要关闭这个页面缓存,配置如下。
spring:
thymeleaf:
cache: false #关闭缓存
否则会有缓存,导致页面没法及时看到更新后的效果。 比如你修改了一个文件,已经 update 到 tomcat 了,但刷新页面还是之前的页面,就是因为缓存引起的。
4. Thymeleaf 的使用
4.1 访问静态页面
这个和 Thymeleaf 没啥关系,应该说是通用的,我把它一并写到这里的原因是一般我们做网站的时候,都会做一个 404 页面和 500 页面,为了出错时给用户一个友好的展示,而不至于一堆异常信息抛出来。Spring Boot 中会自动识别模板目录(templates/)下的 404.html 和 500.html 文件。我们在 templates/ 目录下新建一个 error 文件夹,专门放置错误的 html 页面,然后分别打印些信息。以 404.html 为例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是404页面
</body>
</html>
我们再写一个 controller 来测试一下 404 和 500 页面:
@Controller
@RequestMapping("/thymeleaf")
public class ThymeleafController {
@RequestMapping("/test404")
public String test404() {
return "index";
}
@RequestMapping("/test500")
public String test500() {
int i = 1 / 0;
return "index";
}
}
当我们在浏览器中输入 localhost:8080/thymeleaf/test400 时,故意输入错误,找不到对应的方法,就会跳转到 404.html 显示。 当我们在浏览器中输入 localhost:8088/thymeleaf/test505 时,会抛出异常,然后会自动跳转到 500.html 显示。
【注】这里有个问题需要注意一下,前面的课程中我们说了微服务中会走向前后端分离,我们在 Controller 层上都是使用的 @RestController 注解,自动会把返回的数据转成 json 格式。但是在使用模板引擎时,Controller 层就不能用 @RestController 注解了,因为在使用 thymeleaf 模板时,返回的是视图文件名,比如上面的 Controller 中是返回到 index.html 页面,如果使用 @RestController 的话,会把 index 当作 String 解析了,直接返回到页面了,而不是去找 index.html 页面,大家可以试一下。所以在使用模板时要用 @Controller 注解。
4.2 Thymeleaf 中处理对象
我们来看一下 thymeleaf 模板中如何处理对象信息,假如我们在做个人博客的时候,需要给前端传博主相关信息来展示,那么我们会封装成一个博主对象,比如:
public class Blogger {
private Long id;
private String name;
private String pass;
// 省去set和get
}
然后在controller层中初始化一下:
@GetMapping("/getBlogger")
public String getBlogger(Model model) {
Blogger blogger = new Blogger(1L, "倪升武", "123456");
model.addAttribute("blogger", blogger);
return "blogger";
}
我们先初始化一个 Blogger 对象,然后将该对象放到 Model 中,然后返回到 blogger.html 页面去渲染。接下来我们再写一个 blogger.html 来渲染 blogger 信息:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博主信息</title>
</head>
<body>
<form action="" th:object="${blogger}" >
用户编号:<input name="id" th:value="${blogger.id}"/><br>
用户姓名:<input type="text" name="username" th:value="${blogger.getName()}" /><br>
登陆密码:<input type="text" name="password" th:value="*{pass}" />
</form>
</body>
</html>
可以看出,在 thymeleaf 模板中,使用 th:object="${}" 来获取对象信息,然后在表单里面可以有三种方式来获取对象属性。如下:
使用 th:value="*{属性名}"使用 th:value="${对象.属性名}",对象指的是上面使用 th:object 获取的对象使用 th:value="${对象.get方法}",对象指的是上面使用 th:object 获取的对象
可以看出,在 Thymeleaf 中可以像写 java 一样写代码,很方便。我们在浏览器中输入 localhost:8080/thymeleaf/getBlogger 来测试一下数据:
4.3 Thymeleaf 中处理 List
处理 List 的话,和处理上面介绍的对象差不多,但是需要在 thymeleaf 中进行遍历。我们先在 Controller 中模拟一个 List。
@GetMapping("/getList")
public String getList(Model model) {
Blogger blogger1 = new Blogger(1L, "倪升武", "123456");
Blogger blogger2 = new Blogger(2L, "达人课", "123456");
List<Blogger> list = new ArrayList<>();
list.add(blogger1);
list.add(blogger2);
model.addAttribute("list", list);
return "list";
}
接下来我们写一个 list.html 来获取该 list 信息,然后在 list.html 中遍历这个list。如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博主信息</title>
</head>
<body>
<form action="" th:each="blogger : ${list}" >
用户编号:<input name="id" th:value="${blogger.id}"/><br>
用户姓名:<input type="text" name="password" th:value="${blogger.name}"/><br>
登录密码:<input type="text" name="username" th:value="${blogger.getPass()}"/>
</form>
</body>
</html>
可以看出,其实和处理单个对象信息差不多,Thymeleaf 使用 th:each 进行遍历,${} 取 model 中传过来的参数,然后自定义 list 中取出来的每个对象,这里定义为 blogger。表单里面可以直接使用 ${对象.属性名} 来获取 list 中对象的属性值,也可以使用 ${对象.get方法} 来获取,这点和上面处理对象信息是一样的,但是不能使用 *{属性名} 来获取对象中的属性,thymeleaf 模板获取不到。
4.4 其他常用 thymeleaf 操作
我们来总结一下 thymeleaf 中的一些常用的标签操作,如下:
标签 |
功能 |
例子 |
|
给属性赋值 |
|
|
设置样式 |
|
|
点击事件 |
|
|
条件判断 |
|
|
超链接 |
|
|
条件判断和 相反 |
|
|
配合 |
|
|
配合 |
|
|
地址引入 |
|
|
表单提交的地址 |
|
Thymeleaf 还有很多其他用法,这里就不总结了,具体的可以参考Thymeleaf的官方文档(v3.0)。主要要学会如何在 Spring Boot 中去使用 thymeleaf,遇到对应的标签或者方法,查阅官方文档即可。
5. 总结
Thymeleaf 在 Spring Boot 中使用非常广泛,本节课主要分析了 thymeleaf 的优点,以及如何在 Spring Boot 中集成并使用 thymeleaf 模板,包括依赖、配置,相关数据的获取、以及一些注意事项等等。最后列举了一些 thymeleaf 中常用的标签,在实际项目中多使用,多查阅就能熟练掌握,thymeleaf 中的一些标签或者方法不用死记硬背,用到什么去查阅什么,关键是要会在 Spring Boot 中集成,用的多了就熟能生巧。
课程源代码下载地址:戳我下载
第08课:Spring Boot中的全局异常处理
在项目开发过程中,不管是对底层数据库的操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。如果对每个过程都单独作异常处理,那系统的代码耦合度会变得很高,此外,开发工作量也会加大而且不好统一,这也增加了代码的维护成本。 针对这种实际情况,我们需要将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能单一,也实现了异常信息的统一处理和维护。同时,我们也不希望直接把异常抛给用户,应该对异常进行处理,对错误信息进行封装,然后返回一个友好的信息给用户。这节主要总结一下项目中如何使用 Spring Boot 如何拦截并处理全局的异常。
1. 定义返回的统一 json 结构
前端或者其他服务请求本服务的接口时,该接口需要返回对应的 json 数据,一般该服务只需要返回请求着需要的参数即可,但是在实际项目中,我们需要封装更多的信息,比如状态码 code、相关信息 msg 等等,这一方面是在项目中可以有个统一的返回结构,整个项目组都适用,另一方面是方便结合全局异常处理信息,因为异常处理信息中一般我们需要把状态码和异常内容反馈给调用方。 这个统一的 json 结构这可以参考第02课:Spring Boot 返回 JSON 数据及数据封装中封装的统一 json 结构,本节内容我们简化一下,只保留状态码 code 和异常信息 msg即可。如下:
public class JsonResult {
/**
* 异常码
*/
protected String code;
/**
* 异常信息
*/
protected String msg;
public JsonResult() {
this.code = "200";
this.msg = "操作成功";
}
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
// get set
}
2. 处理系统异常
新建一个 GlobalExceptionHandler 全局异常处理类,然后加上 @ControllerAdvice 注解即可拦截项目中抛出的异常,如下:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
// 打印log
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// ……
}
我们点开 @ControllerAdvice 注解可以看到,@ControllerAdvice 注解包含了 @Component 注解,说明在 Spring Boot 启动时,也会把该类作为组件交给 Spring 来管理。除此之外,该注解还有个 basePackages 属性,该属性是用来拦截哪个包中的异常信息,一般我们不指定这个属性,我们拦截项目工程中的所有异常。@ResponseBody 注解是为了异常处理完之后给调用方输出一个 json 格式的封装数据。 在项目中如何使用呢?Spring Boot 中很简单,在方法上通过 @ExceptionHandler 注解来指定具体的异常,然后在方法中处理该异常信息,最后将结果通过统一的 json 结构体返回给调用者。下面我们举几个例子来说明如何来使用。
2.1 处理参数缺失异常
在前后端分离的架构中,前端请求后台的接口都是通过 rest 风格来调用,有时候,比如 POST 请求 需要携带一些参数,但是往往有时候参数会漏掉。另外,在微服务架构中,涉及到多个微服务之间的接口调用时,也可能出现这种情况,此时我们需要定义一个处理参数缺失异常的方法,来给前端或者调用方提示一个友好信息。
参数缺失的时候,会抛出 HttpMessageNotReadableException,我们可以拦截该异常,做一个友好处理,如下:
/**
* 缺少请求参数异常
* @param ex HttpMessageNotReadableException
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public JsonResult handleHttpMessageNotReadableException(
MissingServletRequestParameterException ex) {
logger.error("缺少请求参数,{}", ex.getMessage());
return new JsonResult("400", "缺少必要的请求参数");
}
我们来写个简单的 Controller 测试一下该异常,通过 POST 请求方式接收两个参数:姓名和密码。
@RestController
@RequestMapping("/exception")
public class ExceptionController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);
@PostMapping("/test")
public JsonResult test(@RequestParam("name") String name,
@RequestParam("pass") String pass) {
logger.info("name:{}", name);
logger.info("pass:{}", pass);
return new JsonResult();
}
}
然后使用 Postman 来调用一下该接口,调用的时候,只传姓名,不传密码,就会抛缺少参数异常,该异常被捕获之后,就会进入我们写好的逻辑,给调用方返回一个友好信息,如下:
2.2 处理空指针异常
空指针异常是开发中司空见惯的东西了,一般发生的地方有哪些呢? 先来聊一聊一些注意的地方,比如在微服务中,经常会调用其他服务获取数据,这个数据主要是 json 格式的,但是在解析 json 的过程中,可能会有空出现,所以我们在获取某个 jsonObject 时,再通过该 jsonObject 去获取相关信息时,应该要先做非空判断。 还有一个很常见的地方就是从数据库中查询的数据,不管是查询一条记录封装在某个对象中,还是查询多条记录封装在一个 List 中,我们接下来都要去处理数据,那么就有可能出现空指针异常,因为谁也不能保证从数据库中查出来的东西就一定不为空,所以在使用数据时一定要先做非空判断。 对空指针异常的处理很简单,和上面的逻辑一样,将异常信息换掉即可。如下:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 空指针异常
* @param ex NullPointerException
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleTypeMismatchException(NullPointerException ex) {
logger.error("空指针异常,{}", ex.getMessage());
return new JsonResult("500", "空指针异常了");
}
}
这个我就不测试了,代码中 ExceptionController 有个 testNullPointException 方法,模拟了一个空指针异常,我们在浏览器中请求一下对应的 url 即可看到返回的信息:
{"code":"500","msg":"空指针异常了"}
2.3 一劳永逸?
当然了,异常很多,比如还有 RuntimeException,数据库还有一些查询或者操作异常等等。由于 Exception 异常是父类,所有异常都会继承该异常,所以我们可以直接拦截 Exception 异常,一劳永逸:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 系统异常 预期以外异常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleUnexpectedServer(Exception ex) {
logger.error("系统异常:", ex);
return new JsonResult("500", "系统发生异常,请联系管理员");
}
}
但是项目中,我们一般都会比较详细的去拦截一些常见异常,拦截 Exception 虽然可以一劳永逸,但是不利于我们去排查或者定位问题。实际项目中,可以把拦截 Exception 异常写在 GlobalExceptionHandler 最下面,如果都没有找到,最后再拦截一下 Exception 异常,保证输出信息友好。
3. 拦截自定义异常
在实际项目中,除了拦截一些系统异常外,在某些业务上,我们需要自定义一些业务异常,比如在微服务中,服务之间的相互调用很平凡,很常见。要处理一个服务的调用时,那么可能会调用失败或者调用超时等等,此时我们需要自定义一个异常,当调用失败时抛出该异常,给 GlobalExceptionHandler 去捕获。
3.1 定义异常信息
由于在业务中,有很多异常,针对不同的业务,可能给出的提示信息不同,所以为了方便项目异常信息管理,我们一般会定义一个异常信息枚举类。比如:
/**
* 业务异常提示信息枚举类
* @author shengwu ni
*/
public enum BusinessMsgEnum {
/** 参数异常 */
PARMETER_EXCEPTION("102", "参数异常!"),
/** 等待超时 */
SERVICE_TIME_OUT("103", "服务调用超时!"),
/** 参数过大 */
PARMETER_BIG_EXCEPTION("102", "输入的图片数量不能超过50张!"),
/** 500 : 一劳永逸的提示也可以在这定义 */
UNEXPECTED_EXCEPTION("500", "系统发生异常,请联系管理员!");
// 还可以定义更多的业务异常
/**
* 消息码
*/
private String code;
/**
* 消息内容
*/
private String msg;
private BusinessMsgEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
// set get方法
}
3.2 拦截自定义异常
然后我们可以定义一个业务异常,当出现业务异常时,我们就抛这个自定义的业务异常即可。比如我们定义一个 BusinessErrorException 异常,如下:
/**
* 自定义业务异常
* @author shengwu ni
*/
public class BusinessErrorException extends RuntimeException {
private static final long serialVersionUID = -7480022450501760611L;
/**
* 异常码
*/
private String code;
/**
* 异常提示信息
*/
private String message;
public BusinessErrorException(BusinessMsgEnum businessMsgEnum) {
this.code = businessMsgEnum.code();
this.message = businessMsgEnum.msg();
}
// get set方法
}
在构造方法中,传入我们上面自定义的异常枚举类,所以在项目中,如果有新的异常信息需要添加,我们直接在枚举类中添加即可,很方便,做到统一维护,然后再拦截该异常时获取即可。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 拦截业务异常,返回业务异常信息
* @param ex
* @return
*/
@ExceptionHandler(BusinessErrorException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleBusinessError(BusinessErrorException ex) {
String code = ex.getCode();
String message = ex.getMessage();
return new JsonResult(code, message);
}
}
在业务代码中,我们可以直接模拟一下抛出业务异常,测试一下:
@RestController
@RequestMapping("/exception")
public class ExceptionController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);
@GetMapping("/business")
public JsonResult testException() {
try {
int i = 1 / 0;
} catch (Exception e) {
throw new BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
}
return new JsonResult();
}
}
运行一下项目,测试一下,返回 json 如下,说明我们自定义的业务异常捕获成功:
{"code":"500","msg":"系统发生异常,请联系管理员!"}
4. 总结
本节课程主要讲解了Spring Boot 的全局异常处理,包括异常信息的封装、异常信息的捕获和处理,以及在实际项目中,我们用到的自定义异常枚举类和业务异常的捕获与处理,在项目中运用的非常广泛,基本上每个项目中都需要做全局异常处理。
课程源代码下载地址:戳我下载
第09课:Spring Boot中的切面AOP处理
1. 什么是AOP
AOP:Aspect Oriented Programming 的缩写,意为:面向切面编程。面向切面编程的目标就是分离关注点。什么是关注点呢?就是关注点,就是你要做的事情。假如你是一位公子哥,没啥人生目标,每天衣来伸手,饭来张口,整天只知道一件事:玩(这就是你的关注点,你只要做这一件事)!但是有个问题,你在玩之前,你还需要起床、穿衣服、穿鞋子、叠被子、做早饭等等等等,但是这些事情你不想关注,也不用关注,你只想想玩,那么怎么办呢?
对!这些事情通通交给下人去干。你有一个专门的仆人 A 帮你穿衣服,仆人 B 帮你穿鞋子,仆人 C 帮你叠好被子,仆人 D 帮你做饭,然后你就开始吃饭、去玩(这就是你一天的正事),你干完你的正事之后,回来,然后一系列仆人又开始帮你干这个干那个,然后一天就结束了!
这就是 AOP。AOP 的好处就是你只需要干你的正事,其它事情别人帮你干。也许有一天,你想裸奔,不想穿衣服,那么你把仆人 A 解雇就是了!也许有一天,出门之前你还想带点钱,那么你再雇一个仆人 E 专门帮你干取钱的活!这就是AOP。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。
2. Spring Boot 中的 AOP 处理
2.1 AOP 依赖
使用AOP,首先需要引入AOP的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 实现 AOP 切面
Spring Boot 中使用 AOP 非常简单,假如我们要在项目中打印一些 log,在引入了上面的依赖之后,我们新建一个类 LogAspectHandler,用来定义切面和处理方法。只要在类上加个@Aspect注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解让该类交给 Spring 来管理。
@Aspect
@Component
public class LogAspectHandler {
}
这里主要介绍几个常用的注解及使用:
1.@Pointcut:定义一个切面,即上面所描述的关注的某件事入口。 2.@Before:在做某件事之前做的事。 3.@After:在做某件事之后做的事。 4.@AfterReturning:在做某件事之后,对其返回值做增强处理。 5.@AfterThrowing:在做某件事抛出异常时,处理。
2.2.1 @Pointcut 注解
@Pointcut 注解:用来定义一个切面(切入点),即上文中所关注的某件事情的入口。切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。
@Aspect
@Component
public class LogAspectHandler {
/**
* 定义一个切面,拦截com.itcodai.course09.controller包和子包下的所有方法
*/
@Pointcut("execution(* com.itcodai.course09.controller..*.*(..))")
public void pointCut() {}
}
@Pointcut 注解指定一个切面,定义需要拦截的东西,这里介绍两个常用的表达式:一个是使用 execution(),另一个是使用 annotation()。 以 execution(* com.itcodai.course09.controller..*.*(..))) 表达式为例,语法如下:
execution() 为表达式主体 第一个 * 号的位置:表示返回值类型,* 表示所有类型 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.itcodai.course09.controller 包、子包下所有类的方法 第二个 * 号的位置:表示类名,* 表示所有类 *(..) :这个星号表示方法名,* 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
annotation() 方式是针对某个注解来定义切面,比如我们对具有@GetMapping注解的方法做切面,可以如下定义切面:
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void annotationCut() {}
然后使用该切面的话,就会切入注解是 @GetMapping 的方法。因为在实际项目中,可能对于不同的注解有不同的逻辑处理,比如 @GetMapping、@PostMapping、@DeleteMapping 等。所以这种按照注解的切入方式在实际项目中也很常用。
2.2.2 @Before 注解
@Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 log 处理,也可以做一些信息的统计,比如获取用户的请求 url 以及用户的 ip 地址等等,这个在做个人站点的时候都能用得到,都是常用的方法。例如:
@Aspect
@Component
public class LogAspectHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在上面定义的切面方法之前执行该方法
* @param joinPoint jointPoint
*/
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
logger.info("====doBefore方法进入了====");
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执行的方法名
String funcName = signature.getName();
logger.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);
// 也可以用来记录一些信息,比如获取请求的url和ip
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取请求url
String url = request.getRequestURL().toString();
// 获取请求ip
String ip = request.getRemoteAddr();
logger.info("用户请求的url为:{},ip地址为:{}", url, ip);
}
}
JointPoint 对象很有用,可以用它来获取一个签名,然后利用签名可以获取请求的包名、方法名,包括参数(通过 joinPoint.getArgs() 获取)等等。
2.2.3 @After 注解
@After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 log 处理。
@Aspect
@Component
public class LogAspectHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 定义一个切面,拦截com.itcodai.course09.controller包下的所有方法
*/
@Pointcut("execution(* com.itcodai.course09.controller..*.*(..))")
public void pointCut() {}
/**
* 在上面定义的切面方法之后执行该方法
* @param joinPoint jointPoint
*/
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) {
logger.info("====doAfter方法进入了====");
Signature signature = joinPoint.getSignature();
String method = signature.getName();
logger.info("方法{}已经执行完", method);
}
}
到这里,我们来写一个 Controller 来测试一下执行结果,新建一个 AopController 如下:
@RestController
@RequestMapping("/aop")
public class AopController {
@GetMapping("/{name}")
public String testAop(@PathVariable String name) {
return "Hello " + name;
}
}
启动项目,在浏览器中输入 localhost:8080/aop/CSDN,观察一下控制台的输出信息:
====doBefore方法进入了====
即将执行方法为: testAop,属于com.itcodai.course09.controller.AopController包
用户请求的url为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1
====doAfter方法进入了====
方法testAop已经执行完
从打印出来的 log 中可以看出程序执行的逻辑与顺序,可以很直观的掌握 @Before 和 @After 两个注解的实际作用。
2.2.4 @AfterReturning 注解
@AfterReturning 注解和 @After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理,例如:
@Aspect
@Component
public class LogAspectHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
* @param joinPoint joinPoint
* @param result result
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
logger.info("方法{}执行完毕,返回参数为:{}", classMethod, result);
// 实际项目中可以根据业务做具体的返回值增强
logger.info("对返回参数进行业务上的增强:{}", result + "增强版");
}
}
需要注意的是:在 @AfterReturning注解 中,属性 returning 的值必须要和参数保持一致,否则会检测不到。该方法中的第二个入参就是被切方法的返回值,在 doAfterReturning 方法中可以对返回值进行增强,可以根据业务需要做相应的封装。我们重启一下服务,再测试一下(多余的 log 我不贴出来了):
方法testAop执行完毕,返回参数为:Hello CSDN
对返回参数进行业务上的增强:Hello CSDN增强版
2.2.5 @AfterThrowing 注解
顾名思义,@AfterThrowing 注解是当被切方法执行时抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。
/**
* 使用AOP处理log
* @author shengwu ni
* @date 2018/05/04 20:24
*/
@Aspect
@Component
public class LogAspectHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在上面定义的切面方法执行抛异常时,执行该方法
* @param joinPoint jointPoint
* @param ex ex
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
// 处理异常的逻辑
logger.info("执行方法{}出错,异常为:{}", method, ex);
}
}
该方法我就不测试了,大家可以自行测试一下。
3. 总结
本节课针对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
课程源代码下载地址:戳我下载
第10课:Spring Boot集成MyBatis
1. MyBatis 介绍
大家都知道,MyBatis 框架是一个持久层框架,是 Apache 下的顶级项目。Mybatis 可以让开发者的主要精力放在 sql 上,通过 Mybatis 提供的映射方式,自由灵活的生成满足需要的 sql 语句。使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs 映射成数据库中的记录,在国内可谓是占据了半壁江山。本节课程主要通过两种方式来对 Spring Boot 集成 MyBatis 做一讲解。重点讲解一下基于注解的方式。因为实际项目中使用注解的方式更多一点,更简洁一点,省去了很多 xml 配置(这不是绝对的,有些项目组中可能也在使用 xml 的方式)。
2. MyBatis 的配置
2.1 依赖导入
Spring Boot 集成 MyBatis,需要导入 mybatis-spring-boot-starter 和 mysql 的依赖,这里我们使用的版本时 1.3.2,如下:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
我们点开 mybatis-spring-boot-starter 依赖,可以看到我们之前使用 Spring 时候熟悉的依赖,就像我在课程的一开始介绍的那样,Spring Boot 致力于简化编码,使用 starter 系列将相关依赖集成在一起,开发者不需要关注繁琐的配置,非常方便。
<!-- 省去其他 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
2.2 properties.yml配置
我们再来看一下,集成 MyBatis 时需要在 properties.yml 配置文件中做哪些基本配置呢?
# 服务端口号
server:
port: 8080
# 数据库地址
datasource:
url: localhost:3306/blog_test
spring:
datasource: # 数据库配置
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${datasource.url}?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
username: root
password: 123456
hikari:
maximum-pool-size: 10 # 最大连接池数
max-lifetime: 1770000
mybatis:
# 指定别名设置的包为所有entity
type-aliases-package: com.itcodai.course10.entity
configuration:
map-underscore-to-camel-case: true # 驼峰命名规范
mapper-locations: # mapper映射文件位置
- classpath:mapper/*.xml
我们来简单介绍一下上面的这些配置:关于数据库的相关配置,我就不详细的解说了,这点相信大家已经非常熟练了,配置一下用户名、密码、数据库连接等等,这里使用的连接池是 Spring Boot 自带的 hikari,感兴趣的朋友可以去百度或者谷歌搜一搜,了解一下。
这里说明一下 map-underscore-to-camel-case: true, 用来开启驼峰命名规范,这个比较好用,比如数据库中字段名为:user_name, 那么在实体类中可以定义属性为 userName (甚至可以写成 username,也能映射上),会自动匹配到驼峰属性,如果不这样配置的话,针对字段名和属性名不同的情况,会映射不到。
3. 基于 xml 的整合
使用原始的 xml 方式,需要新建 UserMapper.xml 文件,在上面的 application.yml 配置文件中,我们已经定义了 xml 文件的路径:classpath:mapper/*.xml,所以我们在 resources 目录下新建一个 mapper 文件夹,然后创建一个 UserMapper.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.itcodai.course10.dao.UserMapper">
<resultMap id="BaseResultMap" type="com.itcodai.course10.entity.User">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="user_name" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
</resultMap>
<select id="getUserByName" resultType="User" parameterType="String">
select * from user where user_name = #{username}
</select>
</mapper>
这和整合 Spring 一样的,namespace 中指定的是对应的 Mapper, <resultMap> 中指定对应的实体类,即 User。然后在内部指定表的字段和实体的属性相对应即可。这里我们写一个根据用户名查询用户的 sql。
实体类中有 id,username 和 password,我不在这贴代码,大家可以下载源码查看。UserMapper.java 文件中写一个接口即可:
User getUserByName(String username);
中间省略 service 的代码,我们写一个 Controller 来测试一下:
@RestController
public class TestController {
@Resource
private UserService userService;
@RequestMapping("/getUserByName/{name}")
public User getUserByName(@PathVariable String name) {
return userService.getUserByName(name);
}
}
启动项目,在浏览器中输入:http://localhost:8080/getUserByName/CSDN 即可查询到数据库表中用户名为 CSDN 的用户信息(事先搞两个数据进去即可):
{"id":2,"username":"CSDN","password":"123456"}
这里需要注意一下:Spring Boot 如何知道这个 Mapper 呢?一种方法是在上面的 mapper 层对应的类上面添加 @Mapper 注解即可,但是这种方法有个弊端,当我们有很多个 mapper 时,那么每一个类上面都得添加 @Mapper 注解。另一种比较简便的方法是在 Spring Boot 启动类上添加@MaperScan 注解,来扫描一个包下的所有 mapper。如下:
@SpringBootApplication
@MapperScan("com.itcodai.course10.dao")
public class Course10Application {
public static void main(String[] args) {
SpringApplication.run(Course10Application.class, args);
}
}
这样的话,com.itcodai.course10.dao 包下的所有 mapper 都会被扫描到了。
4. 基于注解的整合
基于注解的整合就不需要 xml 配置文件了,MyBatis 主要提供了 @Select, @Insert, @Update, Delete 四个注解。这四个注解是用的非常多的,也很简单,注解后面跟上对应的 sql 语句即可,我们举个例子:
@Select("select * from user where id = #{id}")
User getUser(Long id);
这跟 xml 文件中写 sql 语句是一样的,这样就不需要 xml 文件了,但是有个问题,有人可能会问,如果是两个参数呢?如果是两个参数,我们需要使用 @Param 注解来指定每一个参数的对应关系,如下:
@Select("select * from user where id = #{id} and user_name=#{name}")
User getUserByIdAndName(@Param("id") Long id, @Param("name") String username);
可以看出,@Param 指定的参数应该要和 sql 中 #{} 取的参数名相同,不同则取不到。可以在 controller 中自行测试一下,接口都在源码中,文章中我就不贴测试代码和结果了。
有个问题需要注意一下,一般我们在设计表字段后,都会根据自动生成工具生成实体类,这样的话,基本上实体类是能和表字段对应上的,最起码也是驼峰对应的,由于在上面配置文件中开启了驼峰的配置,所以字段都是能对的上的。但是,万一有对不上的呢?我们也有解决办法,使用 @Results 注解来解决。
@Select("select * from user where id = #{id}")
@Results({
@Result(property = "username", column = "user_name"),
@Result(property = "password", column = "password")
})
User getUser(Long id);
@Results 中的 @Result 注解是用来指定每一个属性和字段的对应关系,这样的话就可以解决上面说的这个问题了。
当然了,我们也可以 xml 和注解相结合使用,目前我们实际的项目中也是采用混用的方式,因为有时候 xml 方便,有时候注解方便,比如就上面这个问题来说,如果我们定义了上面的这个 UserMapper.xml,那么我们完全可以使用 @ResultMap 注解来替代 @Results 注解,如下:
@Select("select * from user where id = #{id}")
@ResultMap("BaseResultMap")
User getUser(Long id);
@ResultMap 注解中的值从哪来呢?对应的是 UserMapper.xml 文件中定义的 <resultMap> 时对应的 id 值:
<resultMap id="BaseResultMap" type="com.itcodai.course10.entity.User">
这种 xml 和注解结合着使用的情况也很常见,而且也减少了大量的代码,因为 xml 文件可以使用自动生成工具去生成,也不需要人为手动敲,所以这种使用方式也很常见。
5. 总结
本节课主要系统的讲解了 Spring Boot 集成 MyBatis 的过程,分为基于 xml 形式和基于注解的形式来讲解,通过实际配置手把手讲解了 Spring Boot 中 MyBatis 的使用方式,并针对注解方式,讲解了常见的问题已经解决方式,有很强的实战意义。在实际项目中,建议根据实际情况来确定使用哪种方式,一般 xml 和注解都在用。