SpringBoot学习笔记

文章目录

  • 1 SpringBoot简介
    • 1.1 回顾什么是Spring
    • 1.2 Spring是如何简化java开发的呢?
    • 1.3 什么是SpringBoot
    • 1.4 微服务架构
  • 2 Hello, World
    • 2.1 环境配置
    • 2.2 创建基础项目说明
    • 2.3 pom.xml分析
    • 2.4 编写一个http接口
    • 2.5 将项目打成jar包,点击maven的package
    • 2.6 自定义banner图案
    • 2.7 SpringBoot
      • 2.7.1 依赖管理
      • 2.7.2 自动配置
    • 2.8 容器功能
      • 2.8.1 组件添加
      • 2.8.2 原生配置文件引入
      • 2.8.3 配置绑定
  • 3 自动配置原理初探
      • 3.1 pom.xml
        • 3.1.1 父依赖
        • 3.1.2 启动器 spring-boot-starter
      • 3.2 主启动类
        • 3.2.1 默认的主启动类
        • 3.2.2 @SpringBootApplication
        • 3.2.3 @ComponentScan
        • 3.2.4 @SpringBootConfiguration
        • 3.2.5 @EnableAutoConfiguration
        • 3.2.6 spring.factories
        • 3.2.7 自动配置生效
        • 3.2.8 结论
      • 3.3 **SpringApplication**
        • 3.3.1 不简单的方法
        • 3.3.2 SpringApplication
        • 3.3.3 run方法流程分析
  • 4 SpringBoot配置文件
    • 4.1 配置文件
    • 4.2 yaml概述
    • 4.3 yaml注入配置文件
      • 4.3.1 原始的给实体类对象赋值
      • 4.3.2 通过yaml文件赋值
    • 4.4 加载指定的配置文件
    • 4.5 配置文件占位符
    • 4.6 回顾properties配置
    • 4.7 Properties和Yaml的对比小结
  • 5 JSR303数据校检及多环境切换
    • 5.1 JSR303
      • 5.1.1 @Validated使用
      • 5.1.2 @Validated常见参数
    • 5.2 多环境切换
      • 5.2.1 多配置文件
      • 5.2.2 yaml的多文档块
    • 5.3 配置文件加载位置
    • 5.4 拓展,运维小技巧
  • 6 自动配置原理
    • 6.1分析自动配置原理
    • 6.2 精髓
    • 6.3 了解:@Conditional
  • 7 自定义starter启动器
    • 7.1 说明
    • 7.2 编写启动器
    • 7.3 新建项目测试我们自己写的启动器
  • 8 SpringBoot Web开发总览
    • 8.1 Web开发静态资源处理
    • 8.2 首页处理
  • 9 Thymeleaf模板引擎
    • 9.1 模板引擎
    • 9.2 引入Thymeleaf
    • 9.3 Thymeleaf分析
    • 9.4 Thymeleaf 语法学习
  • 10 SpringMVC自动配置原理
    • 10.1 自动配置原理
    • 10.2 转换器和格式化器
    • 10.3 修改SpringBoot的默认配置
    • 10.4 全面接管SpringMVC
  • 11 聊一聊怎么写一个网页
  • 12 SpringBoot整合数据库操作
    • 12.1 整合JDBC
      • 12.1.1 SpringData简介
      • 12.1.2 整合JDBC
      • 12.1.3 JdbcTemplate
      • 12.1.4 测试
    • 12.2 整合Druid数据源
      • 12.2.1 Druid简介
      • 12.2.2 配置数据源
      • 12.2.3 配置Druid数据源监控
      • 12.2.4 配置 Druid web 监控 filter 过滤器
    • 12.3 整合 Mybatis
      • 12.3.1 整合测试
  • 13 SpringSecurity(安全)
    • 13.1 认识SpringSecurity
    • 13.2 实战使用

1 SpringBoot简介

1.1 回顾什么是Spring

Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架,作者:Rod Johnson。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

1.2 Spring是如何简化java开发的呢?

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

  1. 基于POJO的轻量级和低侵入式编程,所有东西都是bean;
  2. 通过控制反转(IOC),依赖注入(DI)和面向接口实现低耦合;
  3. 基于切面(AOP)和惯例进行声明式编程;
  4. 通过切面和模板减少样式代码,如RedisTemplate,xxxTemplate;

1.3 什么是SpringBoot

学过javaweb的同学就知道,开发一个web应用,从最初接触到Servlet结合Tomcat,跑出一个Hello World程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍。

言归正传,什么是SpringBoot呢?其实它是一个Javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,采用“约定优于配置”的思想,you can “just run”,能迅速开发web应用,几行代码开发一个http接口。

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能就成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定优于配置”,进而衍生出一些一站式的解决方案。

是的,这就是Java企业级应用 --> J2EE–> Spring --> SpringBoot的过程。

随着Spring框架的不断发展,涉及的领域越来越多,项目整合开发需要配置各种各样的文件,慢慢变得不那么易用简单,违背了最初的设计理念,甚至被人称之为"配置地狱"。Spring Boot正是在这样的一个背景下被抽象出来的开发框架,目的是为了让大家更容易地使用Spring、更容易地集成各种常用的中间件、开源软件;

Spring Boot是基于Spring开发的,Spring 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的主要优点:

  1. 为所有Spring开发者更快的入门
  2. 开箱即用,提供各种默认配置来简化项目配置
  3. 内嵌式容器简化Web项目
  4. 没有冗余代码生成和XML配置的要求

​ SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。

1.4 微服务架构

微服务架构是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须建成一系列小型服务的组合,可以通过http的方式进行通信。

所谓的微服务架构,就是要打破之前all in one的架构方式,把每个功能元素独立出来,吧独立出来的功能元素进行动态组合,需要的功能元素才拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行赋值,而没有对整个应用进行赋值。这样做的好处是:

  • 节省了调用资源
  • 每个功能元素的服务都是一个可替换的,可独立升级的软件代码

程序核心要素:

  1. 高内聚(在划分模块时,要把功能关系紧密的放在一个模块中)
  2. 低耦合(模块之间的联系越少越好,接口越简单越好)

微服务理论最早由Martin Fowler等人提出,论文地址:微服务架构

SpringBoot学习笔记_第1张图片

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
  • 大型分布式网络服务的调用,这部分通过springcloud来完成,实现分布式
  • 在分布式中间,进行流式数据计算,批处理,可以使用spring cloud data flow
  • spring为我们想清楚了整个应用从开始构建到大型分布式应用全流程解决方案

SpringBoot学习笔记_第2张图片

2 Hello, World

2.1 环境配置

我们将学习如何快速地创建一个Spring Boot应用,并且实现一个简单的http请求处理,通过这个例子,可以对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

环境准备:

  • java version “1.8.0_311”
  • maven 3.8.1
  • SpringBoot 2.6.x 最新版

开发工具:

  • IDEA

2.2 创建基础项目说明

Spring官方提供了非常方便的使用工具,可以让我们快速构建应用。IDEA也集成了这个网站。

SpringBoot学习笔记_第3张图片

Spring Initializr: https://start.spring.io/

**项目创建方式一:**使用 Spring Initializr 的Web页面创建项目

  1. 打开 https://start.spring.io/
  2. 填写项目信息
  3. 点击"Genernate Project"按钮生成项目,下载此项目
  4. 解压项目包,并使用IDEA以maven项目导入,一路下一步即可,直到项目导入完毕
  5. 如果是第一次使用,可能速度会比较慢,包比较多,需要耐心等待一切就绪。

SpringBoot学习笔记_第4张图片

**项目创建方式二:**使用IDEA直接创建项目

  1. 创建一个新项目
  2. 选择Spring Initializr,可以看到默认就是使用官网地址来快速构建项目
  3. 填写项目信息
  4. 选择初始化组件(初学勾选Web即可)
  5. 填写项目路径
  6. 等待项目构建成功

SpringBoot学习笔记_第5张图片

项目结构分析:

通过上面步骤就可以完成基础项目的创建,就会自动生成以下文件。

