栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

spring boot快速入门

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spring boot快速入门

1.什么是SpringBoot
  • 一个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来直接运行。

2.3.编写一个http接口

1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到

![image-20211108111755801](https://gitee.com/wyl1924/cdn/raw/master/img/blog/image-20211108111755801.png

2.4.将项目打成jar包

点击 maven的 package,等待生成。

如果测试用例影响到打包,可以跳过


    org.apache.maven.plugins
    maven-surefire-plugin
    
        
        true
    

打成了jar包后,就可以在任何地方运行了

2.5.端口

有没有看到我的端口被占用了

2.6.生成项目模板

为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。

  1. 打开浏览器,访问:https://start.spring.io/

  2. 根据页面提示,选择构建工具,开发语言,项目信息等。

  3. 点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。

3.运行原理探究

我们之前写的HelloSpringBoot,到底是怎么运行的呢,我们从pom.xml文件探究起;

3.1.父依赖

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中!
  • 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,就因为有这些版本仓库

1、其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.6
        
    

2、点进去,发现还有一个父依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zKvnCc5u-1636425800343)(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

3.3.主程序 3.3.1.默认的主启动类
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应用;

  • 进入这个注解:可以看到上面还有很多其他注解!

@ComponentScan
  • 这个注解在Spring中很重要 ,它对应XML配置中的元素。

  • 作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

@SpringBootConfiguration
  • 作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

  • 我们继续进去这个注解查看

  • 这里的 @Configuration,说明这是一个spring的配置类 ,配置类就是对应Spring的xml 配置文件;

  • @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

  • 我们回到 SpringBootApplication 注解中继续看。

@EnableAutoConfiguration
  • 开启自动配置功能

    • 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
    • @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

    点进注解接续查看:

  • @AutoConfigurationPackage :自动配置包

    • @import :Spring底层注解@import , 给容器中导入一个组件

    • Registrar.class 作用:自动配置包注册,将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

    • 这个分析完了,退到上一步,继续看

  • @import({AutoConfigurationimportSelector.class}) :给容器导入组件 ;

    • AutoConfigurationimportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
// 获取所有的配置
List configurations = 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

3.3.3.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.结论:
  1. SpringBoot在启动的时候从类路径下的meta-INF/spring.factories中获取EnableAutoConfiguration指定的值

  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;

  3. 以前我们需要自动配置的东西,现在springboot帮我们做了

  4. 整合JavaEE,整体解决方案和自动配置的东西都在springboot-autoconfigure的jar包中;

  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器中

  6. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并自动配置,@Configuration(javaConfig) ;

  7. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

3.4启动
@SpringBootApplication
public class Springboot01HellowordApplication {

    public static void main(String[] args) {
       	//该方法返回一个ConfigurableApplicationContext对象
 		//参数一:应用入口的类; 参数二:命令行参数  
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }

}

SpringApplication.run分析

  • 分析该方法主要分两部分
  • 一是SpringApplication的实例化,
  • 二是run方法的执行;
3.4.1.SpringApplication

这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是Web项目

  2. 查找并加载所有可用初始化器 , 设置到initializers属性中

  3. 找出所有的应用程序监听器,设置到listeners属性中

  4. 推断并设置main方法的定义类,找到运行的主类

查看构造器:

3.4.2.run方法流程分析

4.yaml语法学习 4.1.配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties

    • 语法结构 :key=value
  • application.yaml

    • 语法结构 :key:空格 value

**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

server:
  port: 8081
4.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
      
yaml基础语法

说明:语法要求严格!

  1. 空格不能省略

  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

  3. 属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

  • 字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;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: 8082
4.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 lists;
    private Dog dog;
    
    //有参无参构造、get、set方法、toString()方法  
}
 

⑥ 使用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 lists;
    private Dog dog;
}
 

⑧ 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:默认从全局配置文件中获取值

  1. 在resources目录下新建一个person.properties文件
name=hello
  1. 在代码中指定加载person.properties文件
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
    @Value("${name}")
    private String name;
    ......
}
  1. 再次输出测试,指定配置文件绑定成功
