-
一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
-
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
-
是的这就是Java企业级应用->J2EE->spring->springboot的过程。
-
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
-
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
-
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
-
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
● 能够快速创建基于Spring的应用程序
● 能够直接使用java main方法启动内嵌的Tomcat服务器运行SpringBoot程序,不需要部署war包文件
● 提供约定的starter POM来简化Maven配置,让Maven的配置变得简单
● 自动化配置,根据项目的Maven依赖配置,Springboot自动配置Spring、Spring mvc等
● 提供了程序的健康检查等功能
● 基本可以完全不使用XML配置文件,采用注解配置
SpringBoot四大核心
● 自动配置
针对很多Spring应用程序和常见的应用功能,SpringBoot能自动提供相关配置
● 起步依赖
告诉SpringBoot需要什么功能,它就能引入需要的依赖库
● Actuator
让你能够深入运行中的SpringBoot应用程序,一探SpringBoot程序的内部信息
● 命令行界面
这是SpringBoot的可选特性,主要针对Groovy语言使用;
Groovy是一种基于JVM(Java虚拟机) 的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与Java代码很好地结合,也能用于扩展现有代码,由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。
2.第一个spring boot程序 2.1.使用 IDEA 直接创建项目1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
2.2.pom.xml 分析4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.6 com.example springbootOne 0.0.1-SNAPSHOT springbootOne Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin
-
项目元数据:创建时候输入的Project metadata部分,也就是Maven项目的基本元素,包括:groupId、artifactId、version、name、description等
-
parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容
-
dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用将在后面逐步展开。
-
build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。
1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
(C:/Users/17144/AppData/Roaming/Typora/typora-user-images/image-20211108135229517.png)]
org.springframework.boot spring-boot-dependencies 2.5.6
3、这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
4、以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了
3.2.启动器 spring-boot-starter-
依赖
org.springframework.boot spring-boot-starter -
springboot-boot-starter-xxx,说白了就是Springboot的启动场景
-
比如spring-boot-starter-web,他就会帮我们自动导入web的所有依赖
-
springboot会将所有的功能场景,都变成一个个的启动器
-
我们要使用什么功能,就只需要找到对应的启动器就好了start
package com.example.springbootone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootOneApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootOneApplication.class, args);
}
}
3.3.2注解(@SpringBootApplication)
-
作用:标注在某个类上说明这个类是SpringBoot的主配置
-
SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
-
进入这个注解:可以看到上面还有很多其他注解!
-
这个注解在Spring中很重要 ,它对应XML配置中的元素。
-
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
-
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
-
我们继续进去这个注解查看
-
这里的 @Configuration,说明这是一个spring的配置类 ,配置类就是对应Spring的xml 配置文件;
-
@Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
-
我们回到 SpringBootApplication 注解中继续看。
-
开启自动配置功能
- 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
- @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
点进注解接续查看:
-
@AutoConfigurationPackage :自动配置包
-
-
@import :Spring底层注解@import , 给容器中导入一个组件
-
Registrar.class 作用:自动配置包注册,将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
-
这个分析完了,退到上一步,继续看
-
-
@import({AutoConfigurationimportSelector.class}) :给容器导入组件 ;
- AutoConfigurationimportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
// 获取所有的配置 Listconfigurations = this.getCandidateConfigurations(annotationmetadata, attributes);
-
获得候选的配置
protected List
getCandidateConfigurations(Annotationmetadata metadata, AnnotationAttributes attributes) { // 和下面的方法对应 //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in meta-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } //和上面的类的方法loadFactoryNames里面的第一个参数对应 //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration protected Class> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } -
这个方法getCandidateConfigurations()又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法,获取所有的加载配置
public static List
loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } -
我们继续点击查看 loadSpringFactories 方法
- 项目资源:meta-INF/spring.factories
- 系统资源:meta-INF/spring.factories
- 从这些资源中配置了所有的nextElement(自动配置),分装成properties
//将所有的资源加载到配置类中(将下面的抽离出来分析,第15行) Properties properties = PropertiesLoaderUtils.loadProperties(resource);
private static Map
> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "meta-INF/spring.factories" Enumeration urls = classLoader != null ? classLoader.getResources("meta-INF/spring.factories") : ClassLoader.getSystemResources("meta-INF/spring.factories"); linkedMultiValueMap result = new linkedMultiValueMap(); //判断有没有更多的元素,将读取到的资源循环遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [meta-INF/spring.factories]", var13); } } } -
发现一个多次出现的文件:spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
3.3.4.WebMvcAutoConfiguration在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的meta-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中
3.3.5.结论:-
SpringBoot在启动的时候从类路径下的meta-INF/spring.factories中获取EnableAutoConfiguration指定的值
-
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
-
以前我们需要自动配置的东西,现在springboot帮我们做了
-
整合JavaEE,整体解决方案和自动配置的东西都在springboot-autoconfigure的jar包中;
-
它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器中
-
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并自动配置,@Configuration(javaConfig) ;
-
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
@SpringBootApplication
public class Springboot01HellowordApplication {
public static void main(String[] args) {
//该方法返回一个ConfigurableApplicationContext对象
//参数一:应用入口的类; 参数二:命令行参数
SpringApplication.run(Springboot01HellowordApplication.class, args);
}
}
SpringApplication.run分析
- 分析该方法主要分两部分
- 一是SpringApplication的实例化,
- 二是run方法的执行;
这个类主要做了以下四件事情:
-
推断应用的类型是普通的项目还是Web项目
-
查找并加载所有可用初始化器 , 设置到initializers属性中
-
找出所有的应用程序监听器,设置到listeners属性中
-
推断并设置main方法的定义类,找到运行的主类
查看构造器:
3.4.2.run方法流程分析 4.yaml语法学习 4.1.配置文件SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
-
application.properties
-
- 语法结构 :key=value
-
application.yaml
-
- 语法结构 :key:空格 value
**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server: port: 80814.2.YAML yaml概述
-
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
-
这种语言以数据作为中心,而不是以标记语言为重点!
-
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
-
传统xml配置:
8081 -
yaml配置:
server: prot: 8080
-
说明:语法要求严格!
-
空格不能省略
-
以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
-
属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
-
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;k: v
注意:
-
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “wangn jingmo” 输出 :wang换行 jingmo
-
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘wangn jingmo’ 输出 :wangn jingmo
-
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: wangyanling
age: 3
行内写法
student: {name: wangyanling,age: 3}
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets: - cat - dog - pig
行内写法
pets: [cat,dog,pig]
修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server: port: 80824.3.注入配置文件
yaml文件强大的地方在于可以给实体类直接注入匹配值
4.3.1. yaml注入配置文件① 在springboot项目中的resources目录下新建一个文件 application.yml
② 编写一个实体类 Dog
package com.wyl.springboot.pojo;
@Component //注册bean到容器中
public class Dog {
private String name;
private Integer age;
//有参无参构造、get、set方法、toString()方法
}
③ 试着用@Value给bean注入属性值
@Component //注册bean
public class Dog {
@Value("旺财")
private String name;
@Value("1")
private Integer age;
}
④ 在SpringBoot的测试类下注入并输出
@SpringBootTest
class DemoApplicationTests {
@Autowired //将狗狗自动注入进来
Dog dog;
@Test
public void contextLoads() {
System.out.println(dog); //打印看下狗狗对象
}
}
结果成功输出,@Value注入成功
Dog{name='旺财', age=1}
⑤ 再编写一个复杂点的实体类
@Component //注册bean到容器中
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map maps;
private List
⑥ 使用yaml配置的方式进行注入
写的时候注意区别和优势,首先编写一个yaml配置
person:
name: wjm
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1
⑦ 把对象的所有值都写好后,注入到类中
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map maps;
private List
⑧ IDEA 提示,springboot配置注解处理器没有找到
Not Found The requested URL /spring-boot/docs/2.3.3.RELEASE/reference/html/configuration-metadata.html was not found on this server.
查看文档(在网址中更改版本获得,如回到2.1.9),找到一个依赖
org.springframework.boot spring-boot-configuration-processor true
⑨ 确认以上配置都完成后,去测试类中测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
Person person; //将person自动注入进来
@Test
public void contextLoads() {
System.out.println(person); //打印person信息
}
}
结果:所有值全部注入成功
4.3.2. 加载指定配置文件@PropertySource :加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值
- 在resources目录下新建一个person.properties文件
name=hello
- 在代码中指定加载person.properties文件
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
......
}
- 再次输出测试,指定配置文件绑定成功
配置文件还可以编写占位符生成随机数
person:
name: wangjingmo${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
4.3.4.回顾properties配置
上面采用的yaml方法都是最简单的方式,也是开发中最常用的、pringboot所推荐的
接下来看看其他的实现方式,原理都是相同的,写还是那样写
配置文件除了yml还有之前常用的properties
【注意】properties配置文件在写中文的时候会有乱码 , 需要去IDEA中设置编码格式为UTF-8:settings–>FileEncodings 中配置
测试步骤
- 新建一个实体类User
@Component //注册bean
public class User {
private String name;
private int age;
private String sex;
}
- 编辑配置文件 user.properties
user1.name=wyl user1.age=18 user1.sex=男
- 在User类上使用@Value来进行注入
@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
//直接使用@value
@Value("${user.name}") //从配置文件中取值
private String name;
@Value("#{9*2}") // #{SPEL} Spring表达式
private int age;
@Value("男") // 字面量
private String sex;
}
- Springboot测试
SpringBootTest
class DemoApplicationTests {
@Autowired
User user;
@Test
public void contextLoads() {
System.out.println(user);
}
}
结果正常输出
4.3.4.对比小结@Value使用起来并不友好!我们需要为每个属性单独注解赋值比较麻烦
- @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
- 松散绑定:这个什么意思呢? 比如yml中写的last-name,这个和lastName是一样的,- 后面跟着的字母默认是大写的。这就是松散绑定
- JSR303数据校验 ,可以在字段是增加一层过滤器验证 , 保证数据的合法性
- 复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
- 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
- 如果在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
- 如果专门编写了一个JavaBean来和配置文件进行一一映射,就直接使用@configurationProperties
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。这里来写个注解让name只能支持Email格式
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
运行结果:default message [不是一个合法的电子邮件地址]
使用数据校验,可以保证数据的正确性; 下面列出一些常见的使用
@NotNull(message="名字不能为空") private String userName; @Max(value=120,message="年龄最大不能查过120") private int age; @Email(message="邮箱格式错误") private String email; 空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空 格. @NotEmpty 检查约束元素是否为NULL或者是EMPTY. Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false 长度检查 @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) string is between min and max included. 日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则 .......等等 除此以外,我们还可以自定义一些数据校验规则5.1.多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境
5.1.1.多配置文件在主配置文件编写的时候,文件名可以是application-{profile}.properties/yml , 用来指定多个环境版本。例如:application-test.properties 代表测试环境配置 application-dev.properties代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件。但可以通过配置来选择需要激活的环境
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试; #我们启动SpringBoot,就可以看到已经切换到dev下的配置了; spring.profiles.active=dev5.1.2.yml的多文档块
和properties配置文件中一样,但使用yml去实现不需要创建多个配置文件,更加方便
server: port: 8081 #选择要激活那个环境块 spring: profiles: active: prod --- server: port: 8083 spring: profiles: dev #配置环境的名称 --- server: port: 8084 spring: profiles: prod #配置环境的名称
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的
5.1.3.配置文件加载位置外部加载配置文件的方式很多,一般选择最常用的即可,在开发的资源文件中进行配置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
优先级1:项目路径下的config文件夹配置文件 优先级2:项目路径下配置文件 优先级3:资源路径下的config文件夹配置文件 优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置
指定位置加载配置文件
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;
这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties6.自动配置原理
----联系---- spring.factories
SpringBoot官方文档中有大量的配置,我们无法全部记住,官网:https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-application-properties.html#core-properties
6.1.分析自动配置原理-
SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
-
@EnableAutoConfiguration 作用
-
利用EnableAutoConfigurationimportSelector给容器中导入一些组件
-
可以查看selectimports()方法的内容,他返回了一个autoConfigurationEnty,来自this.getAutoConfigurationEntry(autoConfigurationmetadata,annotationmetadata);这个方法我们继续来跟踪:
-
这个方法有一个值:List
configurations = getCandidateConfigurations(annotationmetadata, attributes);叫做获取候选的配置 ,我们点击继续跟踪 - SpringFactoriesLoader.loadFactoryNames()
- 扫描所有jar包类路径下meta-INF/spring.factories
- 把扫描到的这些文件的内容包装成properties对象
- 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中
-
在类路径下,meta-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到容器中:
-
-
- 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
-
每一个自动配置类进行自动配置功能;
-
我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件; @Configuration //启动指定类的ConfigurationProperties功能; //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来; //并把HttpProperties加入到ioc容器中 @EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解 //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效; //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnWebApplication( type = Type.SERVLET ) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass({CharacterEncodingFilter.class}) //判断配置文件中是否存在某个配置:spring.http.encoding.enabled; //如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。 }
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
-
一但这个配置类生效;这个配置类就会给容器中添加各种组件;
-
这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
-
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
-
配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // ..... }
我们去配置文件里面试试前缀,看提示!
这就是自动装配的原理!
6.2.总结-
SpringBoot启动会加载大量的自动配置类
-
我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
-
我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
-
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
| @Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
|---|---|
| @ConditionalOnJava | 系统的java版本是否符合要求 |
| @ConditionalOnJava | 容器中存在指定Bean ; |
| @ConditionalOnMissingBean | 容器中不存在指定Bean ; |
| @ConditionalOnexpression | 满足SpEL表达式指定 |
| @ConditionalOnClass | 系统中有指定的类 |
| @ConditionalOnMissingClass | 系统中没有指定的类 |
| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean ,或者这个Bean是首选Bean |
| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
| @ConditionalOnResource | 类路径下是否存在指定资源文件 |
| @ConditionalOnWebApplication | 当前是web环境 |
| @ConditionalOnNotWebApplication | 当前不是web环境 |
| @ConditionalOnJndi | JNDI存在指定项 |
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
6.4.自动配置类是否生效我们可以在application.properties通过启用 debug=true属性;
在控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类 debug=true
-
Positive matches:(自动配置类启用的:正匹配)
-
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
-
Unconditional classes: (没有条件的类)
我们分析完毕了源码以及自动装配的过程,我们可以尝试自定义一个启动器来玩玩!
6.5.1.说明启动器模块是一个 空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库;
命名归约:
官方命名:
- 前缀:spring-boot-starter-xxx
- 比如:spring-boot-starter-web…
自定义命名:
- xxx-spring-boot-starter
- 比如:mybatis-spring-boot-starter
-
在IDEA中新建一个空项目 spring-boot-starter-diy
-
新建一个普通Maven模块:kuang-spring-boot-starter
-
新建一个Springboot模块:kuang-spring-boot-starter-autoconfigure
-
点击apply即可,基本结构
-
在我们的 starter 中 导入 autoconfigure 的依赖!
com.kuang kuang-spring-boot-starter-autoconfigure 0.0.1-SNAPSHOT -
将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是所有的启动器基本配置!
-
我们编写一个自己的服务
package wyl.ss; public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHello(String name){ return helloProperties.getPrefix() + name + helloProperties.getSuffix(); } } -
编写HelloProperties 配置类
package wyl.ss; import org.springframework.boot.context.properties.ConfigurationProperties; // 前缀 kuang.hello @ConfigurationProperties(prefix = "kuang.hello") public class HelloProperties { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } } -
编写我们的自动配置类并注入bean,测试!
package wyl.ss; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnWebApplication //web应用生效 @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService(){ HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; } } -
在resources编写一个自己的 meta-INFspring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration= wyl.ss.HelloServiceAutoConfiguration
-
编写完成后,可以安装到maven仓库中!
-
新建一个SpringBoot 项目
-
导入我们自己写的启动器
wyl.ss ss-spring-boot-starter 1.0-SNAPSHOT -
编写一个 HelloController 进行测试我们自己的写的接口!
package wyl.ss.controller; @RestController public class HelloController { @Autowired HelloService helloService; @RequestMapping("/hello") public String hello(){ return helloService.sayHello("zxc"); } } -
编写配置文件 application.properties
ss.hello.prefix="ppp" ss.hello.suffix="sss"
-
启动项目进行测试,结果成功 !
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网
数据库相关的启动器 :可以参考官方文档
7.2.创建项目 7.2.1.新建项目测试引入相应的模块:Spring Web、SQL中的JDBC API、MySql Driver
项目建好之后,Spring Boot自动导入了启动器
7.2.2.编写yaml配置文件,连接数据库新建一个 application.yml
spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/数据库名称?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver7.2.3.测试
配置完这一些东西后就可以直接去使用了,SpringBoot已经默认进行了自动配置
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭连接
connection.close();
}
}
可以看到默认配置的数据源为 class com.zaxxer.hikari.HikariDataSource, 我们并没有手动配置
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀
可以使用 spring.datasource.type 指定自定义的数据源类型,值为使用的连接池实现的完全限定名
有了数据库连接,就可以 CRUD 操作数据库了。但需要先了解对象 JdbcTemplate
7.2.4.源码打开 DataSourceProperties 的源码,能配置的所有东西都在这
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName = true;
private Class extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
............
打开 DataSourceAutoConfiguration 源码,数据源的所有自动配置都在这里
7.2.5.JDBCTemplate- 有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库
- 即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
- 数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
- Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
- JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
测试
新建一个 controller 目录,在里面编写一个 JdbcController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
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;
import java.util.Date;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/userList")
public List
8.集成 druid
8.1.druid简介
-
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
-
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
-
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
-
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
-
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
-
Github地址:https://github.com/alibaba/druid/
加入druid相关配置(.yml配置文件)
spring:
#数据库配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
druid:
# 初始连接数
initial-size: 10
# 最大连接池数量
max-active: 100
# 最小连接池数量
min-idle: 10
# 配置获取连接等待超时的时间
max-wait: 60000
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid*.xml
true
编写部门的 UserController 进行测试!
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List queryUserList() {
List userList = userMapper.queryUserList();
for (User user : userList) {
System.out.println(user);
}
return userList;
}
//添加一个用户
@GetMapping("/addUser")
public String addUser() {
userMapper.addUser(new User(7,"wyl","123456"));
return "ok";
}
//修改一个用户
@GetMapping("/updateUser")
public String updateUser() {
userMapper.updateUser(new User(7,"wjm","123456"));
return "ok";
}
@GetMapping("/deleteUser")
public String deleteUser() {
userMapper.deleteUser(7);
return "ok";
}
}
启动项目访问进行测试!
9.5.分页 添加相关依赖首先,我们需要在 pom.xml 文件中添加分页插件依赖包。
pom.xml
添加相关配置com.github.pagehelper pagehelper-spring-boot-starter 1.2.5
然后在 application.yml 配置文件中添加分页插件有关的配置。
application.yml
# pagehelper
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
编写分页代码
首先,在 DAO 层添加一个分页查找方法。这个查询方法跟查询全部数据的方法除了名称几乎一样。
SysUserMapper.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
public interface SysUserMapper {
int deleteByPrimaryKey(Long id);
int insert(SysUser record);
int insertSelective(SysUser record);
SysUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(SysUser record);
int updateByPrimaryKey(SysUser record);
List selectAll();
List selectPage();
}
然后在 SysUserMapper.xml 中加入selectPage的实现,当然你也可以直接用@Select注解将查询语句直接写在DAO代码,但我们这里选择写在XML映射文件,这是一个普通的查找全部记录的查询语句,并不需要写分页SQL,分页插件会拦截查询请求,并读取前台传来的分页查询参数重新生成分页查询语句。
SysUserMapper.xml
服务层通过调用DAO层代码完成分页查询,这里统一封装分页查询的请求和结果类,从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会影响服务层以上的分页接口,起到了解耦的作用。
SysUserService.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
public interface SysUserService {
SysUser findByUserId(Long userId);
List findAll();
PageResult findPage(PageRequest pageRequest);
}
服务实现类通过调用分页插件完成最终的分页查询,关键代码是 PageHelper.startPage(pageNum, pageSize),将前台分页查询参数传入并拦截MyBtis执行实现分页效果。
SysUserServiceImpl.java
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.louis.springboot.demo.dao.SysUserMapper;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
import com.louis.springboot.demo.util.PageUtils;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser findByUserId(Long userId) {
return sysUserMapper.selectByPrimaryKey(userId);
}
@Override
public List findAll() {
return sysUserMapper.selectAll();
}
@Override
public PageResult findPage(PageRequest pageRequest) {
return PageUtils.getPageResult(pageRequest, getPageInfo(pageRequest));
}
private PageInfo getPageInfo(PageRequest pageRequest) {
int pageNum = pageRequest.getPageNum();
int pageSize = pageRequest.getPageSize();
PageHelper.startPage(pageNum, pageSize);
List sysMenus = sysUserMapper.selectPage();
return new PageInfo(sysMenus);
}
}
在控制器SysUserController中添加分页查询方法,并调用服务层的分页查询方法。
SysUserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
@RestController
@RequestMapping("user")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@GetMapping(value="/findByUserId")
public Object findByUserId(@RequestParam Long userId) {
return sysUserService.findByUserId(userId);
}
@GetMapping(value="/findAll")
public Object findAll() {
return sysUserService.findAll();
}
@PostMapping(value="/findPage")
public Object findPage(@RequestBody PageRequest pageQuery) {
return sysUserService.findPage(pageQuery);
}
}
分页查询请求封装类。
PageRequest.java
public class PageRequest {
private int pageNum;
private int pageSize;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
分页查询结果封装类。
PageResult.java
import java.util.List;
public class PageResult {
private int pageNum;
private int pageSize;
private long totalSize;
private int totalPages;
private List> content;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public List> getContent() {
return content;
}
public void setContent(List> content) {
this.content = content;
}
}
分页查询相关工具类。
PageUtils.java
import com.github.pagehelper.PageInfo;
public class PageUtils {
public static PageResult getPageResult(PageRequest pageRequest, PageInfo> pageInfo) {
PageResult pageResult = new PageResult();
pageResult.setPageNum(pageInfo.getPageNum());
pageResult.setPageSize(pageInfo.getPageSize());
pageResult.setTotalSize(pageInfo.getTotal());
pageResult.setTotalPages(pageInfo.getPages());
pageResult.setContent(pageInfo.getList());
return pageResult;
}
}
编译测试运行
启动应用,访问:localhost:8088/swagger-ui.html,找到对应接口,模拟测试,结果如下。
参数:pageNum: 1, pageSize: 5
10.事务管理SpringBoot 使用事务非常简单,底层依然采用的是Spring本身提供的事务管理
• 在入口类中使用注解 @EnableTransactionManagement 开启事务支持
• 在访问数据库的Service方法上添加注解 @Transactional 即可
案例思路
通过SpringBoot +MyBatis实现对数据库学生表的更新操作,在service层的方法中构建异常,查看事务是否生效;
项目名称:springboot–transacation
10.1.实现步骤 10.1.1.StudentController@Controller
public class SpringBootController {
@Autowired
private StudentService studentService;
@RequestMapping(value = "/springBoot/update")
public @ResponseBody Object update() {
Student student = new Student();
student.setId(1);
student.setName("Mark");
student.setAge(100);
int updateCount = studentService.update(student);
return updateCount;
}
}
10.1.2.StudentService接口
public interface StudentService {
int update(Student student);
}
10.1.3.StudentServiceImpl
接口实现类中对更新学生方法进行实现,并构建一个异常,同时在该方法上加@Transactional注解
@Override
@Transactional //添加此注解说明该方法添加的事务管理
public int update(Student student) {
int updateCount = studentMapper.updateByPrimaryKeySelective(student);
System.out.println("更新结果:" + updateCount);
//在此构造一个除数为0的异常,测试事务是否起作用
int a = 10/0;
return updateCount;
}
10.1.4.Application
在类上加@EnableTransactionManagement开启事务支持
@EnableTransactionManagement可选,但是@Service必须添加事务才生效
@SpringBootApplication
@EnableTransactionManagement //SpringBoot开启事务的支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
10.1.5.启动Application
1.查看数据库,并没有修改数据,通过以上结果,说明事务起作用了。
2.注释掉StudentServiceImpl上的@Transactional测试----数据库发上来改变。
11.SpringMVC注解SpringBoot下的SpringMVC和之前的SpringMVC使用是完全一样的,主要有以下注解:
1.@Controller
Spring MVC的注解,处理http请求
2.@RestController
Spring4后新增注解,是@Controller注解功能的增强,是@Controller与@ResponseBody的组合注解;
如果一个Controller类添加了@RestController,那么该Controller类下的所有方法都相当于添加了@ResponseBody注解;
用于返回字符串或json数据。
案例:
• 创建MyUserController类,演示@RestController替代@Controller + @ResponseBody
@RestController
public class MyUserController {
@Autowired
private UserService userService;
@RequestMapping("/user/getUser")
public Object getUser(){
return userService.getUser(1);
}
}
3.@RequestMapping(常用)
支持Get请求,也支持Post请求
4.@GetMapping
RequestMapping和Get请求方法的组合只支持Get请求;Get请求主要用于查询操作。
5.@PostMapping
RequestMapping和Post请求方法的组合只支持Post请求;Post请求主要用户新增数据。
6.@PutMapping
RequestMapping和Put请求方法的组合只支持Put请求;Put通常用于修改数据。
7.@DeleteMapping
RequestMapping 和 Delete请求方法的组合只支持Delete请求;通常用于删除数据。
12.RESTful实现Spring boot开发RESTFul 主要是几个注解实现:
@PathVariable:获取url中的数据,该注解是实现RESTFul最主要的一个注解。
@PostMapping:接收和处理Post方式的请求
@DeleteMapping:接收delete方式的请求,可以使用GetMapping代替
@PutMapping:接收put方式的请求,可以用PostMapping代替
@GetMapping:接收get方式的请求
RESTful的优点
• 轻量,直接基于http,不再需要任何别的诸如消息协议,get/post/put/delete为CRUD操作
• 面向资源,一目了然,具有自解释性。
• 数据描述简单,一般以xml,json做数据交换。
• 无状态,在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了复杂度。
• 简单、低耦合
12.1.案例使用RESTful风格模拟实现对学生的增删改查操作
该项目集成了MyBatis、spring、SpringMVC,通过模拟实现对学生的增删改查操作。
1.创建RESTfulController,并编写代码
@RestController
public class RESTfulController {
@PostMapping(value = "/springBoot/student/{name}/{age}")
public Object addStudent(@PathVariable("name") String name,
@PathVariable("age") Integer age) {
Map retMap = new HashMap();
retMap.put("name",name);
retMap.put("age",age);
return retMap;
}
@DeleteMapping(value = "/springBoot/student/{id}")
public Object removeStudent(@PathVariable("id") Integer id) {
return "删除的学生id为:" + id;
}
@PutMapping(value = "/springBoot/student/{id}")
public Object modifyStudent(@PathVariable("id") Integer id) {
return "修改学生的id为" + id;
}
@GetMapping(value = "/springBoot/student/{id}")
public Object queryStudent(@PathVariable("id") Integer id) {
return "查询学生的id为" + id;
}
}
12.2.RESTful原则
• 增post请求、删delete请求、改put请求、查get请求
• 请求路径不要出现动词
例如:查询订单接口
/boot/order/1021/1(推荐)
/boot/queryOrder/1021/1(不推荐)
• 分页、排序等操作,不需要使用斜杠传参数
例如:订单列表接口 /boot/orders?page=1&sort=desc
一般传的参数不是数据库表的字段,可以不采用斜杠
13.静态资源处理 13.1.对哪些目录映射?classpath:/meta-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ /:当前项目的根路径
就我们在上面五个目录下放静态资源(比如:a.png等),可以直接访问(http://localhost:8080/a.png),类似于以前web项目的webapp下;放到其他目录下无法被访问。
优先级:resources > static(默认) > public
13.2.源码分析SpringBoot自动配置的WebMvcAutoConfiguration.java类
-
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
-
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
-
有一个方法:addResourceHandlers 添加资源处理
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { // 已禁用默认资源处理 logger.debug("Default resource handling disabled"); return; } // 缓存控制 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // webjars 配置 if (!registry.hasMappingForPattern("/webjars @Api(value = "desc of class") @RestController public class HelloController { @ApiOperation(value = "desc of method", notes = "") @GetMapping(value="/hello") public Object hello( @ApiParam(value = "desc of param" , required=true ) @RequestParam String name) { return "Hello " + name + "!"; } }运行即可
15.4.常用注解说明swagger 通过注解接口生成文档,包括接口名,请求方法,参数,返回信息等。
@Api: 修饰整个类,用于controller类上
@ApiOperation: 描述一个接口,用户controller方法上
@ApiParam: 单个参数描述
@ApiModel: 用来对象接收参数,即返回对象
@ApiModelProperty: 对象接收参数时,描述对象的字段
@ApiResponse: Http响应其中的描述,在ApiResonse中
@ApiResponses: Http响应所有的描述,用在
@ApiIgnore: 忽略这个API
@ApiError: 发生错误的返回信息
@ApiImplicitParam: 一个请求参数
@ApiImplicitParam: 多个请求参数
更多使用说明,参考 Swagger 使用手册。
15.5.添加请求参数在很多时候,我们需要在调用我们每一个接口的时候都携带上一些通用参数,比如采取token验证逻辑的往往在接口请求时需要把token也一起传入后台,接下来,我们就来讲解一下如何给Swagger添加固定的请求参数。
修改SwaggerConfig配置类,替换成如下内容,利用ParameterBuilder构成请求参数。
SwaggerConfig.java
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ // 添加请求参数,我们这里把token作为请求头部参数传入后端 ParameterBuilder parameterBuilder = new ParameterBuilder(); Listparameters = new ArrayList (); parameterBuilder.name("token").description("令牌") .modelRef(new ModelRef("string")).parameterType("header").required(false).build(); parameters.add(parameterBuilder.build()); return new Docket(documentationType.SWAGGER_2).apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()) .build().globalOperationParameters(parameters); // return new Docket(documentationType.SWAGGER_2).apiInfo(apiInfo()) // .select() // .apis(RequestHandlerSelectors.any()) // .paths(PathSelectors.any()).build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("Swagger API Doc") .description("This is a restful api document of Swagger.") .version("1.0") .build(); } } 完成之后重新启动应用,再次查看hello接口,可以看到已经支持发送token请求参数了。
15.6.配置API分组如果没有配置分组,默认是default。通过groupName()方法即可配置分组
@Bean public Docket docket(Environment environment) { return new Docket(documentationType.SWAGGER_2).apiInfo(apiInfo()) .groupName("group1") // 配置分组 .... }如何配置多个分组?配置多个分组只需要配置多个docket即可:
@Bean public Docket docket1(){ return new Docket(documentationType.SWAGGER_2).groupName("group1"); } @Bean public Docket docket2(){ return new Docket(documentationType.SWAGGER_2).groupName("group2"); } @Bean public Docket docket3(){ return new Docket(documentationType.SWAGGER_2).groupName("group3"); }16.拦截器在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
-
定义拦截器;
-
注册拦截器;
-
指定拦截规则(如果是拦截所有,静态资源也会被拦截)。
在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。
HandlerInterceptor 接口中定义以下 3 个方法,如下表。
返回值类型 方法声明 描述 boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。 void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { //未登录,返回登陆页 request.setAttribute("msg", "您没有权限进行此操作,请先登陆!"); request.getRequestDispatcher("/index.html").forward(request, response); return false; } else { //放行 return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle执行{}", modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("afterCompletion执行异常{}", ex); } }16.2.注册拦截器创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。
在配置类 MyMvcConfig 中,添加以下方法注册拦截器,代码如下。
@Configuration public class MyMvcConfig implements WebMvcConfigurer { ...... @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()); } }16.3.指定拦截规则在使用 registry.addInterceptor() 方法将拦截器注册到容器中后,我们便可以继续指定拦截器的拦截规则了,代码如下
@Slf4j @Configuration public class MyConfig implements WebMvcConfigurer { ...... @Override public void addInterceptors(InterceptorRegistry registry) { log.info("注册拦截器"); registry.addInterceptor(new LoginInterceptor()).addPathPatterns(" @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { //获取错误状态码 HttpStatus status = getStatus(request); //getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示 Mapmodel = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); //为响应对象设置错误状态码 response.setStatus(status.value()); //调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容) ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity > error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } ...... } Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。
返回值类型 方法声明 客户端类型 错误信息返类型 ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) 浏览器客户端 text/html(错误页面) ResponseEntity > error(HttpServletRequest request) 机器客户端(例如安卓、IOS、Postman 等等) JSON 换句话说,当使用浏览器访问出现异常时,会进入 BasicErrorController 控制器中的 errorHtml() 方法进行处理,当使用安卓、IOS、Postman 等机器客户端访问出现异常时,就进入error() 方法处理。
在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法,代码如下。
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Mapmodel) { //获取容器中的所有的错误视图解析器来处理该异常信息 for (ErrorViewResolver resolver : this.errorViewResolvers) { //调用错误视图解析器的 resolveErrorView 解析到错误视图页面 ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; } 从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。
DefaultErrorViewResolverErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 DefaultErrorViewResolver,代码如下。
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); }当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调用它们的 resolveErrorView() 方法对异常信息进行解析,其中自然也包括 DefaultErrorViewResolver(默认错误信息解析器)。
DefaultErrorViewResolver 的部分代码如下。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final MapSERIES_VIEWS; static { Map views = new EnumMap<>(HttpStatus.Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); } ...... @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) { //尝试以错误状态码作为错误页面名进行解析 ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { //尝试以 4xx 或 5xx 作为错误页面页面进行解析 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map model) { //错误模板页面,例如 error/404、error/4xx、error/500、error/5xx String errorViewName = "error/" + viewName; //当模板引擎可以解析这些模板页面时,就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { //在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图 return new ModelAndView(errorViewName, model); } //若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面 return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map model) { //遍历所有静态资源文件夹 for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); //静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html resource = resource.createRelative(viewName + ".html"); //若静态资源文件夹下存在以上错误页面,则直接返回 if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; } ...... } DefaultErrorViewResolver 解析异常信息的步骤如下:
- 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。
- 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。
- 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到第 4 步。
- 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。
- 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。
- 处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)。
ErrorMvcAutoConfiguration 还向容器中注入了一个组件默认错误属性处理工具 DefaultErrorAttributes,代码如下。
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理工具,它可以从请求中获取异常或错误信息,并将其封装为一个 Map 对象返回,其部分代码如下。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { ...... @Override public MapgetErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE)); if (!options.isIncluded(Include.EXCEPTION)) { errorAttributes.remove("exception"); } if (!options.isIncluded(Include.STACK_TRACE)) { errorAttributes.remove("trace"); } if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) { errorAttributes.remove("message"); } if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } return errorAttributes; } private Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map errorAttributes = new linkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, webRequest); addErrorDetails(errorAttributes, webRequest, includeStackTrace); addPath(errorAttributes, webRequest); return errorAttributes; } ...... } 在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:
- timestamp:时间戳;
- status:错误状态码
- error:错误的提示
- exception:导致请求处理失败的异常对象
- message:错误/异常消息
- trace: 错误/异常栈信息
- path:错误/异常抛出时所请求的URL路径
我们知道 Spring Boot 已经提供了一套默认的异常处理机制,但是 Spring Boot 提供的默认异常处理机制却并不一定适合我们实际的业务场景,因此,我们通常会根据自身的需要对 Spring Boot 全局异常进行统一定制,例如定制错误页面,定制错误数据等。
定制错误页面我们可以通过以下 3 种方式定制 Spring Boot 错误页面:
- 自定义 error.html
- 自定义动态错误页面
- 自定义静态错误页面
我们可以直接在模板引擎文件夹(/resources/templates)下创建 error.html ,覆盖 Spring Boot 默认的错误视图页面(Whitelabel Error Page)。
自定义 error.html 自定义 error.html
status:
error:
timestamp:
message:
path:
如果 Sprng Boot 项目使用了模板引擎,当程序发生异常时,Spring Boot 的默认错误视图解析器(DefaultErrorViewResolver)就会解析模板引擎文件夹(resources/templates/)下 error 目录中的错误视图页面。
精确匹配我们可以根据错误状态码(例如 404、500、400 等等)的不同,分别创建不同的动态错误页面(例如 404.html、500.html、400.html 等等),并将它们存放在模板引擎文件夹下的 error 目录中。当发生异常时,Spring Boot 会根据其错误状态码精确匹配到对应的错误页面上。
匹配自定义动态错误页面 404.html
status:
error:
timestamp:
message:
path:
我们还可以使用 4xx.html 和 5xx.html 作为动态错误页面的文件名,并将它们存放在模板引擎文件夹下的 error 目录中,来模糊匹配对应类型的所有错误,例如 404、400 等错误状态码以“4”开头的所有异常,都会解析到动态错误页面 4xx.html 上。
自定义静态错误页面自定义动态错误页面 4xx.html
status:
error:
timestamp:
message:
path:
若 Sprng Boot 项目没有使用模板引擎,当程序发生异常时,Spring Boot 的默认错误视图解析器(DefaultErrorViewResolver)则会解析静态资源文件夹下 error 目录中的静态错误页面。
精确匹配我们可以根据错误状态码(例如 404、500、400 等等)的不同,分别创建不同的静态错误页面(例如 404.html、500.html、400.html 等等),并将它们存放在静态资源文件夹下的 error 目录中。当发生异常时,Spring Boot 会根据错误状态码精确匹配到对应的错误页面上。
模糊匹配自定义静态错误页面 404.html
status:
error:
timestamp:
message:
path:
我们还可以使用 4xx.html 和 5xx.html 作为静态错误页面的文件名,并将它们存放在静态资源文件夹下的 error 目录中,来模糊匹配对应类型的所有错误,例如 404、400 等错误状态码以“4”开头的所有错误,都会解析到静态错误页面 4xx.html 上。
错误页面优先级自定义静态错误页面 4xx.html
status:
error:
timestamp:
message:
path:
以上 5 种方式均可以定制 Spring Boot 错误页面,且它们的优先级顺序为:自定义动态错误页面(精确匹配)>自定义静态错误页面(精确匹配)>自定义动态错误页面(模糊匹配)>自定义静态错误页面(模糊匹配)>自定义 error.html。
定制错误数据当遇到错误时,Spring Boot 会按照优先级由高到低,依次查找解析错误页,一旦找到可用的错误页面,则直接返回客户端展示。
我们知道,Spring Boot 提供了一套默认的异常处理机制,其主要流程如下:
- 发生异常时,将请求转发到“/error”,交由 BasicErrorController(Spring Boot 默认的 Error 控制器) 进行处理;
- BasicErrorController 根据客户端的不同,自动适配返回的响应形式,浏览器客户端返回错误页面,机器客户端返回 JSON 数据。
- BasicErrorController 处理异常时,会调用 DefaultErrorAttributes(默认的错误属性处理工具) 的 getErrorAttributes() 方法获取错误数据。
我们还可以定制 Spring Boot 的错误数据,具体步骤如下。
- 自定义异常处理类,将请求转发到 “/error”,交由 Spring Boot 底层(BasicErrorController)进行处理,自动适配浏览器客户端和机器客户端。
- 通过继承 DefaultErrorAttributes 来定义一个错误属性处理工具,并在原来的基础上添加自定义的错误数据。
被 @ControllerAdvice 注解的类可以用来实现全局异常处理,这是 Spring MVC 中提供的功能,在 Spring Boot 中可以直接使用。
创建一个名为 UserNotExistException 的异常类,代码如下
public class UserNotExistException extends RuntimeException { public UserNotExistException() { super("用户不存在!"); } }在 IndexController 添加以下方法,触发 UserNotExistException 异常,代码如下
@Controller public class IndexController { ...... @GetMapping(value = {"/testException"}) public String testException(String user) { if ("user".equals(user)) { throw new UserNotExistException(); } //跳转到登录页 login.html return "login"; } }创建一个名为 MyExceptionHandler 异常处理类,代码如下
import com.wyl.Exception.UserNotExistException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request) { Map2. 自定义错误属性处理工具map = new HashMap<>(); //向 request 对象传入错误状态码 request.setAttribute("javax.servlet.error.status_code",500); //根据当前处理的异常,自定义的错误数据 map.put("code", "user.notexist"); map.put("message", e.getMessage()); //将自定的错误数据传入 request 域中 request.setAttribute("ext",map); return "forward:/error"; } } 1)在 net.biancheng.www.componet 包内,创建一个错误属性处理工具类 MyErrorAttributes(继承 DefaultErrorAttributes ),通过该类我们便可以添加自定义的错误数据,代码如下。
import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.util.Map; //向容器中添加自定义的储物属性处理工具 @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public MapgetErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map errorAttributes = super.getErrorAttributes(webRequest, options); //添加自定义的错误数据 errorAttributes.put("company", "www.biancheng.net"); //获取 MyExceptionHandler 传入 request 域中的错误数据 Map ext = (Map) webRequest.getAttribute("ext", 0); errorAttributes.put("ext", ext); return errorAttributes; } } 在 templates/error 目录下,创建动态错误页面 5xx.html,代码如下。
自定义 error.html status:
error:
timestamp:
message:
path:
以下为定制错误数据:
company:
code:
path:
-