SpringBoot学习笔记_第6张图片

  1. 程序的主启动类(程序的主入口)
  2. 一个 application.properties 配置文件(SpringBoot的核心配置文件)
  3. 一个测试类
  4. 一个pom.xml

2.3 pom.xml分析

打开pom.xml,查看Spring Boot项目的依赖:


<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.7.3version>
    <relativePath/>
parent>

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
        
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintagegroupId>
                <artifactId>junit-vintage-engineartifactId>
            exclusion>
        exclusions>
    dependency>
dependencies>

<build>
    <plugins>
        
        <plugin>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-maven-pluginartifactId>
        plugin>
    plugins>
build>

2.4 编写一个http接口

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

SpringBoot学习笔记_第7张图片

  1. 在controller包中新建一个HelloController类

    /**
     * 处理hello请求
     */
    @RestController
    public class HelloController {
        /**
         * 处理请求:/hello
         *
         * @return 数据
         */
        @GetMapping("/hello")
        public String hello() {
            return "hello springboot!";
        }
    }
    
  2. 编写完毕后,从主程序启动项目,浏览器发起请求,看到页面显示"Hello World"字符串;控制台输出了Tomcat访问的端口号!

SpringBoot学习笔记_第8张图片

SpringBoot学习笔记_第9张图片

更改端口号:在application.properties资源文件中配置

server.port=xxxx

简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!

2.5 将项目打成jar包,点击maven的package

SpringBoot学习笔记_第10张图片

SpringBoot学习笔记_第11张图片

如果打包成功,则会在target目录下生成一个 jar 包

SpringBoot学习笔记_第12张图片

如果遇到以上错误,可以配置打包时 跳过项目运行测试用例


<plugin>
    <groupId>org.apache.maven.pluginsgroupId>
    <artifactId>maven-surefire-pluginartifactId>
    <configuration>
        
        <skipTests>trueskipTests>
    configuration>
plugin>

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

2.6 自定义banner图案

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;

只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。

图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!

SpringBoot学习笔记_第13张图片

SpringBoot这么简单的东西背后一定有故事,我们之后去进行一波源码分析!

2.7 SpringBoot

2.7.1 依赖管理

  • 父项目做依赖管理

    依赖管理
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.7.3version>
        <relativePath/> 
    parent>
    
    它的父项目
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-dependenciesartifactId>
        <version>2.7.3version>
    parent>
    几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
    
  • 开发导入starter场景启动器

    1.见到很多 spring-boot-starter-* : *就是某种场景
    2.只要引入starter,这个场景的所有常规需要的依赖,它都会自动引入
    3.SpringBoot所有支持的场景
    https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
    4.见到 *-spring-boot-starter:第三方库为我们提供的简化开发的场景启动器
    5.所有场景启动器最底层的依赖是spring-boot-starter
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starterartifactId>
      <version>2.7.3version>
      <scope>compilescope>
    dependency>
    
  • 无须关注版本号,自动版本仲裁

    1.引入依赖默认都可以不写版本
    2.引入非版本仲裁的jar,要写版本号
    
  • 可以修改默认版本号

    1.查看spring-boot-dependencies里面规定当前依赖的版本 用的是key
    2.在当前项目里面重写配置
    <properties>
    	<mysql.version>8.0.22mysql.version>
    properties>
    

2.7.2 自动配置

  • 自动配置Tomcat
    • 引入Tomcat依赖
    • 配置Tomcat
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-tomcatartifactId>
    <version>2.7.3version>
    <scope>compilescope>
dependency>

自动配好SpringMVC

  • 引入SpringMVC全套组件

  • 自动配好SpringMVC常用组件(功能)

  • 自动配好Web常见功能,如:字符编码问题

  • SpringBoot帮我们配置好了所有web开发的常见场景

  • 默认的包结构

  • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

  • 无需以前的包扫描配置

  • 想要改变扫描路径,使用注解@SpringBootApplication(scanBasePackages=“xxx”)

    或者使用注解@ComponentScan(basePackages=“xxx”) 指定扫描路径

  • @SpringBootApplication注解

    @SpringBootApplication
    等价于
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan("com.kuang")
    

在这里插入图片描述

  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类,这个类会在容器中创建对象
    • 按需加载所有自动配置项
      • 非常多的starter
      • 引入了哪些场景,这些场景的自动配置才会开启
      • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

2.8 容器功能

2.8.1 组件添加

1.@Configuration 给类添加这个注解相当于原来的bean注入bean.xml文件中

  • 基本使用
    • Full模式(全配置)和Lite模式(轻量级配置)

代码示例:

#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认,proxyBeanMethods = true。
 		其他默认是否Lite模式:proxyBeanMethods = flase
 *
 *
 *
 */
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}


################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);

        System.out.println("组件:"+(tom01 == tom02));


        //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);


        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
}

2.@Bean、@Component、@Controller、@Service、@Repository

3.@ComponentScan、@Import

@Import会给容器中自动创建要导入类的Bean

 * 4@Import({User.class, DBHelper.class})
 *      给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
 */

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
    ...
}

4.@Conditional(条件装配注解)

条件装配:当满足Conditional指定的条件,才进行组件注入

SpringBoot学习笔记_第14张图片

先看结论:当这个注解配置在类上时,只有这个类内注入了响应名称的组件才会生成相应的实体

当配置在部分方法时,只有相应的被注入才可以生成实体,否则就不生效

=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */

    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);
    }
}

2.8.2 原生配置文件引入

@ImportResource(locations={“xxx.xml”}) locations参数指定bean.xml配置文件

======================beans.xml=========================

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan">property>
        <property name="age" value="18">property>
    bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat">property>
    bean>
beans>

2.8.3 配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

public class GetProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

1.@ConfigurationProperties

参数prefix匹配application.properties中对应的前缀下的数据内容

注意想要使用这个注解必须声明组件注解@Component

/**
 * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

对应的application.properties文件:

mycar.price=100000
mycar.brind=BYD

@Component + @ConfigurationProperties

@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
    ...
}

3 自动配置原理初探

3.1 pom.xml

3.1.1 父依赖

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

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.7.3version>
    <relativePath/> 
parent>

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

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-dependenciesartifactId>
    <version>2.7.3version>
    <relativePath>../../spring-boot-dependenciesrelativePath>
parent>

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

3.1.2 启动器 spring-boot-starter

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

springboot-boot-starter-xxx:就是spring-boot的场景启动器

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;

3.2 主启动类

3.2.1 默认的主启动类

/**
 * 主程序类
 *
 * @SpringBootApplication 标注的类表示这是一个springboot的应用
 */
@SpringBootApplication
public class ApplicationContext {
    public static void main(String[] args) {
        //将springboot应用启动
        SpringApplication.run(ApplicationContext.class, args);
    }
}

但是**一个简单的启动类并不简单!**我们来分析一下这些注解都干了什么

3.2.2 @SpringBootApplication

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

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

SpringBoot学习笔记_第15张图片

点击@EnableAutoConfiguration注解里面看看

SpringBoot学习笔记_第16张图片

而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件

3.2.3 @ComponentScan

SpringBoot学习笔记_第17张图片

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

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

3.2.4 @SpringBootConfiguration

SpringBoot学习笔记_第18张图片

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

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

SpringBoot学习笔记_第19张图片

对应的注解代码:

// 点进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {
    ...
}

@Component
public @interface Configuration {
    ...
}

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

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

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

3.2.5 @EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

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

SpringBoot学习笔记_第20张图片

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

SpringBoot学习笔记_第21张图片

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

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

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

@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

SpringBoot学习笔记_第22张图片

  1. 这个类中有一个这样的方法

    //获取候选的配置
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //这里的getSpringFactoriesLoaderFactoryClass()方法
        //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    

在这里插入图片描述

  1. 这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    

SpringBoot学习笔记_第23张图片

  1. 我们继续点击查看 loadSpringFactories 方法

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
    
        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                    }
                }
            }
    
            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                              .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                                               FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }
    