4.3.3.配置文件占位符

配置文件还可以编写占位符生成随机数

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 中配置

测试步骤

  1. 新建一个实体类User
@Component //注册bean
public class User {
    private String name;
    private int age;
    private String sex;
}
  1. 编辑配置文件 user.properties
user1.name=wyl
user1.age=18
user1.sex=男
  1. 在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;
}
  1. Springboot测试
SpringBootTest
class DemoApplicationTests {

    @Autowired
    User user;

    @Test
    public void contextLoads() {
        System.out.println(user);
    }

}

结果正常输出

4.3.4.对比小结

@Value使用起来并不友好!我们需要为每个属性单独注解赋值比较麻烦

  1. @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
  2. 松散绑定:这个什么意思呢? 比如yml中写的last-name,这个和lastName是一样的,- 后面跟着的字母默认是大写的。这就是松散绑定
  3. JSR303数据校验 ,可以在字段是增加一层过滤器验证 , 保证数据的合法性
  4. 复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

  1. 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
  2. 如果在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  3. 如果专门编写了一个JavaBean来和配置文件进行一一映射,就直接使用@configurationProperties
5.JSR303数据校验

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=dev
5.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会从这四个位置全部加载主配置文件;互补配置

4.4.4.运维小技巧

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;

这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
6.自动配置原理

----联系---- spring.factories

SpringBoot官方文档中有大量的配置,我们无法全部记住,官网:https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-application-properties.html#core-properties

6.1.分析自动配置原理
  1. SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration

  2. @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类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
  1. 每一个自动配置类进行自动配置功能;

  2. 我们以**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.总结
  1. SpringBoot启动会加载大量的自动配置类

  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

    **xxxxAutoConfigurartion:自动配置类;**给容器中添加组件

    xxxxProperties:封装配置文件中相关属性;

6.3.@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@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环境
@ConditionalOnJndiJNDI存在指定项

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

6.4.自动配置类是否生效

我们可以在application.properties通过启用 debug=true属性;

在控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true 
  • Positive matches:(自动配置类启用的:正匹配)

  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

  • Unconditional classes: (没有条件的类)

6.5.自定义Starter

我们分析完毕了源码以及自动装配的过程,我们可以尝试自定义一个启动器来玩玩!

6.5.1.说明

启动器模块是一个 空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库;

命名归约:

官方命名:

  • 前缀:spring-boot-starter-xxx
  • 比如:spring-boot-starter-web…

自定义命名:

  • xxx-spring-boot-starter
  • 比如:mybatis-spring-boot-starter
6.5.2.编写启动器
  1. 在IDEA中新建一个空项目 spring-boot-starter-diy

  2. 新建一个普通Maven模块:kuang-spring-boot-starter

  3. 新建一个Springboot模块:kuang-spring-boot-starter-autoconfigure

  4. 点击apply即可,基本结构

  5. 在我们的 starter 中 导入 autoconfigure 的依赖!

    
        
        
            com.kuang
            kuang-spring-boot-starter-autoconfigure
            0.0.1-SNAPSHOT
        
    
    
  6. 将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是所有的启动器基本配置!

  7. 我们编写一个自己的服务

    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();
        }
    
    }
    
  8. 编写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;
        }
    }
    
  9. 编写我们的自动配置类并注入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;
        }
    
    }
    
  10. 在resources编写一个自己的 meta-INFspring.factories

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    wyl.ss.HelloServiceAutoConfiguration
    
  11. 编写完成后,可以安装到maven仓库中!

6.5.3.新建项目测试我们自己写的启动器
  1. 新建一个SpringBoot 项目

  2. 导入我们自己写的启动器

    
        wyl.ss
        ss-spring-boot-starter
        1.0-SNAPSHOT
    
    
  3. 编写一个 HelloController 进行测试我们自己的写的接口!

    package wyl.ss.controller;
    
    @RestController
    public class HelloController {
    
        @Autowired
        HelloService helloService;
    
        @RequestMapping("/hello")
        public String hello(){
            return helloService.sayHello("zxc");
        }
    
    }
    
  4. 编写配置文件 application.properties

    ss.hello.prefix="ppp"
    ss.hello.suffix="sss"
    
  5. 启动项目进行测试,结果成功 !