SpringBoot学习笔记_第24张图片

  1. 发现一个多次出现的文件:spring.factories,全局搜索它

3.2.6 spring.factories

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!

SpringBoot学习笔记_第25张图片

WebMvcAutoConfiguration

我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration

SpringBoot学习笔记_第26张图片

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

3.2.7 自动配置生效

每一个XxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在SpringBoot中以注解的形式体现,常见的条件注解有如下几项:

@ConditionalOnBean:当容器中有指定的bean的条件下。

@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
    
@ConditionalOnClass:当类路径下有指定类的条件下。

@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
    
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,
如果没有设置的情况下也为true

例子

以ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)

SpringBoot学习笔记_第27张图片

SpringBoot学习笔记_第28张图片

在以ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启属性配置,而它后面的参数是一个ServerProperties类,这就是约定大于配置的最终落地点。

3.2.8 结论

springboot所有的自动配置都是在启动的时候扫描并加载,扫描了spring.properties配置文件,所有的自动配置类都在这里面,但是不定生效,因为要判断条件是否成立,只要导入了对应的start,就有对应的启动器,有了启动器我们自动装配就会生效,然后就配置成功。

步骤:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

现在大家应该大概的了解了下,SpringBoot的运行原理,后面我们还会深化一次!

3.3 SpringApplication

3.3.1 不简单的方法

我最初以为就是运行了一个main方法,没想到却开启了一个服务;

/**
 * 主程序类
 *
 * @SpringBootApplication 标注的类表示这是一个springboot的应用
 */
@SpringBootApplication
public class ApplicationContext {
    public static void main(String[] args) {
        //将springboot应用启动
        SpringApplication.run(ApplicationContext.class, args);
    }
}

SpringApplication.run分析

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

3.3.2 SpringApplication

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

  1. 推断应用的类型是普通的项目还是Web项目
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

查看run方法:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        listeners.started(context, timeTakenToStartup);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }

    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

3.3.3 run方法流程分析

SpringBoot学习笔记_第29张图片

跟着源码和这幅图就可以一探究竟了!

@SpringBootApplication注解的原理图:

@SpringBootApplication注解的自动装配原理图:

4 SpringBoot配置文件

4.1 配置文件

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

  • application.properties

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

    • 语法结构:key.空格value
    server:
      port: 8081
    

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

4.2 yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

这种语言以数据为中心,而不是以标记语言为重点!

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml的写法。

传统的xml配置:

<server>
	<port>8081port>
server>

yaml配置:

server:
  port: 8081

yaml基础语法

说明:语法要求严格!

  1. 空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的
  3. 属性和值的大小写都是十分敏感的

1.字面量:普通的值【数字,布尔值,字符串】

字面量直接写在后面就可以,字符串默认不用加上双引号或者单引号

k: v

注意:

  • “”,双引号,不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思

    比如:name: “kuang \n shen” 输出:kuang 换行 shen

  • ‘’,单引号,会转义特殊字符,特殊字符最终会变成和普通字符一样输出

    比如:name: ‘kuang \n shen’ 输出:kuang \n shen

2.对象、Map(键值对)

#对象、Map格式
key:
	value1:
	value2:

下一行来写对象的属性和值得关系,注意缩进。比如:

student:
	name: qinjiang
	age: 3

行内写法

student: {name: qinjiang, age: 3}

2.数组(List、Set)

用 - 值表示数组中的一个元素,比如:

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:
  port: 8082

注入配置文件

yaml文件更强大的地方在于,它可以给我们的实体类直接注入匹配值!

4.3 yaml注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

4.3.1 原始的给实体类对象赋值

  1. 在springboot项目中的resources目录下新建一个文件 application.yml

  2. 编写一个实体类 User;

    @Component  //注册bean到容器中,使得这个类可以被扫描到
    public class Dog {
        private String name;
        private Integer age;
        
        //有参无参构造、get、set方法、toString()方法  
    }
    
  3. 思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下;

    /**
     * 小狗
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Dog {
        @Value("秦疆")
        private String name;
        @Value("18")
        private Integer age;
    }
    
  4. 在SpringBoot的测试类下注入user输出一下;

    /**
     * 小狗对象测试
     */
    @SpringBootTest
    public class DogTest {
        @Autowired
        private Dog dog;
    
        /**
         * 查看dog对象内容
         */
        @Test
        void test() {
            System.out.println(dog);
        }
    }
    

    结果成功输出,@Value注入成功,这是我们原来的办法对吧。

SpringBoot学习笔记_第30张图片

4.3.2 通过yaml文件赋值

  1. 我们编写一个复杂一点的实体类:Person类

    /**
     * 人
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Person {
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private List<Object> list;
        private Map<String, Object> map;
        private Dog dog;
    }
    
  2. 我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

    person:
      name: 秦疆
      age: 19
      happy: false
      birth: 2000/01/01
      map: {k1: v1, k2: v2}
      list:
        - code
        - girl
        - music
      dog:
        name: 旺财
        age: 2
    
  3. 我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

    /**
     * 人
     *
     * @ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中;
     * 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
     * 参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @ConfigurationProperties(prefix = "person")
    public class Person {
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private List<Object> list;
        private Map<String, Object> map;
        private Dog dog;
    }
    

SpringBoot学习笔记_第31张图片

  1. IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

    SpringBoot学习笔记_第32张图片

SpringBoot学习笔记_第33张图片


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-configuration-processorartifactId>
    <optional>trueoptional>
dependency>
  1. 确认以上配置都OK之后,我们去测试类中测试一下:

    /**
     * 人对象测试
     */
    @SpringBootTest
    public class PersonTest {
        @Resource
        private Person person;
    
        /**
         * 查看person对象内容
         */
        @Test
        void test() {
            System.out.println(person);
        }
    }
    

    结果:所有值全部注入成功!

在这里插入图片描述

yaml配置注入到实体类完全OK!

课堂测试:

  1. 将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败
  2. 在配置一个person2,然后将 @ConfigurationProperties(prefix = “person2”) 指向我们的person2;

4.4 加载指定的配置文件

**@PropertySource :**加载指定的配置文件;

@ConfigurationProperties:默认从全局配置文件中获取值;

  1. 我们去在resources目录下新建一个person.properties文件

    注意在使用之前要去setting把字符集调整为UTF-8否则会乱码(在后面的回顾properties配置中有具体写到)

SpringBoot学习笔记_第34张图片

name=小秦疆

SpringBoot学习笔记_第35张图片

  1. 然后在我们的代码中指定加载person.properties文件

    /**
     * 人
     *
     * @PropertySource作用:将指定路径下的配置文件中每一个属性的值,映射到这个组件上
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @PropertySource(value = "classpath:person.properties")
    public class Person {
        @Value("${name}")
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private List<Object> list;
        private Map<String, Object> map;
        private Dog dog;
    }
    
  2. 再次输出测试一下:指定配置文件绑定成功!

SpringBoot学习笔记_第36张图片

4.5 配置文件占位符

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

person:
  name: 秦疆${random.uuid} #随机uuid
  age: 19
  happy: false
  birth: 2000/01/01
  map: {k1: v1, k2: v2}
  list:
    - code
    - girl
    - music
  dog:
    name: ${person.hello:other}_旺财  #${person.hello:other}指的是当${person.hello}值不存在时,取默认值other来表示
    age: 2

再次调用Person类,测试一下:

/**
 * 人对象测试
 */
@SpringBootTest
public class PersonTest {
    @Resource
    private Person person;
    
     /**
     * 查看person对象内容
     */
    @Test
    void test() {
        System.out.println(person);
    }
}

结果:配置文件占位符生效了

在这里插入图片描述

4.6 回顾properties配置

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!

【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置;

SpringBoot学习笔记_第37张图片

测试步骤:

  1. 新建一个实体类User

    /**
     * 用户
     *
     * @Component 注册bean
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class User {
        private String name;
        private String sex;
        private Integer age;
    }
    
  2. 编辑配置文件 user.properties

    user.username=张三
    user.age=19
    user.sex=男
    
  3. 我们在User类上使用@Value来进行注入!

    /**
     * 用户
     *
     * @Component 注册bean
     * @PropertySource注解 加载指定路径下的properties文件 将文件中属性与这个组件属性一一映射
     */
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @PropertySource(value ="classpath:user.properties")
    public class User {
        //直接使用@value
        @Value("${user.username}")  //从配置文件中取值
        private String name;
        @Value("${user.sex}")  //从配置文件中取值
        private String sex;
        @Value("#{6*2}")  // 字面量
        private Integer age;
    }
    
  4. Springboot测试

    /**
     * 用户对象测试
     */
    @SpringBootTest
    public class UserTest {
        @Autowired
        @Qualifier("user")
        private User user;
    
        /**
         * 查看user内容
         */
        @Test
        public void test() {
            System.out.println(user);
        }
    }
    

    结果正常输出:

SpringBoot学习笔记_第38张图片

4.7 Properties和Yaml的对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图

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

松散绑定:将yaml写的属性,如last-name,和实体类中属性进行映射绑定,而实体类中属性是驼峰命名的。

测试松散绑定:

1.配置文件和实体类关联映射

SpringBoot学习笔记_第39张图片

2.测试松散绑定结果

/**
     * 查看dog对象内容
     */
@Test
public void test() {
    System.out.println(dog);
}

SpringBoot学习笔记_第40张图片

结论:

  • 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@ConfigurationProperties,不要犹豫!

5 JSR303数据校检及多环境切换

5.1 JSR303

JSR303数据校验是用来校验输入内容的

SpringBoot中可以用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

5.1.1 @Validated使用

我们这里来写个注解让我们的email只能支持Email格式;

/**
 * 用户
 *
 * @Component 注册bean
 * @ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中;
 * 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
 * 参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
 * @Validated作用:数据校检 数据异常会统一抛出异常,方便异常处理中心统一处理
 */
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private List<Object> list;
    private Map<String, Object> map;
    private Dog dog;
    @Email
    private String email;
}

1.使用@Email注解,不携带任何参数

SpringBoot学习笔记_第41张图片

运行结果 :default message [不是一个合法的电子邮件地址];

SpringBoot学习笔记_第42张图片

如果没有@Email注解,需要在pom.xml文件中添加依赖:


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-validationartifactId>
dependency>

2.使用@Email注解,自定义抛出异常时的异常说明(即设置message属性值)

/**
 * 用户
 *
 * @Component 注册bean
 * @ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中;
 * 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
 * 参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
 * @Validated作用:数据校检 数据异常会统一抛出异常,方便异常处理中心统一处理
 */
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private List<Object> list;
    private Map<String, Object> map;
    private Dog dog;
    @Email(message = "请输入一个合法的电子邮箱地址")
    private String email;
}

SpringBoot学习笔记_第43张图片

运行结果 :message [请输入一个合法的电子邮箱地址];

SpringBoot学习笔记_第44张图片

使用数据校验,可以保证数据的正确性

5.1.2 @Validated常见参数

SpringBoot学习笔记_第45张图片

SpringBoot学习笔记_第46张图片

注解 说明
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
@DecimalMax(value) 验证注解的元素值小于等于 @DecimalMax 指定的 value 值
@DecimalMin(value) 验证注解的元素值大于等于 @DecimalMin 指定的 value 值
@Digits(integer, fraction) 验证字符串是否符合指定格式的数字,integer 指定整数精度,fraction指定接受的最大小数位数
@Email 验证是否是合法的邮箱
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@FutureOrPresent 验证 Date 和 Calendar 对象是否在当前时间或者之后
@Max 验证 Number 和 String 对象是否小于等于指定值
@Min 验证 Number 和 String 对象是否大于等于指定值
@Negative 带注释的元素必须是严格的负数(即0 被视为无效值)
@NegativeOrZero 带注释的元素必须是负数或 0
@NotBlank 检查字符串是否为 Null,被 Trim 的长度是否大于0,只对字符串,且会去掉前后空格
@NotEmpty 检查元素是否为 Null 或 Empty
@NotNull 验证对象是否不为 null
@Null 验证对象是否为 null
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@PastOrPresent 验证 Date 和 Calendar 对象是否在当前时间或者之前
@Pattern 验证 String 对象是否符合正则表达式的规则
@Positive 带注释的元素必须是严格的正数(即 0 被视为无效值)
@PositiveOrZero 带注释的元素必须是正数或 0
@Size(min, max) 验证对象长度是否在给定的范围内
@Range(min, max, message) 验证属性值必须在合适的范围内
@Length(min, max) 验证字符串的长度必须在指定范围内
@CreditCardNumber 验证是否是合法的信用卡号
@URL 验证是否是合法的url
@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       验证 DateCalendar 对象是否在当前时间之前  
@Future     验证 DateCalendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

5.2 多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

5.2.1 多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

测试

1.测试不同环境下项目启动情况,注意观察服务器端口号

配置多个环境版本,版本信息如下:

  1. application.yaml

    server:
      port: 8080
    
  2. application-dev.yaml

    server:
      port: 8081
    
  3. application-test.yaml

    server:
      port: 8082
    

2.启动主程序类,查看项目运行情况

/**
 * 主程序
 */
@SpringBootApplication
public class ApplicationContext {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationContext.class,args);
    }
}

SpringBoot学习笔记_第47张图片

SpringBoot学习笔记_第48张图片

如果没有在application.yaml中配置激活环境参数,默认使用application.yaml为主配置文件。

在application.yaml中激活dev开发环境配置

SpringBoot学习笔记_第49张图片

SpringBoot学习笔记_第50张图片

在application.yaml中激活test测试环境配置

SpringBoot学习笔记_第51张图片

SpringBoot学习笔记_第52张图片

5.2.2 yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

通过 — 来分割模块

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称:dev 开发环境


---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称:prod 生产环境

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

启动主程序类,使用默认的 application.yaml 作为主配置文件

SpringBoot学习笔记_第53张图片

SpringBoot学习笔记_第54张图片

在application.yaml中激活dev开发环境配置

SpringBoot学习笔记_第55张图片

SpringBoot学习笔记_第56张图片

在application.yaml中激活test测试环境配置

SpringBoot学习笔记_第57张图片

SpringBoot学习笔记_第58张图片

5.3 配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

官方外部配置文件说明参考文档

SpringBoot学习笔记_第59张图片

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到低,高优先级的配置会覆盖低优先级的配置。

优先级排序:优先级1 > 优先级2 > 优先级3 > 优先级4

SpringBoot会从这四个配置全部加载主配置文件;互补配置;

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

#配置项目的访问路径
server.servlet.context-path=/kuang

根据优先级高低,查看不同级别下的服务器启动端口的差别

1.项目路径下config文件夹下的application.yml【优先级1】

SpringBoot学习笔记_第60张图片

SpringBoot学习笔记_第61张图片

2.项目路径下的application.yml【优先级2】

SpringBoot学习笔记_第62张图片

SpringBoot学习笔记_第63张图片

3.资源路径下config文件夹下的application.yml【优先级3】

SpringBoot学习笔记_第64张图片

SpringBoot学习笔记_第65张图片

4.资源路径下的application.yml【优先级4】

SpringBoot学习笔记_第66张图片

SpringBoot学习笔记_第67张图片

5.4 拓展,运维小技巧

指定位置加载配置文件

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

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

6 自动配置原理

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住!

SpringBoot学习笔记_第68张图片

6.1分析自动配置原理

我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

//表示这是一个自动配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@AutoConfiguration


//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)


//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)


//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)


//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding",
                       value = "enabled", matchIfMissing = true)


public class HttpEncodingAutoConfiguration {
	//他已经和SpringBoot的配置文件映射了
	private final Encoding properties;

    //只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}

    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@Bean
	@ConditionalOnMissingBean  //判断容器没有这个组件?
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}
    
    //....
}

SpringBoot学习笔记_第69张图片

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一旦这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