7.整合JDBC 7.1.SpringData简介

对于数据访问层,无论是 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.Driver
7.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 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> userList(){
        String sql = "select * from user";
        List> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    //新增一个用户
    @GetMapping("/add")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into mybatis.user(id, name,pwd)" +
            " values (9,'Java编程思想','qqwweer987')";
        jdbcTemplate.update(sql);
        //查询
        return "addOk";
    }
    //修改用户信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update mybatis.user set name=?,pwd=? where id="+id;
        //数据封装
        Object[] objects = new Object[2];
        objects[0] = "大威天龙";
        objects[1] = "qwert123";
        jdbcTemplate.update(sql,objects);
        //查询
        return "update-Ok";
    }
    //删除用户
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from mybatis.user where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "delete-Ok";
    }
}
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/

8.2.配置

加入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();  
              List parameters = 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 步:

      1. 定义拦截器;

      2. 注册拦截器;

      3. 指定拦截规则(如果是拦截所有,静态资源也会被拦截)。

      16.1.定义拦截器

      在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。

      HandlerInterceptor 接口中定义以下 3 个方法,如下表。

      返回值类型方法声明描述
      booleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
      voidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
      voidafterCompletion(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 数据,用于页面显示
              Map model = 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() 方法进行处理。

      返回值类型方法声明客户端类型错误信息返类型
      ModelAndViewerrorHtml(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,
                                              Map model) {
          //获取容器中的所有的错误视图解析器来处理该异常信息
          for (ErrorViewResolver resolver : this.errorViewResolvers) {
              //调用错误视图解析器的 resolveErrorView 解析到错误视图页面
              ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
              if (modelAndView != null) {
                  return modelAndView;
              }
          }
          return null;
      }
      

      从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。

      DefaultErrorViewResolver

      ErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 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 Map SERIES_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 解析异常信息的步骤如下:

      1. 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。
      2. 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。
      3. 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到第 4 步。
      4. 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。
      5. 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。
      6. 处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)。
      DefaultErrorAttributes

      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 Map getErrorAttributes(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路径
      18.全局异常处理

      我们知道 Spring Boot 已经提供了一套默认的异常处理机制,但是 Spring Boot 提供的默认异常处理机制却并不一定适合我们实际的业务场景,因此,我们通常会根据自身的需要对 Spring Boot 全局异常进行统一定制,例如定制错误页面,定制错误数据等。

      定制错误页面

      我们可以通过以下 3 种方式定制 Spring Boot 错误页面:

      • 自定义 error.html
      • 自定义动态错误页面
      • 自定义静态错误页面
      自定义 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 提供了一套默认的异常处理机制,其主要流程如下:

      1. 发生异常时,将请求转发到“/error”,交由 BasicErrorController(Spring Boot 默认的 Error 控制器) 进行处理;
      2. BasicErrorController 根据客户端的不同,自动适配返回的响应形式,浏览器客户端返回错误页面,机器客户端返回 JSON 数据。
      3. BasicErrorController 处理异常时,会调用 DefaultErrorAttributes(默认的错误属性处理工具) 的 getErrorAttributes() 方法获取错误数据。

      我们还可以定制 Spring Boot 的错误数据,具体步骤如下。

      1. 自定义异常处理类,将请求转发到 “/error”,交由 Spring Boot 底层(BasicErrorController)进行处理,自动适配浏览器客户端和机器客户端。
      2. 通过继承 DefaultErrorAttributes 来定义一个错误属性处理工具,并在原来的基础上添加自定义的错误数据。
      1. 自定义异常处理类

      被 @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) {
              Map 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";
          }
      }
      
      2. 自定义错误属性处理工具

      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 Map getErrorAttributes(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:

      转载请注明:文章转载自 www.mshxw.com
      我们一直用心在做
      关于我们 文章归档 网站地图 联系我们

      版权所有 (c)2021-2022 MSHXW.COM

      ICP备案号:晋ICP备2021003244-6号