通俗理解:把我们原先需要在bean中手打的属性(property)封装成了一个类,然后通过yaml文件进行自动注入,而我们也可以在application.yaml文件中对这些property进行赋值。

//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
     /**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;

	@NestedConfigurationProperty
	private final ErrorProperties error = new ErrorProperties();

	/**
	 * Strategy for handling X-Forwarded-* headers.
	 */
	private ForwardHeadersStrategy forwardHeadersStrategy;

	/**
	 * Value to use for the Server response header (if empty, no header is sent).
	 */
	private String serverHeader;

	/**
	 * Maximum size of the HTTP message header.
	 */
	private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);

	/**
	 * Type of shutdown that the server will support.
	 */
	private Shutdown shutdown = Shutdown.IMMEDIATE;

	@NestedConfigurationProperty
	private Ssl ssl;
    //......
}
public class Encoding {

	/**
	 * Default HTTP encoding for Servlet applications.
	 */
	public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

	/**
	 * Charset of HTTP requests and responses. Added to the "Content-Type" header if not
	 * set explicitly.
	 */
	private Charset charset = DEFAULT_CHARSET;

	/**
	 * Whether to force the encoding to the configured charset on HTTP requests and
	 * responses.
	 */
	private Boolean force;

	/**
	 * Whether to force the encoding to the configured charset on HTTP requests. Defaults
	 * to true when "force" has not been specified.
	 */
	private Boolean forceRequest;

	/**
	 * Whether to force the encoding to the configured charset on HTTP responses.
	 */
	private Boolean forceResponse;

	/**
	 * Mapping of locale to charset for response encoding..
	 */
	private Map<Locale, Charset> mapping;
	//......
}

我们去配置文件里面试试前缀,看提示!

SpringBoot学习笔记_第70张图片

这就是自动装配的原理!

6.2 精髓

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

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

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

怎么去修改这些属性呢:说白了就是SpringBoot配置,---->.yaml、.properties这些文件

6.3 了解:@Conditional

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

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的Java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在着指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项
@ConditionalOnCloudPlatform @Conditional在指定的云平台处于活动状态时匹配
@ConditionalOnWarDeployment @Conditional当应用程序是传统的 WAR 部署时匹配。对于具有嵌入式服务器的应用程序,此条件将返回 false

SpringBoot学习笔记_第71张图片

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

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

在这里插入图片描述

SpringBoot学习笔记_第72张图片

SpringBoot学习笔记_第73张图片

Positive matches:(自动配置类启用的:正匹配)

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

Unconditional classes: (没有条件的类)

【演示:查看输出的日志】

掌握吸收理解原理,即可以不变应万变!

7 自定义starter启动器

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

7.1 说明

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

命名归约:

官方命名:

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

自定义命名:

  • xxx-spring-boot-starter
  • 比如:mybatis-spring-boot-starter

7.2 编写启动器

  1. 在IDEA中新建一个空项目 spring-boot-starter-diy

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

SpringBoot学习笔记_第74张图片

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

在这里插入图片描述

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

SpringBoot学习笔记_第75张图片

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

    
    <dependencies>
        
        <dependency>
            <groupId>com.kuanggroupId>
            <artifactId>kuang-spring-boot-starter-autoconfigureartifactId>
            <version>0.0.1-SNAPSHOTversion>
        dependency>
    dependencies>
    
  2. 将 autoconfigure 项目下多余的文件都删掉,pom中只留下一个 starter,这是所有的启动器基本配置!

SpringBoot学习笔记_第76张图片

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

    package com.kuang;
     
     
    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();
        }
    }
    
  2. 编写HelloProperties 配置类

    package com.kuang;
      
    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;
        }
    }
    
  3. 编写我们的自动配置类并注入bean,测试!

    package com.kuang;
      
    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;
        } 
    }
    
  4. 在resources编写一个自己的 META-INF\spring.factories

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.kuang.HelloServiceAutoConfiguration
    
  5. 编写完成后,可以安装到maven仓库中!

SpringBoot学习笔记_第77张图片

7.3 新建项目测试我们自己写的启动器

  1. 新建一个SpringBoot 项目

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

    <dependency>
        <groupId>com.kuanggroupId>
        <artifactId>kuang-spring-boot-starterartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
    
  3. 编写一个 HelloController 进行测试我们自己的写的接口!

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

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

SpringBoot学习笔记_第78张图片

8 SpringBoot Web开发总览

在之前我们的项目都是以jar包结尾的,没有放webapp的地方。

springboot最大的特点:自动装配。

  1. 创建应用,选择模块导入starter,只需要专注于业务代码

springboot到底帮我们配置了什么,我们能不能修改?能修改哪些东西?能不能扩展?

  • xxxAutoConfiguration:向容器中自动配置组件
  • xxxProperties:自动配置类,装配配置文件中自定义的一些内容

要解决的问题:

  • 导入静态资源html,css,js
  • 首页
  • 写jsp的地方,模板引擎Thymeleaf
  • 装配和扩展SpringMVC
  • 增删改查
  • 拦截器

8.1 Web开发静态资源处理

静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法:addResourceHandlers 添加资源处理

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
        logger.debug("Default resource handling disabled");
        return;
    }
    // webjars 配置
    addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
    // 静态资源配置
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
            registration.addResourceLocations(resource);
        }
    });
}

读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

什么是webjars 呢?

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars,我们可以去搜索一下:

网站:https://www.webjars.org

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

<dependency>
    <groupId>org.webjarsgroupId>
    <artifactId>jqueryartifactId>
    <version>3.6.1version>
dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件!

SpringBoot学习笔记_第79张图片

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.6.1/jquery.js

SpringBoot学习笔记_第80张图片

第二种静态资源映射规则

那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;

我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:

// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}

/**
 * 静态资源的位置。默认为类路径:
 * [/META-INF/resources/、/resources/、/static/、/public/]。
 */
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

当然他们也有优先级,优先级为:“classpath:/resources/” > “classpath:/static/” > “classpath:/public/”

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/one.js , 他就会去这些文件夹中寻找对应的静态资源文件;

SpringBoot学习笔记_第81张图片

当资源路径下resources目录里有one.js静态资源文件时,优先访问该文件

SpringBoot学习笔记_第82张图片

当资源路径下resources目录不存在或者该目录下没有one.js静态资源文件时,优先访问static目录下one.js静态资源文件

SpringBoot学习笔记_第83张图片

SpringBoot学习笔记_第84张图片

当资源路径下resources目录不存在或者该目录下没有one.js静态资源文件,且资源路径下static目录不存在或者该目录下没有one.js静态资源文件时,优先访问public目录下one.js静态资源文件

SpringBoot学习笔记_第85张图片

SpringBoot学习笔记_第86张图片

自定义静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;

spring.web.resources.static-locations=classpath:/coding/, classpath:/kuang/

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

SpringBoot学习笔记_第87张图片

此时我们访问 http://localhost:8080/one.js , 他就会去这些文件夹中寻找对应的静态资源文件;

SpringBoot学习笔记_第88张图片

根据浏览器返回的内容,可以看到,获取的静态资源是根据配置静态路径的属性值先后顺序进行的

删除资源路径下的 /coding/ 目录,继续查看访问情况:

SpringBoot学习笔记_第89张图片

SpringBoot学习笔记_第90张图片

可以看到,请求返回的结果正常!

8.2 首页处理

静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}

点进去继续看

private Resource getWelcomePage() {
    for (String location : this.resourceProperties.getStaticLocations()) {
        // ::是 java8 中新引入的运算符
    	// Class::function的时候function是属于Class的,应该是静态方法。
    	// this::function的funtion是属于这个对象的。
    	// 简而言之,就是一种语法糖而已,是一种简写
        Resource indexHtml = getIndexHtml(location);
        if (indexHtml != null) {
            return indexHtml;
        }
    }
    ServletContext servletContext = getServletContext();
    if (servletContext != null) {
        return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
    }
    return null;
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(Resource location) {
    try {
        Resource resource = location.createRelative("index.html");
        if (resource.exists() && (resource.getURL() != null)) {
            return resource;
        }
    }
    catch (Exception ex) {
    }
    return null;
}

欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!

关于网站图标说明:
SpringBoot学习笔记_第91张图片

与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。

  1. 关闭SpringBoot默认图标

    #关闭默认图标
    spring.mvc.favicon.enabled=false
    
  2. 自己放一个图标在静态资源目录下,我放在 public 目录下

  3. 清除浏览器缓存!刷新网页,发现图标已经变成自己的了!

SpringBoot学习笔记_第92张图片

9 Thymeleaf模板引擎

9.1 模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

SpringBoot学习笔记_第93张图片

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单,而且功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

9.2 引入Thymeleaf

怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到对应的pom依赖:可以适当点进源码看下本来的包!


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

本来的包,对应的pom依赖

SpringBoot学习笔记_第94张图片
Maven会自动下载jar包,我们可以去看下下载的东西;
SpringBoot学习笔记_第95张图片

9.3 Thymeleaf分析

前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?

我们首先得按照SpringBoot的自动配置原理,看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下Thymeleaf的自动配置类:ThymeleafProperties

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";

	/**
	 * Whether to check that the template exists before rendering it.
	 */
	private boolean checkTemplate = true;

	/**
	 * Whether to check that the templates location exists.
	 */
	private boolean checkTemplateLocation = true;

	/**
	 * Prefix that gets prepended to view names when building a URL.
	 */
	private String prefix = DEFAULT_PREFIX;

	/**
	 * Suffix that gets appended to view names when building a URL.
	 */
	private String suffix = DEFAULT_SUFFIX;

	/**
	 * Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
	 */
	private String mode = "HTML";

	/**
	 * Template files encoding.
	 */
	private Charset encoding = DEFAULT_ENCODING;

	/**
	 * Whether to enable template caching.
	 */
	private boolean cache = true;

	/**
	 * Order of the template resolver in the chain. By default, the template resolver is
	 * first in the chain. Order start at 1 and should only be set if you have defined
	 * additional "TemplateResolver" beans.
	 */
	private Integer templateResolverOrder;

	/**
	 * Comma-separated list of view names (patterns allowed) that can be resolved.
	 */
	private String[] viewNames;

	/**
	 * Comma-separated list of view names (patterns allowed) that should be excluded from
	 * resolution.
	 */
	private String[] excludedViewNames;

	/**
	 * Enable the SpringEL compiler in SpringEL expressions.
	 */
	private boolean enableSpringElCompiler;

	/**
	 * Whether hidden form inputs acting as markers for checkboxes should be rendered
	 * before the checkbox element itself.
	 */
	private boolean renderHiddenMarkersBeforeCheckboxes = false;

	/**
	 * Whether to enable Thymeleaf view resolution for Web frameworks.
	 */
	private boolean enabled = true;

	private final Servlet servlet = new Servlet();

	private final Reactive reactive = new Reactive();
    //...
}

SpringBoot学习笔记_第96张图片

我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试

1.编写一个TestController

/**
 * 处理/index请求
 */
@Controller
public class IndexController {
    /**
     * 处理/index请求
     *
     * @return 首页地址
     */
    @GetMapping("/index")
    public String index() {
        return "index";
    }
}

2.编写一个测试页面 test.html 放在 templates 目录下

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页title>
head>
<body>
<h1>首页欢迎您!h1>
body>
html>

3.启动项目请求测试

结果:

SpringBoot学习笔记_第97张图片

9.4 Thymeleaf 语法学习

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;

Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!

我们做个最简单的练习 :我们需要查出一些数据,在页面中展示

1、修改测试请求,增加数据传输;

/**
 * 处理/index请求
 *
 * @param model 模型
 * @return 首页地址
 */
@GetMapping("/index")
public String index(Model model) {
    model.addAttribute("msg", "欢迎使用 Thymeleaf 模板!");
    return "index";
}

2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。

我们可以去官方文档的#3中看一下命名空间拿来过来:

xmlns:th="http://www.thymeleaf.org"

3、我们去编写下前端页面

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>首页title>
head>
<body>
<h1 th:text="${msg}">Welcome to thymeleaf templates!h1>
body>
html>

4、启动项目请求测试

SpringBoot学习笔记_第98张图片

OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!

1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!

SpringBoot学习笔记_第99张图片

2、我们能写哪些表达式呢?

Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
  Message Expressions: #{...}:获取国际化内容
  Link URL Expressions: @{...}:定义URL;
  Fragment Expressions: ~{...}:片段引用表达式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,…
      Number literals: 0 , 34 , 3.0 , 12.3 ,…
      Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,…
      
Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

SpringBoot学习笔记_第100张图片

SpringBoot学习笔记_第101张图片

练习测试:

1、 我们编写一个Controller,放一些数据

/**
 * 处理/index请求
 *
 * @param map key-value pair
 * @return 首页地址
 */
@GetMapping("/index2")
public String index(Map<String, Object> map) {
    map.putIfAbsent("msg", "

Hello Thymeleaf!

"
); map.putIfAbsent("users", Arrays.asList("秦疆", "Li Si")); //classpath:/templates/index.html return "index"; }

2、编写前端页面

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>首页title>
head>
<body>

    
    <div th:text="${msg}">内容div>
    
    <div th:utext="${msg}">展示内容div>

    
    
    <table>
        <tr>
            <th>姓名th>
        tr>
        <tr th:each="user : ${users}">
           <td th:text="${user}">Zhang Santd>
        tr>
    table>

    
    <ul>
        <li th:each="user : ${users}" th:text="${user}">张三li>
    ul>
body>
html>

3、启动项目请求测试

SpringBoot学习笔记_第102张图片

10 SpringMVC自动配置原理

10.1 自动配置原理

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!

官网地址链接

Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@Configuration类,类型为WebMvcConfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCRegistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

SpringBoot学习笔记_第103张图片

我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?

ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到 WebMvcAutoConfiguration, 然后搜索ContentNegotiatingViewResolver。找到如下方法!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    //ContentNegotiatingViewResolver 使用所有其他视图解析器来定位视图,因此它应该具有高优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

SpringBoot学习笔记_第104张图片

我们可以点进这类看看!找到对应的解析视图的代码;

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回
        View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }

    String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
        " given " + requestedMediaTypes.toString() : "";

    if (this.useNotAcceptableStatusCode) {
        if (logger.isDebugEnabled()) {
            logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
        }
        return NOT_ACCEPTABLE_VIEW;
    }
    else {
        logger.debug("View remains unresolved" + mediaTypeInfo);
        return null;
    }
}

SpringBoot学习笔记_第105张图片

我们继续点进去看,他是怎么获得候选的视图的呢?

getCandidateViews中看到他是把所有的视图解析器拿forEach循环,挨个解析!

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
    throws Exception {

    List<View> candidateViews = new ArrayList<>();
    if (this.viewResolvers != null) {
        Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                candidateViews.add(view);
            }
            for (MediaType requestedMediaType : requestedMediaTypes) {
                List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                for (String extension : extensions) {
                    String viewNameWithExtension = viewName + '.' + extension;
                    view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                    if (view != null) {
                        candidateViews.add(view);
                    }
                }
            }
        }
    }
    if (!CollectionUtils.isEmpty(this.defaultViews)) {
        candidateViews.addAll(this.defaultViews);
    }
    return candidateViews;
}

SpringBoot学习笔记_第106张图片

所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!

@Override
protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的所有视图解析器
    // ViewRescolver.class 把所有的视图解析器来组合的
    Collection<ViewResolver> matchingBeans =
        BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList<>(matchingBeans.size());
        for (ViewResolver viewResolver : matchingBeans) {
            if (this != viewResolver) {
                this.viewResolvers.add(viewResolver);
            }
        }
    }
    else {
        for (int i = 0; i < this.viewResolvers.size(); i++) {
            ViewResolver vr = this.viewResolvers.get(i);
            if (matchingBeans.contains(vr)) {
                continue;
            }
            String name = vr.getClass().getName() + i;
            obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
        }

    }
    AnnotationAwareOrderComparator.sort(this.viewResolvers);
    this.cnmFactoryBean.setServletContext(servletContext);
}

SpringBoot学习笔记_第107张图片

既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动地将它组合进来;我们去实现一下

1、我们在我们的主程序中去写一个视图解析器来试试;

/**
 * 配置类
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver myMvcResolver() {
        return new MyViewResolver();
    }

    static class MyViewResolver implements ViewResolver {
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

2、怎么看我们自己写的视图解析器有没有起作用呢?

我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中

SpringBoot学习笔记_第108张图片

3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;

SpringBoot学习笔记_第109张图片

找到视图解析器,我们看到我们自己定义的就在这里了;

SpringBoot学习笔记_第110张图片

所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!

10.2 转换器和格式化器

找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    Format format = this.mvcProperties.getFormat();
    WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
                                                                      .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
    addFormatters(conversionService);
    return conversionService;
}

SpringBoot学习笔记_第111张图片

点进去:

public String getDateTime() {
    return this.dateTime;
}
/**
 * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'.
 */
private String dateTime;

可以看到在我们的Properties文件中,我们可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

SpringBoot学习笔记_第112张图片

10.3 修改SpringBoot的默认配置

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@Bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
扩展使用SpringMVC 官方文档如下:

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;

/**
 * 配置类
 * 应为类型要求为WebMvcConfigurer,所以我们实现其接口
 * 可以使用自定义类扩展MVC的功能
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test,就会跳转到index页面
        registry.addViewController("/test").setViewName("index");
    }
}

我们去浏览器访问一下:

SpringBoot学习笔记_第113张图片

确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!

我们可以去分析一下原理:

  1. WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

  2. 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

SpringBoot学习笔记_第114张图片

  1. 我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

在这里插入图片描述

这个父类中有这样一段代码:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	// 从容器中获取所有的webmvcConfigurer
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

在这里插入图片描述

  1. 我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addViewControllers(registry);
        }
    }
    

SpringBoot学习笔记_第115张图片

  1. 我们点进去看一下

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addViewControllers(registry);
        }
    }
    

SpringBoot学习笔记_第116张图片

所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;

10.4 全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

SpringBoot学习笔记_第117张图片

给配置类加上注解:@EnableWebMvc

SpringBoot学习笔记_第118张图片

我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;

当然,我们开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

1、这里发现它是导入了一个类,我们可以继续进去看

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

SpringBoot学习笔记_第119张图片

2、它继承了一个父类 WebMvcConfigurationSupport

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
	// ......
}

SpringBoot学习笔记_第120张图片

3、我们来回顾一下Webmvc自动配置类

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {
	// ......
}

SpringBoot学习笔记_第121张图片

总结一句话:@EnableWebMvc将 WebMvcConfigurationSupport 组件导入进来了;

而导入的 WebMvcConfigurationSupport 只是SpringMVC最基本的功能!

在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~

11 聊一聊怎么写一个网页

  • 前端必须使用推荐模板:别人写好的,我们拿来改成自己需要的
  • 框架:组件:需要自己手动组合拼接 layui,elementui
    • 栅格系统
    • 导航栏
    • 侧边栏

1.前端搞定:页面长什么样子:数据

2.设计数据库

3.前端让他能够自动运行,独立化工程

4.数据接口如何对接,json,对象all in one

5.前后端联调测试

后台框架:xadmin

前端界面:至少自己能够通过前端框架,组合出来一个网站页面

注意资源导出问题:

在build标签下添加:

<resources>
    <resource>
        <directory>src/main/javadirectory>
        <includes>
            <include>**/*.*include>
        includes>
    resource>
    <resource>
        <directory>src/main/resourcesdirectory>
        <includes>
            <include>**/*.*include>
        includes>
    resource>
resources>

12 SpringBoot整合数据库操作

12.1 整合JDBC

12.1.1 SpringData简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

SpringBoot学习笔记_第122张图片

数据库相关的启动器 :可以参考官方文档:

https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

SpringBoot学习笔记_第123张图片

12.1.2 整合JDBC

创建测试项目测试数据源

1、新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块

插入图片

2、项目建好之后,发现自动帮我们导入了如下的启动器

<dependency>    
    <groupId>org.springframework.bootgroupId>    
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>    
    <groupId>mysqlgroupId>    
    <artifactId>mysql-connector-javaartifactId>    
    <scope>runtimescope>
dependency>

3、编写yaml配置文件连接数据库

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterSet=utf-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下

@SpringBootTest
class UserMapperTest {
    @Autowired
    private DataSource dataSource;

    /**
     * 测试默认数据源 HikariDataSource
     */
    @Test
    void contextLoads() {
        //获取数据源类型
        System.out.println(dataSource.getClass());
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            System.out.println(connection);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭连接池
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

执行结果:

在这里插入图片描述

结果:我们可以看到他默认给我们配置的数据源为 : class class com.zaxxer.hikari.HikariDataSource,我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在 DataSourceAutoConfiguration 文件:

@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
         DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
         DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {

}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.7.3 默认使用 HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。

关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate

12.1.3 JdbcTemplate

1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;

2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即 JdbcTemplate

3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

12.1.4 测试

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;

Controller层

/**
 * 处理用户请求
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 查询所有用户
     *
     * @return 数据
     */
    @GetMapping("/list")
    public List<Map<String, Object>> queryUserList() {
        return userService.queryUserList();
    }

    /**
     * 查询单个用户
     *
     * @param id 编号
     * @return 数据
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Map<String, Object> queryUserById(@PathVariable("id") Integer id) {
        return userService.queryUserById(id);
    }

    /**
     * 新增用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 数据
     */
    @PostMapping("/add")
    public String addUser(@RequestParam("id") Integer id,
                          @RequestParam(value = "name", defaultValue = "", required = false) String name,
                          @RequestParam(value = "pwd", defaultValue = "", required = false) String pwd) {
        return userService.addUser(id, name, pwd);
    }

    /**
     * 更新用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 数据
     */
    @PutMapping("/update")
    public String updateUser(@RequestParam("id") Integer id,
                             @RequestParam(value = "name", defaultValue = "", required = false) String name,
                             @RequestParam(value = "pwd", defaultValue = "", required = false) String pwd) {
        return userService.updateUser(id, name, pwd);
    }

    /**
     * 删除用户
     *
     * @param id 编号
     * @return 数据
     */
    @DeleteMapping("/delete")
    public String deleteUser(@RequestParam("id") Integer id) {
        return userService.deleteUser(id);
    }
}

Service层

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    /**
     * 查询所有用户信息
     *
     * @return 数据
     */
    public List<Map<String, Object>> queryUserList() {
        return userMapper.queryUserList();
    }

    /**
     * 查询单个用户
     *
     * @param id 编号
     * @return 用户
     */
    public Map<String, Object> queryUserById(Integer id) {
        if (ObjectUtils.isEmpty(id)) {
            return new HashMap<>(1);
        }
        return userMapper.queryUserById(id);
    }

    /**
     * 新增用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 状态
     */
    public String addUser(Integer id, String name, String pwd) {
        if (ObjectUtils.isEmpty(id)) {
            return "用户编号不能为空";
        }
        return userMapper.addUser(id, name, pwd) > 0 ? "添加成功" : "添加失败";
    }

    /**
     * 修改用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 状态
     */
    public String updateUser(Integer id, String name, String pwd) {
        if (ObjectUtils.isEmpty(id)) {
            return "用户编号不能为空";
        }
        return userMapper.updateUser(id, name, pwd) > 0 ? "更新成功" : "更新失败";
    }

    /**
     * 删除用户
     *
     * @param id 编号
     * @return 状态
     */
    public String deleteUser(Integer id) {
        if (ObjectUtils.isEmpty(id)) {
            return "用户编号不能为空";
        }
        return userMapper.deleteUser(id) > 0 ? "删除成功" : "删除失败";
    }
}

Mapper层

@Repository
public class UserMapper {
    /**
     * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
     * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 查询所有用户
     * List 中的1个 Map 对应数据库的 1行数据
     *     Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
     * @return 用户列表
     */
    public List<Map<String, Object>> queryUserList() {
        String sql = "select id, name, pwd from user";
        return jdbcTemplate.queryForList(sql);
    }

    /**
     * 查询单个用户
     *
     * @param id 编号
     * @return 用户
     */
    public Map<String, Object> queryUserById(Integer id) {
        String sql = "select id, name, pwd from user where id = ?";
        return jdbcTemplate.queryForMap(sql, id);
    }

    /**
     * 新增用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 影响的行数
     */
    public int addUser(Integer id, String name, String pwd) {
        String sql = "insert into user (id, name, pwd) values (?, ?, ?)";
        return jdbcTemplate.update(sql, id, name, pwd);
    }

    /**
     * 修改用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 影响的行数
     */
    public int updateUser(Integer id, String name, String pwd) {
        String sql = "update user set name = ?, pwd = ? where id = " + id;
        return jdbcTemplate.update(sql, name, pwd);
    }

    /**
     * 删除用户
     *
     * @param id 编号
     * @return 影响的行数
     */
    public int deleteUser(Integer id) {
        String sql = "delete from user where id = ?";
        return jdbcTemplate.update(sql, id);
    }
}

1、查询所有用户

SpringBoot学习笔记_第124张图片

2、查询单个用户

SpringBoot学习笔记_第125张图片

3、新增用户

SpringBoot学习笔记_第126张图片

4、更新用户

SpringBoot学习笔记_第127张图片

5、删除用户

SpringBoot学习笔记_第128张图片

12.2 整合Druid数据源

12.2.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/

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this).
url 连接数据库的url ,不同数据库不一样。例如: mysql: jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:orade:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用 ConfigFilter。
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说orade。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache ,必须配置大于0 ,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null , testOnBorrow. testOnReturn、testWhileldle都不会起作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用jdbcStatement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为true ,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis ,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableldleTimeMillis则关闭物理连接2)testWhileldle的判断依据,详细看testWhileldle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 30分钟(1.0.14) 连接保持空闲而不被驱逐的最长时间
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters 类型是List,如果此时配置了filters和proxyFilters ,是组合关系,并非替换关系

12.2.2 配置数据源

1、添加上 Druid 数据源依赖


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.2.8version>
dependency>

2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterSet=utf-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;

SpringBoot学习笔记_第129张图片

4、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码

#Spring Boot 默认是不注入这些属性值的,需要自己绑定
 #druid: 数据源专有配置
    initialSize: 0
    minIdle: 2
    maxActive: 8
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

SpringBoot学习笔记_第130张图片

这些配置是核心:

SpringBoot学习笔记_第131张图片

5、看到需要用到log4j,所以需要在pom中导入log4j的依赖


<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

@Configuration
public class DruidConfig {

    /*
       将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}

7、去测试类中测试一下;看是否成功!

@SpringBootTest
public class DruidDataSourceTest {
    @Autowired
    private DataSource dataSource;

    /**
     * 测试类路径下的数据源 DruidDataSource
     */
    @Test
    void test() {
        //获取数据源类型
        System.out.println(dataSource.getClass());
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            System.out.println(connection);
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;

            System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
            System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭连接池
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

SpringBoot学习笔记_第132张图片

12.2.3 配置Druid数据源监控

/**
 * 注册数据源监控
 * 配置 Druid 监控管理后台的Servlet;
 * 内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
 */
@Bean
public ServletRegistrationBean<Servlet> druidServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
    // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
    // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin");//后台管理界面的登录账号
    initParams.put("loginPassword", "12345");//后台管理界面的登录密码

    //后台允许谁可以访问
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html

SpringBoot学习笔记_第133张图片

登陆网站,输入用户名:admin,密码:12345

SpringBoot学习笔记_第134张图片

在访问http://localhost:8080/user/list后:

在这里插入图片描述

12.2.4 配置 Druid web 监控 filter 过滤器

//配置 Druid 监控 之  web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

注意点:DruidConfig文件一定要添加配置注解,在里面配置的一些servlet和filter都要添加@Bean注解

12.3 整合 Mybatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.2.2
SpringBoot学习笔记_第135张图片

12.3.1 整合测试

1、导入 MyBatis 所需要的依赖


<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.2version>
dependency>

2.配置数据库连接信息(不变)

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterSet=utf-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 2
    minIdle: 2
    maxActive: 10
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  type-aliases-package: com.kuang.entity
  mapper-locations: classpath:mybatis/mapper/*.xml
server:
  port: 8081

3、测试数据库是否连接成功!

4、创建实体类,导入 Lombok!

User.java

/**
 * 用户
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Validated
public class User {
    @NotNull
    private Integer id;
    @NotBlank
    @Length(min = 4,max = 10)
    private String name;
    @NotBlank
    @Size(min = 5,max = 10)
    private String pwd;
}

5、创建mapper目录以及对应的 Mapper 接口

UserMapper2.java

在这里插入图片描述

@Mapper
@Repository
public interface UserMapper2 {

    /**
     * 查询所有用户
     * List 中的1个 Map 对应数据库的 1行数据
     *     Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
     * @return 用户列表
     */
    List<Map<String, Object>> queryUserList();

    /**
     * 查询单个用户
     *
     * @param id 编号
     * @return 用户
     */
    Map<String, Object> queryUserById(Integer id);

    /**
     * 新增用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 影响的行数
     */
    int addUser(Integer id, String name, String pwd);

    /**
     * 修改用户
     *
     * @param id   编号
     * @param name 姓名
     * @param pwd  密码
     * @return 影响的行数
     */
    int updateUser(Integer id, String name, String pwd);

    /**
     * 删除用户
     *
     * @param id 编号
     * @return 影响的行数
     */
    int deleteUser(Integer id);
}

SpringBoot学习笔记_第136张图片

接下来该去配置mapper.xml文件了,这里建议创建在resources的目录下:

SpringBoot学习笔记_第137张图片

UserMapper.xml文件:

SpringBoot学习笔记_第138张图片

SpringBoot学习笔记_第139张图片

编写service层,直接写controller调用service层,service层调用mapper层

/**
 * 处理用户请求
 */
@RestController
@RequestMapping("/u")
public class UserController2 {
    @Autowired
    private UserService2 userService;

    /**
     * 查询所有用户
     *
     * @return 数据
     */
    @GetMapping("/list")
    public List<User> queryUserList() {
        return userService.queryUserList();
    }

    /**
     * 查询单个用户
     *
     * @param id 编号
     * @return 数据
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User queryUserById(@PathVariable("id") Integer id) {
        return userService.queryUserById(id);
    }

    /**
     * 新增用户
     *
     * @param user 用户
     * @return 数据
     */
    @PostMapping("/add")
    public String addUser(@RequestBody @Validated User user) {
        return userService.addUser(user);
    }

    /**
     * 更新用户
     *
     * @param user 用户
     * @return 数据
     */
    @PutMapping("/update")
    public String updateUser(@RequestBody @Validated User user) {
        return userService.updateUser(user);
    }

    /**
     * 删除用户
     *
     * @param id 编号
     * @return 数据
     */
    @DeleteMapping("/delete")
    public String deleteUser(@RequestParam("id") Integer id) {
        return userService.deleteUser(id);
    }
}

查询所有用户

SpringBoot学习笔记_第140张图片

查询单个用户

SpringBoot学习笔记_第141张图片

添加用户

SpringBoot学习笔记_第142张图片

修改用户

SpringBoot学习笔记_第143张图片

删除用户

SpringBoot学习笔记_第144张图片

13 SpringSecurity(安全)

一个安全的框架,其实通过过滤器和拦截器也可以实现

首先我们看下它的官网介绍:Spring Security官网地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

13.1 认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

13.2 实战使用

你可能感兴趣的:(Springboot,spring,boot)