Java-SpringBoot学习笔记

Java-SpringBoot-1

学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1PE411i7CV

学习文档: 微信公众号 狂神说 –https://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18&uin=&key=&devicetype=Windows+10+x64&version=63020170&lang=zh_CN&ascene=7&fontgear=2

SpringBoot 官方文档链接 – https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#appendix

1、SpringBoot简介

1.1、回顾什么是Spring:

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

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

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

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

Spring就是一个容器大杂烩。对象的创建控制权交给Spring IOC容器Bean,不再是程序猿控制。解耦,提供代码复用性。下面回忆的就是 Spring的核心:Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块。

IOC实现创建对象的方式:

  • 使用xml配置开发

    • spring-config.xml配置文件中定义 bean的信息,,对于对象的具体实例 注入方式(定义属性值和 引用自定义类):构造器注入、set注入、c命名和p命名空间注入方式。【IOC创建bean,创建对象】

      <!-- 构造器注入-->
      <bean id="exampleBean" class="examples.ExampleBean"> 
          <constructor-arg name="years" value="7500000"/> 
          <constructor-arg name="ultimateAnswer" value="42"/> 
      </bean>
      <!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
      类型 变量名 = new 类型();
      Hello hello = new Hello
      
      在这个bean中:
      id = 变量名
      class = new 的对象
      property 就相当于给对象hello中的属性设置一个值
      -->
      <!-- set注入-->
      <bean id="student" class="com.AL.pojo.Student">
          <property name="name" value="小明"/>
      </bean>
      
      <!--P(属性: properties)命名空间 , 属性依然要设置set方法
      set方法,即此时的 setName 等. 在这里可以直接注入属性的值:property-->
      <bean id="user" class="com.AL.pojo.User" p:name="鑫鑫" p:age="18"/>
      <!--C(构造: Constructor)命名空间, 通过构造器注入-->
      <bean id="user2" class="com.AL.pojo.User" c:name="鑫仔" c:age="18"/>
      
    • 自动装配Bean在spring容器上下文中去寻找和自己对象set方法后面的值相对应的 bean id。 【寻找想要的容器bean,想要的对象实例】

      <bean id="dog" class="com.AL.pojo.Dog"/>
      <bean id="cat" class="com.AL.pojo.Cat"/>
          <!-- 原始的:获取想要的容器bean, 使用了 ref 引入 -->
      <bean id="people" class="com.AL.pojo.People">
          <property name="cat" ref="cat"/>
          <property name="dog" ref="dog"/>
          <property name="name" value="ALZN"/>
      </bean>
      
      <!--byName自动装配:会自动在容器上下文中去查找,和自己对象set方法后面的值相对应的 bean id!-->
      <bean id="people2" class="com.AL.pojo.People" autowire="byName">
          <property name="name" value="鑫仔"/>
      </bean>
      <!--byType自动装配:会自动在容器上下文中去查找,和自己对象属性类型相同的 bean!-->
      <bean id="people3" class="com.AL.pojo.People" autowire="byType">
          <property name="name" value="鑫仔"/>
      </bean>
      
    • beans.xml:配置类文件。

  • 使用注解开发

    • 定义容器bean的信息。原来是使用bean标签 去进行bean注入到spring容器;在这里

      1. 在开发中使用注解实现bean注入(@component注解表明这是一个组件,等价于标签)。IOC创建对象,容器bean注入spring中
      2. 对于bean对象的属性值使用 @value 注解 赋值。
    • 自动装配bean。获取想要的容器bean,对于一个类,属性可能包含其它自定义类,会想要某一个具体实例。完成自动装配bean,获取想要的对象。

      1. 使用@Autowired。根据类型 byType 寻找对象实例。当有多个实例时,结合@Qualitier 指定特定的id 去获取对象。
      2. 或者使用@Resource。
    • @Configuration,它表示这个类 是一个配置类, 就和我们之前的 beans.xml 一样

    @Component  // 注解说明,表示创建使用 spring bean
    public class User {
        private String name;
        public String getName() {
            return name;
        }
        @Value("xinxin") // 属性注入
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    @Configuration
    public class ALConfig {
    
        @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        public User getUser(){
            return new User();
        }
    }
    
    @Configuration //代表这是一个配置类
    public class ALConfig2 {
        @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        public User getUser2(){
            return new User();
        }
    }
    
    @Configuration
    @Import(ALConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
    public class ALConfig {
    
        // 注册一个 bean,相当于 我们之前写的一个  bean 标签
        // 这个方法的名字就 相当于 bean标签中的 id属性
        // 这个方法的返回值,就相当于 bean标签中的 class属性
        @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        public User getUser(){
            return new User();
        }
    }
    

    测试:

        @Test
        public void test02() {
            /**等价于ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
             * 原先的是xml配置文件中寻找 bean 的配置信息
             * 这里是JavaConfig 中定义了bean的配置信息
             */
            ApplicationContext context = new AnnotationConfigApplicationContext(ALConfig.class);
    
            // ALConfigz中的方法名就是bean的id! getUser()方法,则od为 getUser
            //User getUser = (User) applicationContext.getBean("getUser");
            User getUser = context.getBean("getUser2", User.class);
            System.out.println(getUser.getName());
        }
    

关于@Component和@Autowired 注解之间的区别

  • @Component、@Bean 告诉 Spring:“这是这个类的一个实例,请保留它,并在我请求时将它还给我”。 将这个类的一个实例注册交管给Spring容器中
  • @Autowired、@Qualifiter 说:“请给我一个这个类的实例,例如,一个我之前用@Bean注释创建的实例”。从Spring容器中获取这个类的实例

总之它们之间的关系是:一个进行定义创建具体的对象实例;一个是获取想要的类的具体对象实例。 不过都需要从配置文件中获取上下文解析,才能得到bean的信息。

1.2、什么是SpringBoot?

学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架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 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目【自带web服务器:**Tomcat、Jetty和Undertow**三种Servlet容器嵌入到Web应用程序】
  • 没有冗余代码生成和XML配置的要求【不用像原来 mybatis、数据库、spring中那样写xml配置文件了】

1.3、Spring,Spring MVC,Spring Boot 之间什么关系?

内容来源:https://mp.weixin.qq.com/s/lPE7hE0KmiJONeJmsTMv_g

Spring 包含了多个功能模块(上面刚刚提高过),其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。

下图对应的是 Spring4.x 版本。目前最新的 5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

spring主要模块:

图片

Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

图片

使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!

Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。

Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!

2、什么是微服务架构

Dubbo 官方文档: https://dubbo.apache.org/zh/docs/v2.7/

代码:高内聚,低耦合

微服务架构:

关于微服务架构这个概念的文章: https://martinfowler.com/articles/microservices.html#CharacteristicsOfAMicroserviceArchitecture

究竟什么是微服务呢?我们在此引用ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话:

原文:https://martinfowler.com/articles/microservices.html

汉化:https://www.cnblogs.com/liuning8023/p/4493156.html

就目前而言,对于微服务,业界并没有一个统一的,标准的定义。

但通常而言,微服务架构是一种架构模式,或者说是一种架构风格,它提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值,服务之间采用轻量级的通信机制(HTTP)互相沟通,每个服务都围绕着具体的业务进行构建,并且能狗被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应该根据业务上下文,选择合适的语言,工具(Maven)对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。

  • 微服务更加强调单一职责、轻量级通信(HTTP)、独立性并且进程隔离。
  • 微服务更在解耦合,使每个模块都独立。

原来的单体应用架构(all in one):我们将一个应用中的所有应用服务都封装在一个应用中。无论是ERP、CRM或是其它什么系统,你都能够去访问数据库、web访问,等等各个功能放到一个war包内。这样做的好处是:

  • 易于开发和测试,十分方便部署。当需要扩展的时候,直接将war复制多份,然后放到多个服务器中,再做一个负载均衡。
  • 单体应用架构的缺点是:修改了一个小的地方,都需要停掉整个服务,再重新打包、部署这个应用war包。

微服务架构就是打破了all in one 的架构方式,把每个功能元素独立出来。把独立出来的功能元素进行动态组合完成业务应用。所以微服务架构是对功能元素进行复制,不是对整个应用进行复制。所以 这样做的微服务架构的好处是:

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

3、第一个Springboot程序

3.1、创建一个SpringBoot应用

创建一个SpringBoot应用。准备工作

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

我的环境准备:

  • java version “1.8.0_181”
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

开发工具:

  • IDEA

创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用

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

Quickstart Your Project:快速创建你的项目的

https://start.spring.io/

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

1、打开 https://start.spring.io/

2、填写项目信息

3、点击”Generate Project“按钮生成项目;下载此项目

4、解压项目包,并用IDEA以Maven项目导入 选择 import,而不是打开项目,一路下一步即可,直到项目导入完毕。

5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

image-20210407171859775

image-20220411190907809

选择导入一个maven项目, 而不是 creat 创建一个。

image-20210407171934798

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

1、创建一个新项目

2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现

3、填写项目信息

4、选择初始化的组件(初学勾选 Web 即可)

5、填写项目路径

6、等待项目构建成功

image-20210407171956126

image-20220411222026749

image-20210407172011286

我们开发的时候,一般在IDEA中进行创建就可以了。

先不去添加Spring web 依赖,直接创建 Springboot 项目。创建完成后,运行测试:结果为:

image-20220412092840563

然后导入 web 的依赖: pom.xml配置文件中:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

再次进行测试,这时就出现了Tomcat:

3.2、项目结构分析:

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

image-20210407172317076

1、程序的主启动类

2、一个 application.properties 配置文件

3、一个 测试类

4、一个 pom.xml

  • 程序的主启动类:
package com.AL;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 程序的主入口
@SpringBootApplication
public class Springboot01HellowordApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }
}
  • 对于pom.xml配置文件:pom.xml 分析。

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--有一个父项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.AL</groupId>
    <artifactId>springboot-01-helloword</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-01-helloword</name>
    <description>AL first springboot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--web依赖; tomcat, dispatcherServlet, xml-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring-boot-starter 所有的springboot依赖都是用这个开头的-->
        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
    </dependencies>

    <build>
        <!--打包插件 -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--
                在工作中,很多情况下我们打包是不想执行测试用例的.可能是测试用例不完事,或是测试用例会影响数据库数据
                跳过测试用例执 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <!--跳过项目运行测试用例-->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

pom.xml中主要包括四个部分:

  • 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括:GroupId、ArtifactId、version、name、description等
  • parent:继承 spring-boot-starter-parent 的依赖管理,控制版本与打包等内容
  • dependencies:项目具体依赖,这里包含了
    • spring-boot-starter-web 用于实现HTTP接口(该依赖中包含了Spring MVC)。官网的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器
    • spring-boot-starter-test用于编写单元测试的依赖包。
  • build:构建配置部分。默认使用了 spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。

测试,启动项目。 在浏览器中输入 localhost:8080 出现以下结果,表示访问成功,创建的项目没有问题:

image-20210407185350213

页面能够访问成功,但是我们想要在这个访问的页面查看如何输出 信息。那我们在此去编写一个 http 接口。实现网页跳转。

3.3、编写一个http接口

1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到。如下所示:即和你一开始创建 初始化的 Springboot01HellowordApplication 这个主启动类为 同级目录

image-20210407191014686

2、在包中新建一个HelloController类。代码如下所示: 完成页面跳转

package com.AL.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

// 热部署
@Controller
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello";
    }
}

3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

image-20210407191219513

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

回忆:@Controller + @ResponseBody = @RestController

  • @Controller 单独使用的时候,用于返回一个视图。属于常见的传统的Spring MVC的应用,对应于前后端不分离的情形。
  • @ResponseBody 注解的作用是用于返回Json数据。它不经过视图,直接返回结果。将 Controller 的⽅法返回的对象通过适当的转换器转换为指定的格式之后,写⼊到HTTP 响应(Response)对象的 body 中
  • @RestController 只返回对象,对象数据直接以 JSON 或 XML 形式写⼊ HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是⽬前⽇常开发所接触的最常⽤的情况(前后端分离)。
  • 对于返回的对象,由ObjectMapper将对象解析成json形式 便于返回。

demo: 对于返回的时间对象,json形式输出到前端

    @RequestMapping("/date")
    @ResponseBody //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便
    public String dateController() throws JsonProcessingException {
        //创建一个jackson的对象映射器,用来解析数据
        ObjectMapper mapper = new ObjectMapper();
        Date date = new Date();

        // 自定义日期的格式
        SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");

        //将我们的对象解析成为json格式. ObjectMapper,时间解析后的默认格式为:Timestamp 时间戳
        //String str = mapper.writeValueAsString(date);
        String str = mapper.writeValueAsString(sdf.format(date)); //使用 ObjectMapper 来格式化输出
        return str;
    }

3.4、打包测试:

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

image-20210407191726555

如果没有打包成功,发生错误。 我们可以在配置打包时 跳过项目运行测试用例

在pom.xml 中的 jar打包插件配置中添加 跳过项目运行测试用例的 配置:

    <build>
        <!--打包插件 -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--
                在工作中,很多情况下我们打包是不想执行测试用例的
                可能是测试用例不完事,或是测试用例会影响数据库数据
                跳过测试用例执
                -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <!--跳过项目运行测试用例-->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

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

image-20210407192757832

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

运行打包的 jar包:命令端口的dos命令为:java -jar .\springboot-01-helloWord-0.0.1-SNAPSHOT.jar

java -jar .\springboot-01-helloWord-0.0.1-SNAPSHOT.jar

image-20210407193240594

3.5、Tomcat端口号和Springboot图案修改

我们新建一个springboot项目, 此时不导入web依赖包:即不选择下面的这个 spring web 依赖

image-20210407193610721

项目创建完成后,运行测试,结果为:结果中没有显示端口号8080, 没有tomcat。

image-20210407193723163

然后我们导入maven依赖: spring-boot-starter-web。 这个是 web的依赖,包含tomcat, dispatcherServlet, xml。 这个是 springboot 中的 web场景启动器。 前缀为**spring-boot-starter**,在后面直接加 web 即可。

        <!--web依赖; tomcat, dispatcherServlet, xml-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

此时再次启动项目测试,就可以看到有 tomcat了。如下:

image-20210407194236846

例子:热部署。修改tomcat端口号。

在 controller包中再次添加一个控制器: helloController。

package com.AL.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

//热部署
@Controller
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello";
    }
}

我们在资源配置文件 application,properties中进行更改 Tomcat端口号:

# 更改项目的端口号
server.port=8081

此时启动项目,在浏览器中输入 url路径:http://localhost:8081/hello/hello

image-20210407195236304

表明我们已经改变了 tomcat的端口号为8081。

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。

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

image-20210407195107093

启动项目,观察Java中的结果为:

image-20210407195143648

4、Springboot自动装配原理

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

4.1、pom.xml配置文件

4.1.1、父依赖

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

    <!--有一个父项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

我们点击这个,发现还有一个父项目, 即父依赖:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.4</version>
  </parent>

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

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

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

4.1.2、启动器 spring-boot-starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器。
  • 比如spring-boot-starter-web, 就是web启动器,它会帮我们自动导入web环境所有的依赖!spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

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

在哪里去寻找这些 ‘starter’:https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

4.2、主启动器类

主启动类

分析完了 pom.xml 来看看这个启动类。

package com.AL;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 程序的主入口
// @SpringBootApplication: 表示这个类是一个 springboot的应用
@SpringBootApplication
public class Springboot01HellowordApplication {

    public static void main(String[] args) {
        // 将springboot应用启动
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }
}

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

4.2.1、@SpringBootApplication

参考链接:https://blog.csdn.net/c17315377559/article/details/102734616

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

点击这个@SpringBootApplication 注解,我们可以看到上面有很多其它注解:

image-20210407201032253

@SpringBootApplication这个注解的源码中的@Target、@Retention这两个都是 Java中的元注解,表示这个方法作用的区域、运行的范围。其它的注解应该就是和 启动类有关的:【@Target, 注解所修饰的这个对象的 范围; @Retention(保留,保持,维持) 运行有效范围】

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

4.2.2、@ComponentScan

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

作用:用来将指定包(如果未指定就是将当前类所在包及其子孙包)加入SpringIOC的包扫描,本质上等于context:component-scan配置。自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

其中的注解最终都会有 @Component 组件修饰,表示将其注入到容器 Bean中

4.2.3、@SpringBootConfiguration

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

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

// 点进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}
@Component
public @interface Configuration {}
  • 这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件

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

@Configuration注解是Spring里面的注解,在Spring里,它表示该类也是一个配置类。进入@Configuration注解,我们发现他是被@Component所修饰的,所以该配置类也是一个组件。

4.2.4、@EnableAutoConfiguration

image-20220412130709202

@EnableAutoConfiguration :开启自动配置功能

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

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage:

image-20220412130916076

@AutoConfigurationPackage表示自动注入包,进入@AutoConfigurationPackage里面。发现里面有一个@Import注解。

@Import({Registrar.class})

(1)@Import注解表明给容器导入一个组件,这个组件的内容就由Registrar.class传入的对象决定。进入到该对象中,我们发现

metadata源数据包含@SpringBootApplication修饰的类,包名等信息。所以,

@AutoConfigurationPackage注解的主要作用就是将@SpringBootApplication修饰的类的同包,子孙包下的所有组件都扫描到Spring容器中

AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
  • @import :Spring底层注解@import , 给容器中导入一个组件

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

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

@Import({AutoConfigurationImportSelector.class})

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

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

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

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

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

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {    
    String factoryClassName = factoryClass.getName();    
    //这里它又调用了 loadSpringFactories 方法    
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}

3、我们继续点击查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {    
    //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身    
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);    
    if (result != null) {        
        return result;    
    } else {        
        try {            //去获取一个资源 "META-INF/spring.factories"            
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");            
            LinkedMultiValueMap result = new LinkedMultiValueMap();
            //将读取到的资源遍历,封装成为一个Properties            
            while(urls.hasMoreElements()) {                
                URL url = (URL)urls.nextElement();                
                UrlResource resource = new UrlResource(url);                
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);                
                Iterator var6 = properties.entrySet().iterator();
                while(var6.hasNext()) {                    
                    Entry<?, ?> entry = (Entry)var6.next();                    
                    String factoryClassName = ((String)entry.getKey()).trim();                    
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());                    
                    int var10 = var9.length;
                    for(int var11 = 0; var11 < var10; ++var11) {                        
                        String factoryName = var9[var11];                        
                        result.add(factoryClassName, factoryName.trim());                    
                    }                
                }            
            }
            cache.put(classLoader, result);            
            return result;        
        } 
        catch (IOException var13) {            
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);        
        }    
    }
}

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

4.3、spring.factories

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

META-INF/spring.factories:自动配置的核心文件

image-20211012213731326

WebMvcAutoConfiguration

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

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

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

4.4、结论:

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

image-20210403204845173

image-20210403204109345

image-20210403204137421

image-20220412164036115

4.5、了解主启动如何运行:SpringApplication.run

SpringApplication,不简单的方法

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

package com.AL;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 程序的主入口
// @SpringBootApplication: 表示这个类是一个 springboot的应用
@SpringBootApplication
public class Springboot01HellowordApplication {

    public static void main(String[] args) {
        // 将springboot应用启动
        // SpringApplication 类 run 方法
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }
}

4.5.1、SpringApplication.run分析

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

SpringApplication

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

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

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

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

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

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {    
    // ......    
    this.webApplicationType = WebApplicationType.deduceFromClasspath();    this.setInitializers(this.getSpringFactoriesInstances();    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));    this.mainApplicationClass = this.deduceMainApplicationClass();}

image-20220412224614070

run方法流程分析

run方法源码:

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

	public ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

5、yaml

springboot这个配置文件中到底可以配置哪些东西呢?

  • web
  • webmvc

5.1、application.yml配置文件

配置文件

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

  • application.properties
    • 语法结构:key=value
  • application.yml
    • 语法结构:key:空格 value

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

所以我们对配置文件中的默认值进行修改就好了, 不用把所有的配置文件再导入。比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

server.port=8081

使用 application.properties 配置文件进行端口号修改我们前面已经测试过了。

下面我们用yaml语法去进行配置, 在相同路径下即 resource 静态资源路径下创建一个 application.yml 配置文件:

server:
  port: 8081

启动项目,测试:http://localhost:8081

image-20210407204537528

自我思考

当有多个启动的端口号的时候,该如何选取自己的微服务启动端口? 或者说是有多个微服务?该如何进行选取呢?【GateWay网关】

5.2、yaml概述

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

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

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

  • 传统xml配置:
<server>  
    <port>8081<port>
</server>
  • yaml配置:语法结构为: key:空格 value【空格不能省略】
server: 
  prot: 8080
  • properties配置: key=value
server.port=8081

5.3、yaml基础语法

yaml:语法要求严格!

1、空格不能省略

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

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

在yaml中的语法和 properties中不一样: 如下几个例子:且在yaml中还可以有行内写法,在properties中只能有键值对

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

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

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
    比如 :name: “kuang \n shen” 输出 :kuang 换行 s hen
  • ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
    比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen

2.对象、Map(键值对)

#**对象、Map格式
k:   
  v1:  
  v2:
  • 在下一行来写对象的属性和值的关系,注意缩进;比如:
student:  
  name: alzn  
  age: 3
  • 行内写法:
student: {name: alzn,age: 3}

3.数组( List、set )

  • 用 - 值表示数组中的一个元素,比如:
pets: 
 - cat 
 - dog 
 - pig
  • 行内写法:
pets: [cat,dog,pig]

4.修改SpringBoot的默认端口号

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

demo:

# 对象、map(键值对)
k:
  v1:
  v2:
person:
  name: alzn
  age: 18
# 行内写法:student: {name: alzn,age: 3}

#数组(list set)
pets:
  - cat
  - dog
  - pig
# 行内写法: pets: {cat,dog,pig}

5.4、给属性赋值的几种方式

给属性赋值的方式:【yaml配置文件的注入属性、spring中的注解@Value进行属性注入(或者xml配置文件注入)】

  • yaml文件还能对类进行赋值,即对类的属性赋值。【yaml注入配置文件】

  • 原来的Spring 注解开发中,@Componet表示将其注册到 bean容器中,@Value然后进行赋值。

    • @Component 注册到 bean容器中,表示这是一个组件。@Auotowired 表示从bean容器中获取想要的对象实例

    • @Value 注解表示对属性赋值,类似于标签定义具体的一个bean对象时,属性赋值语句:

      构造器注入:<constructor-arg name="years" value="7500000"/> 
      set注入:<property name="name" value="小明"/>
      

例子:创建实体类:Dog、Person

5.4.1、使用@Value对类的属性赋值

  • 使用**@Component**,表示将普通pojo实例化到spring容器中。 注册到bean 容器中。
  • @Value 注解进行对属性赋值
package com.AL.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component  //使用@Component,表示将普通pojo实例化到spring容器中
public class Dog {
    @Value("小虎")
    private String name;
    @Value("6")
    private Integer age;

    public Dog() {
    }

    public Dog(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 我们使用原来给 bean 注入属性值的方法, @Value, 去进行测试:
@Component  //注册到 bean
public class Dog {
    @Value("小虎")
    private String name;
    @Value("6")
    private Integer age;

在springboot的测试文件中 test文件下的 Springboot01HellowordApplicationTests 对Dog进行输出测试:

package com.AL;

import com.AL.pojo.Dog;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01HellowordApplicationTests {
    @Autowired //将 狗 类自动注入进来。bean自动装配。获取想要的bean对象
    Dog dog;
    @Test
    void contextLoads() {
        System.out.println(dog);  //打印输出 狗对象
    }
}

启动项目,测试的结果为:@Value注入成功,这是我们原来的办法。

Dog{name='小虎', age=6}

5.4.2、使用yaml配置文件配合@ConfigurationProperties(加载配置文件的注解)对属性赋值

创建了一个复杂的Person类, 且在这里实例化了 Dog类:

package com.AL.pojo;

import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Component //注册到 bean容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getHappy() {
        return happy;
    }

    public void setHappy(Boolean happy) {
        this.happy = happy;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

我们通过yaml进行对属性赋值:进行注入。如下:在写maps时利用了行内写法。application.yaml中写入

person:
  name: alzn
  age: 18
  happy: true
  birth: 2002/01/01
  maps: {al: last name, zn: first name}
  lists:
    - learning
    - running
    - dancing
  dog:
    name: xiaohu
    age: 6

还需要在需要的java中的 类进行注解:我们把已经写好的person 对象的所有值写好了, 然后注入到我们的 类 中:

  • @ConfigurationProperties: 这种加载配置文件方式为 从全局配置文件中获取值
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册到 bean容器中
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    ...
}

但是此时的 IDEA却进行提示:springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

image-20210407214720217

但打开后却没有对应的文档。 我们再去springboot的官方文档去 搜索configuration-metadata,可以找到。官方文档地址:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

Configuring the Annotation Processor: 关于配置的注解处理器

To use the processor, include a dependency on spring-boot-configuration-processor.

With Maven the dependency should be declared as optional, as shown in the following example:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

image-20220413113802372

image-20210407214018483

找到后的 maven配置依赖为:导入配置文件处理器:spring-boot-autoconfigure-processor

        <!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure-processor</artifactId>
            <optional>true</optional>
        </dependency>

确认以上配置都OK之后,我们去测试类中测试一下:

package com.AL;

import com.AL.pojo.Dog;
import com.AL.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01HellowordApplicationTests {
    @Autowired //将 狗 类自动注入进来
    //Dog dog;
    Person person;
    @Test
    void contextLoads() {
        //System.out.println(dog);  //打印输出 狗对象
        System.out.println(person);  //打印输出  人 对象
    }
}

结果为:所有的值全部注入成功了。类中所有的属性赋值成功。

Person{name='alzn', age=18, happy=true, birth=Tue Jan 01 00:00:00 CST 2002, maps={al=last name, zn=first name}, lists=[learning, running, dancing], dog=Dog{name='xiaohu', age=6}}

属性注入的方式:使用xml配置开发;使用注解开发。【构造器注入、set注入、c和p命名扩展方式注入】

  • @Value

5.4.3、加载指定的配置文件:@PropertySource

给属性赋值的方式有xnl配置开发(构造器注入、set注入)、使用注解@Value开发完成注入,还有加载配置文件(*.yaml、 *.properties)的方式。

加载配置文件的方式有:

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

  • @configurationProperties:默认从全局配置文件中获取值

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

name=muzhi

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

在这里我们使用了 @PropertySource, 从指定的位置去加载配置文件。

@Component //注册到 bean容器中
//@ConfigurationProperties(prefix = "person")
@PropertySource(value = "classpath:person.properties")
public class Person {
    //SPEL 表达式取出配置文件的值
    @Value("${name}")
    private String name;
    ...
}

我们同样可以用 properties去进行赋值:有可能乱码: 但是这里写 鑫仔 同样乱码了。 是不是因为

image-20210407221258936

结果为:进行测试:指定配置文件绑定成功。

Person{name='muzhi', age=null, happy=null, birth=null, maps=null, lists=null, dog=null}

配置文件中的占位符${}

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

person:
  name: ALZN${random.uuid} # 随机 uuid
  age: ${random.int}  # 随机int
  happy: false
  birth: 2020/02/30
  maps: {A1: L1, Z1: N1}
  lists:
    - code
    - girl
    - music
  dog:
    name: ${person.hello:other}_小虎
    age: 1

结果为:

Person{name='ALZNd29ba368-1cba-45aa-bcd4-4cc0d47a9cbf', age=-1488348305, happy=false, birth=Sun Mar 01 00:00:00 CST 2020, maps={A1=L1, Z1=N1}, lists=[code, girl, music], dog=Dog{name='other_小虎', age=1}}

5.5、回顾properties配置

利用配置文件中的信息去完成 属性赋值 ,在这其中使用的注解(配置文件处理注解)有:

  • @ConfigurationProperties:从全局加载配置文件。 @ConfigurationProperties(prefix = “person”)

  • @PropertySource:从指定位置加载配置文件。

    @PropertySource(value = "classpath:person.properties")
    public class Person {
        //SPEL 表达式取出配置文件的值
        @Value("${name}")
        private String name;
    
  • yaml配置文件、properties配置文件。

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

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

image-20220414203622598

测试步骤:

1、新建一个实体类User

package com.AL.pojo;

import org.springframework.stereotype.Component;

@Component // 将实体类注册到 bean 容器中
public class User {
    private String name;
    private int age;
    private String sex;
}

2、编辑配置文件 user.properties

user.name=鑫仔
user.age=18
user.sex=女

3、我们在User类上使用@Value来进行注入!

package com.AL.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:user.properties")
public class User {
    //直接使用@value
    @Value("${user.name}") //从配置文件中取值
    private String name;

    @Value("#{9*1}")  // #{SPEL} Spring表达式
    //@Value("${user.age}")
    private int age;

    @Value("男")   // 字面量
    //@Value("${user.sex}")
    private String sex;

    public User(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

4、Springboot测试

package com.AL;

import com.AL.pojo.Dog;
import com.AL.pojo.Person;
import com.AL.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01HellowordApplicationTests {
    @Autowired //将 类自动注入进来
    User user;

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

结果正常输出。

对比小结

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

下面这两种方式 进行配置注入时,只要写一次就可以了; 分别是一个从全局寻找配置,另一个是从指定位置去加载配置文件

  • @ConfigurationProperties(prefix = “person”)
  • @PropertySource(value = “classpath:person.properties”)
@ConfigurationProperties@Value
功能批量注入配置文件中的属性一个个指定
松散绑定支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

配置yml和配置properties都可以获取到值 , 强烈推荐 yml

  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

参考链接:https://www.jianshu.com/p/e0b50053b5d3

EL表达式: ${}

  • 获取数据
  • 执行运算
  • 获取web开发的常用对象

我们使用美元符号大括号进行 获取数据和执行运算

获取参数的两种方式:

  • =变量名
  • ${}。 ${ }去获取数据和执行运算

SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。SpEL有三种用法,一种是在注解@Value中;一种是XML配置;最后一种是在代码块中使用Expression。

  1. @Value。@Value(“#{9*1}”) // #{SPEL} Spring表达式; @Value(“${user.name}”) //从配置文件中取值
  2. 配置。
  3. Expression.

5.6、松散绑定

松散绑定:当在 yml中写的 last-name,这个和 lastName 是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下。

我们将 狗 类的名字修改下格式:

  • @ConfigurationProperties: 用于从全局配置文件中寻找。
package com.AL.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component  //使用@Component,表示将普通pojo实例化到spring容器中
@ConfigurationProperties(prefix = "dog")
public class Dog {
    
    private String firstName;
    private Integer age;

    public Dog() {
    }

    public Dog(String firstName, Integer age) {
        this.firstName = firstName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "firstName='" + firstName + '\'' +
                ", age=" + age +
                '}';
    }
}

application.yaml 配置文件: 松散绑定:first-name

  • 一定要注意:yaml语法格式是 key: 空格 value。
dog:
  first-name: xiaohu
  age: 3

测试文件:

package com.AL;

import com.AL.pojo.Dog;
import com.AL.pojo.Person;
import com.AL.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot01HellowordApplicationTests {
    @Autowired //将 狗 类自动注入进来
    Dog dog;
    
    @Test
    void contextLoads() {
        System.out.println(dog);  //打印输出 狗对象
    }
}

测试结果:

Dog{firstName='xiaohu', age=3}

5.7、JSR303校验

数据校验

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

@Validates 为数据校验的注解 以及下方的 @Email 这个是针对特定的,里面必须是Email格式。例子如下:

package com.AL.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册到 bean容器中
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误")  //name必须是邮箱格式
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    ...
}

在这其中,我们需要导入 Email 的maven 依赖:

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>

运行测试结果:显示错误,无法运行。

The Bean Validation API is on the classpath but no implementation could be found
Action:
Add an implementation, such as Hibernate Validator, to the classpath
    ...
    ...
    
Caused by: org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'person': Could not bind properties to 'Person' : prefix=person, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is javax.validation.NoProviderFoundException: Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.

错误提示信息是需要去增加依赖 Hibernate Validator (RI)。

修改的方式: 我重新导入了关于 Validation的依赖:

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.5.Final</version>
        </dependency>

再次运行的结果:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'person' to com.AL.pojo.Person failed:

    Property: person.name
    Value: 鑫仔a5430d4f-1178-4ee9-ae2d-74c1dd22e5ed
    Origin: class path resource [application.yaml] - 22:9
    Reason: 邮箱格式错误
        ...
        ...
Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'person' to com.AL.pojo.Person

修改格式:

person:
#  name: 鑫仔${random.uuid} # 随机 uuid
  name: 1356207897@qq.com
  age: ${random.int}  # 随机int
  happy: false
  birth: 2020/02/30
  maps: {A1: L1, Z1: N1}
  lists:
    - code
    - girl
    - music
  dog:
    name: ${person.hello:other}_小虎
    age: 1

dog:
  name: 小虎${random.uuid}
  age: ${random.int}

此时的测试结果:

Person{name='1356207897@qq.com', age=-1439211545, happy=false, birth=Sun Mar 01 00:00:00 CST 2020, maps={A1=L1, Z1=N1}, lists=[code, girl, music], dog=Dog{name='other_小虎', age=1}}

使用数据校验,可以保证数据的正确性; JSR303常见参数

@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 对象是否符合正则表达式的规则 正则表达式。

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

6、多环境配置及配置文件位置

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

6.1、多配置文件

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

例如:

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

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

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件。在主配置文件中可以通过一个配置来选择需要激活的环境。

demo:

创建几个 properties/yaml 环境配置文件:

image-20210408191705528

在修改 或者 切换配置的环境, 在application.properties中去进行选择激活

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

此时我们在开发环境配置 application-dev.properties 中修改一下 Tomcat端口为9999,

server.port=9999

运行测试:发现此时在java控制台的输出结果中:

2021-04-08 16:32:51.732 INFO 17092 — [ main] com.AL.Springboot2ConfigApplication : The following profiles are active: dev
2021-04-08 16:32:52.303 INFO 17092 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9999 (http)

6.2、yaml的多文档块

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

例子如下:

不用去新建多个环境配置文件, 在 application.yaml配置文件中,可以直接设置多个环境配置文件。

我们选择激活的环境配置文件:这里选择了激活 prod 配置环境

spring:
profiles:
active: prod

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod
    
---
server:
  port: 8082
spring:
  profiles: dev # 设置环境的名称
  
---
server:
  port: 8084
spring:
  profiles: prod # 设置环境的名称

结果:

[ main] com.AL.Springboot2ConfigApplication : The following profiles are active: prod

[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8084 (http)

image-20210408182850647

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

测试例子:

application.yaml 配置文件:

server:
  port: 8081

application.properties 配置文件:

server.port=8082

启动项目,运行的结果为:

No active profile set, falling back to default profiles: default ---- 未设置有效的配置文件,回退到默认配置文件:默认。

然后Tomcat选择的端口号为 8082,表明此时默认的配置文件为 properties 配置文件

2021-04-08 18:33:46.947  INFO 16616 --- [           main] com.AL.Springboot2ConfigApplication      : No active profile set, falling back to default profiles: default
2021-04-08 18:33:47.521  INFO 16616 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8082 (http)
2021-04-08 18:33:47.526  INFO 16616 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

6.3、配置文件加载位置

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

官方外部配置文件说明参考文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-external-config 配置文件加载的位置如下所示: 4个位置。

Config locations are searched in reverse order. By default, the configured locations are classpath:/,classpath:/config/,file:./,file:./config/. The resulting search order is the following:

  1. file:./config/
  2. file:./
  3. classpath:/config/
  4. classpath:/

这里的 classpath 类路径指的是 静态资源配置文件 路径, 等同于 resource。 file指的就是这个整体的项目 project。我们分别创建了这 4 个位置所在的 配置文件,如下所示:

image-20210408185958949

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

  • 优先级1:项目路径下的config文件夹配置文件

  • 优先级2:项目路径下配置文件

  • 优先级3:资源路径下的config文件夹配置文件

  • 优先级4:资源路径下配置文件

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

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

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

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

默认配置文件的优先级测试:

测试例子:我们对上图所创建的四个 application.yaml 配置文件 从上到下依次创建 端口号改变的配置修改:

server:
  port: 8081

server:
  port: 8082
  
server:
  port: 8083
  
server:
  port: 8084

运行结果显示: Tomcat端口号为 8081. 表明优先级最高的就是 第一个文件,即 项目路径下的config文件夹配置文件。

[ main] com.AL.Springboot2ConfigApplication : No active profile set, falling back to default profiles: default
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)

拓展,运维小技巧

指定位置加载配置文件

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

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

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

7、自动配置原理再理解

关于Springboot程序的主启动注解 @SpringBootApplication:【标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用】

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

在导入配置文件时,会解析 spring.factories 文件。里面的内容是配置文件信息,自动配置的核心文件。

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

SpringBoot官方文档中有大量的配置,我们无法全部记住。 网址:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#appendix

如下所示: 这些都是springboot 已经配置的文件:

image-20210408192406047

我们去写的配置文件 和 spring.factories之间有有什么联系, 我们在写 application.yaml 文件的时候,能写什么?

7.1、分析自动配置原理

查看一个资源配置文件:自动配置资源文件即 springboot-autoconfigure

在这个自动配置资源文件下的META-INF/spring.factories中:

image-20210408192922696

我们从 自动导入所有配置的文件中,去寻找一个Http的配置文件, 查看里面有的属性:以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理:

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration(
    proxyBeanMethods = false
)

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
//@EnableConfigurationProperties({HttpProperties.class})  这行代码已经变为了下面的这个。  所以 点击这个  ServerProperties  类查看
@EnableConfigurationProperties({ServerProperties.class})

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

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

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

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //...
}

注意: 在这里,这个版本的源码和狂神说java视频中的源码不一样,更新了应该是。 这里的 http有很多部分 改为了 server 和 servlet。 好像是可以降低版本去查看,点击这个项目的 父项目可以查看版本,父项目中已经导入了所有的版本配置。

点击上方的 @EnableConfigurationProperties({ServerProperties.class}) 这个启动资源配置文件 中的 ServerProperties 类查看:其中部分代码为:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)

和上面的 **HttpEncodingAutoConfiguration(Http编码自动配置)**中的 相对应上了:

//从配置文件中获取指定的值和bean的属性进行绑定
@ConditionalOnProperty(
    prefix = "server.servlet.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

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

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

在我们写 application.yaml 配置文件的时候,写的内容 属性必须是 对应。 因为这个是 配置资源文件已经绑定到的 spring.httpserver.servlet), 所以在你写的时候,必须 属性要一致,才能识别。

我们去配置文件里面试试前缀,查看提示:server.servlet.encoding 。这个对应着编码自动配置类中的 配置文件 形式

image-20210408194907366

上面的 这就是自动装配的原理。

精髓

1、SpringBoot启动会加载大量的自动配置类

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

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

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

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

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

7.2、了解:@Conditional

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

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

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

image-20210408201442797

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

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

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

测试:

# 在我们这个配置文件中能配置的东西,都存在一个固有的规律
# xxxAutoConfiguration:默认值   xxxProperties 和 配置文件绑定,我们就可以使用自定义的配置了。
debug: true

结果中关于类是否有效的词如下:

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

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

Unconditional classes: (没有条件的类)

image-20210408201612927

8、自定义starter

对于springboot中,在主启动类中帮我们定义了很多的启动器。需要哪一个模块,直接添加哪一个模块的启动器 就可以直接使用。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器。
  • 比如spring-boot-starter-web, 就是web启动器,它会帮我们自动导入web环境所有的依赖!

链接:https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483767&idx=1&sn=4c23abf553259052f335086dba1ce80c&scene=19#wechat_redirect

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

命名归约:

官方命名:

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

自定义命名:【命名形式刚好和 官方规定的相反】

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

8.1、编写启动器

  1. 在IDEA中新建一个空的项目:springboot-starter-diy

  2. 新建一个普通的maven项目:AL-springboot-starter

    image-20220416162959836

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

    image-20220416190431636

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

    image-20220416203613233

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

    AL-springboot-starter这个项目中,pom.xml 添加自己设置的starter启动器的依赖。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.AL</groupId>
        <artifactId>AL-springboot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 启动器 -->
        <dependencies>
            <!--  引入自动配置模块 -->
            <dependency>
                <groupId>com.al</groupId>
                <artifactId>al-spring-boot-starter-autoconfigure</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
    </project>
    
  6. 将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是所有的启动器基本配置!

    image-20220416204107309

  7. 编写自己需要定义的服务。

    package com.al;
    
    public class HelloService {
        HelloProperties helloProperties;
    
        public HelloProperties getHelloProperties() {
            return helloProperties;
        }
    
        public void setHelloProperties(HelloProperties helloProperties) {
            this.helloProperties = helloProperties;
        }
    
        public String sayHello(String name){
            return helloProperties.getPrefix() + name + helloProperties.getSuffix();
        }
    }
    
  8. 编写自己定义服务中的配置文件 properties。编写HelloProperties 配置类

    package com.al;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    // 前缀 al.hello
    @ConfigurationProperties(prefix = "al.hello")
    public class HelloProperties {
        private String prefix;
        private String suffix;
    
        public String getPrefix() {
            return prefix;
        }
    
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }
    
        public String getSuffix() {
            return suffix;
        }
    
        public void setSuffix(String suffix) {
            this.suffix = suffix;
        }
    }
    
  9. 编写我们的自动配置类并注入bean,测试!

    package com.al;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ConditionalOnWebApplication //web应用生效
    @EnableConfigurationProperties(HelloProperties.class)
    public class HelloServiceAutoConfiguration {
    
        @Autowired
        HelloProperties helloProperties;
    
        @Bean
        public HelloService helloService(){
            HelloService service = new HelloService();
            service.setHelloProperties(helloProperties);
            return service;
        }
    
    }
    
  10. 在resources编写一个自己的 META-INF\spring.factories

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.al.HelloServiceAutoConfiguration
    
  11. 编写完成后,安装到 maven仓库。

    image-20220416211007528

    Java-SpringBoot-2

学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1PE411i7CV

学习文档: 微信公众号 狂神说 --https://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18&uin=&key=&devicetype=Windows+10+x64&version=63020170&lang=zh_CN&ascene=7&fontgear=2

SpringBoot 官方文档链接 – https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#appendix

SpringBoot Web开发

jar:webapp

自动装配:

  1. 创建应用,选择模块

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

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

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

其实SpringBoot的东西用起来非常简单,因为SpringBoot最大的特点就是自动装配。

使用SpringBoot的步骤:

1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好

2、手动在配置文件中配置部分配置项目就可以运行起来了

3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。

我们在利用 Springboot的时候,能够利用和修改哪些配置,以及增加配置文件呢? 如下所示,我们最终是要通过这两个去进行 环境配置:

  • 向容器中自动配置组件 :xxxAutoconfiguration
  • 自动配置类,封装配置文件的内容:xxxProperties

我们在进行 springboot web 开发要解决的问题

  • 导入静态资源
  • 首页
  • jsp、模板引擎Thymeleaf
  • 装配扩展 SpringMVC
  • 增删查改
  • 拦截器
  • 国际化

1、静态资源导入

1.1、创建一个springboot-web项目

回顾一下 HelloWorld 程序

创建一个 springboot-03-web项目, 且选择了maven依赖 web,选择 web模板: 这是要开始 web 开发的,当然要导入web依赖:

image-20210408202938935

一个简单的 springboot Web项目

1.创建需要的层: controller、dao、pojo层。

此时在控制层 controller 新建一个HelloController 控制器,页面请求响应控制:

package com.AL.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return"hello,word!";
    }
}

2.启动项目,测试。在浏览器中输入 url 路径为 http://localhost:8080/hello。

结果如下所示:表明我们创建的项目没有问题。

image-20210408203537343

1.2、导入静态资源

1.2.1、第一种静态资源映射规则:/webjars/**

我们已经搭建了一个普通的SpringBoot项目,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;
    }
    // 缓存控制
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // webjars 配置
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 静态资源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

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

什么是webjars 呢?

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

使用SpringBoot需要使用Webjars,我们可以去搜索一下:网站:https://www.webjars.org

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

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

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

image-20210408205526715

运行测试:

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

结果如下所示:

image-20210408205651554

1.2.2、第二种静态资源映射规则:导入自定义静态资源

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

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

image-20210408210731993

下面的这个是WebMvcAutoConfiguration.class webmvc的自动配置类, 其中的addResourceHandlers 增加资源处理方法中的代码。和上图对应,红色标记的一个是webjars 配置;另一个是 静态资源配置。

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    super.addResourceHandlers(registry);
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        ServletContext servletContext = this.getServletContext();
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (servletContext != null) {
                registration.addResourceLocations(new Resource[]{new ServletContextResource(servletContext, "/")});
            }

        });
    }
}

我们点击 resourceProperties 即资源配置,可以发现属于常量 Resources ,点击进去得到他的代码: 可以发现,这里标注了能够进行配置的 静态资源 配置文件地址。

public static class Resources {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    private String[] staticLocations;
    private boolean addMappings;
    ...
}

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

对应的,我们也可以在此创建有效的静态资源路径 文件夹:而其中的classpath:/META-INF/resource/ 这个就对应着上面的那个webjars 资源路径。

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

classpath对应的文件夹等级就是 resources 资源文件夹。我们创建以下的几个文件夹,都能够存放我们的静态文件:

image-20210408212018536

测试

我们在这里 public 文件中创建一个静态资源: 1.js 然后启动进行查看,

略略略

我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件。结果如下所示:

image-20210408212228747

然后我们在这几个文件中都创建静态资源为 1.js,去测试这几个静态资源的优先级:

image-20210408212731019

运行结果:

image-20210408212844530

经过测试后:优先级: resources > static(默认) > public

比较思考:

  • 第一种静态资源映射:SpringBoot中封装好的静态资源。地址为 webjars/** ,即"classpath:/META-INF/resources/" 下的文件资源。

image-20220418094140367

外界直接访问地址为:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

  • 第二种静态资源映射:我们自己定义的静态资源,jsp、css文件,保存的地址路径为:“classpath:/resources/”, “classpath:/static/”, “classpath:/public/”。

image-20220418094335674

外界直接访问地址为:http://localhost:8080/1.js

对于这些资源的访问,可以直接去进行获取。第一个是在"classpath:/META-INF/resources/" 路径下的,然后访问的就是它下面的文件地址信息。所以直接是 /webjars/jquery/3.4.1/jquery.js; 对于在"classpath:/public/" 下面这种的,直接 /1.js 访问资源就行。

在测试中,我将 public 下的1.js文件创建成了1.jsp文件。在访问http://localhost:8080/1.jsp 的时候,直接进行了下载功能,获取了这个页面。

1.2.3、自定义静态资源路径

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

image-20210408213145597

spring.web.resources.static-locations=/hello/,classpath:/AL/

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

1.2.4、首页和图标处理

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

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

继续往下滑,可以看到:

private Resource getIndexHtml(String location) {
    return this.getIndexHtml(this.resourceLoader.getResource(location));
}

// 欢迎页就是一个location下的的 index.html
private Resource getIndexHtml(Resource location) {
    try {
        Resource resource = location.createRelative("index.html");
        if (resource.exists() && resource.getURL() != null) {
            return resource;
        }
        .....

欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html。

测试:

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>兄弟,我想飞</h1>
</body>
</html>

结果: 注意,在这里,我们前面让静态资源失效的文件 记得删除掉。

image-20210408215235927

网站图标

关于在Springboot中 网站图标的官方文档说明:

Welcome Page
  • Spring Boot supports both static and templated welcome pages. It first looks for an index.html file in the configured static content locations. If one is not found, it then looks for an index template. If either is found, it is automatically used as the welcome page of the application.
Custom Favicon
  • As with other static resources, Spring Boot looks for a favicon.ico in the configured static content locations. If such a file is present, it is automatically used as the favicon of the application.

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

1、关闭SpringBoot默认图标。

#spring.web.resources.static-locations=/hello/,classpath:/AL/
#关闭默认图标
#spring.mvc.contentnegotiation.favor-parameter=false
#spring.mvc.favicon.enabled=false
spring.mvc.favicon.enabled=false

image-20220418102849258

2、自己放一个图标在静态资源目录下,我放在 public 目录下

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

image-20210408223514132

在 templates目录下的所有页面,只能通过 controller来跳转。这个需要模板引擎的支持。如 thymeleaf。

2、thymeleaf模板引擎

thymeleaf 的官方文档:https://www.thymeleaf.org/

2.1、模板引擎

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

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

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

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

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

image-20210408224108531

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

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

2.2、引入Thymeleaf

怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,即 spring-boot-start- … 这样启动器形式的 maven依赖

我们去在项目中引入一下。给大家三个网址:

  • 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

    或者这个链接: 也是spring的官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

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

        <!--thymeleaf. 我们都是基于 3.x 开发的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

Maven会自动下载jar包,我们可以去看下下载的东西;

image-20210408224808456

Thymeleaf分析

我们前面已经了解了SpringBoot的自动配置原理。规则如下所示:

  • 向容器中自动配置组件 :xxx Autoconfiguration
  • 自动配置类,封装配置文件的内容:xxxProperties

那么Thymeleaf 的自动配置类,为 ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    ...
}

我们可以在其中看到默认的前缀和后缀。即分别为 DEFAULT_PREFIX = “classpath:/templates/” 和DEFAULT_SUFFIX = “.html”。

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

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

测试:

1.在templates文件夹下创建一个 后缀为 html 的文件,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>测试页面</h1>

</body>
</html>

2.在控制层即 controller 中编写一个控制类 IndexController:

package com.AL.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

// 在 templates目录下的所有页面,只能通过 controller来跳转
// 这个需要模板引擎的支持。如 thymeleaf。
//首页控制
@Controller
public class IndexController {
    @RequestMapping("/test")
    public String index(){
        return "test";
    }
}

3.启动项目,进行测试。在浏览器中输入 http://localhost:8080/test 。结果为:

image-20210409103913639

2.3、Thymeleaf语法学习

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!

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

例子:

1.修改测试的请求,想要在页面去 显示传输的数据:IndexController 控制类,去存放一个数据信息

package com.AL.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

// 在 templates目录下的所有页面,只能通过 controller来跳转
// 这个需要模板引擎的支持。如 thymeleaf。
//首页控制
@Controller
public class IndexController {
    @RequestMapping("/test")
    public String index(Model model){
        model.addAttribute("msg","Hello, springboot");
        return "test";
    }
}

2.编写前端页面 test.jtml:

我们使用 thymeleaf,需要在 html 文件中导入 命名空间的约束。 且语法需要遵循 thymeleaf 语法规则。

<!DOCTYPE html>
<!--在这里加入了 thymeleaf 模板引擎。 xmlns:th="http://www.w3.org/1999/xhtml"-->
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>测试页面</h1>
<!--所有的html元素都可以被 thymeleaf 替换接管,  如 th: 元素名-->
<div th:text="${msg}"></div>
</body>
</html>

3.启动项目,运行测试: 在浏览器中输入:http://localhost:8080/test 结果如下:

image-20210409111944632

学习一下 Thymeleaf 的使用语法:文档中的: Attribute Precedence 章节

1.**我们可以使用任意的 th:attr 来替换Html中原生属性的值!**即thymeleaf采用 th: attr 的方式在html文件中进行表示属性和声明判断。

image-20210409113129572

2.那么在表示这些属性的时候, 我们应该用怎样的表达式 去进行赋值和声明判断。

Standard Expression Syntax 这一章节进行了描述。

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: _

例子: 使用 thymeleaf 语法表达式去进行表示属性的值:

1.我们在控制层 controller中编写一个控制器 IndexController ,放置一些数据:

package com.AL.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Arrays;
import java.util.Map;

// 在 templates目录下的所有页面,只能通过 controller来跳转
// 这个需要模板引擎的支持。如 thymeleaf。
@Controller
public class IndexController {
    @RequestMapping("/test")
    public String index(Model model){
        model.addAttribute("msg","Hello, springboot");
        return "test";
    }

    @RequestMapping("/t2")
    public String test2(Map<String,Object> map){
        //存入数据
        map.put("msg","<h1>Hello</h1>");
        map.put("users", Arrays.asList("鑫仔","ALZN"));
        //classpath:/templates/test.html
        return "test";
    }
}

2.在测试页面 test.html中使用 模板引擎 thymeleaf 语法去取出 控制器传递的数据:

<!DOCTYPE html>
<!--在这里加入了 thymeleaf 模板引擎。 xmlns:th="http://www.w3.org/1999/xhtml"-->
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>测试页面</h1>
<!--所有的html元素都可以被 thymeleaf 替换接管,  如 th: 元素名-->
<!--<div th:text="${msg}"></div>-->
<!--转义-->
<div th:text="${msg}"></div>
<!--不转义-->
<div th:utext="${msg}"></div>

<!--遍历数据-->
<!--th:each每次遍历都会生成当前这个标签:官网#9-->
<h4 th:each="user:${users}" th:text="${user}"></h4>

<h4>
    <!--行内写法:官网#12-->
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

</body>
</html>

3.启动测试:在浏览器中输入http://localhost:8080/t2 页面结果如下所示:

测试页面
<h1>Hello</h1>
Hello
鑫仔
ALZN
鑫仔ALZN

我们根据这个 thymeleaf的官方文档去进行使用。链接为:https://www.thymeleaf.org/

3、MVC配置原理

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

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。

  • 途径一:源码分析,
  • 途径二:官方文档!

Springboot中关于 Spring MVC Auto-configuration的官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

在官方文档中, 关于Spring MVC Auto-configuration 的配置介绍:

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 (covered later in this document)).
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
    // Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.

 // 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 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.
    
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
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.

我们去进行对照,观看这个是如何实现的。 查看SpringBoot帮我们自动配置好的 SpringMVC,其中都配置了哪些东西。

image-20220418132530952

WebMvcAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
        ...
}

3.1、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(-2147483648);
    return resolver;
}
// ##############################
@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 uses all the other view resolvers to locate
    // a view so it should have a high precedence
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

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

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

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

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

  • Iterator var5 = this.viewResolvers.iterator();
    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");
            Iterator var5 = this.viewResolvers.iterator();

            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);
                }
                ......

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

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

protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的所有视图解析器
    // ViewRescolver.class 把所有的视图解析器来组合的
    Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
        Iterator var3 = matchingBeans.iterator();

        while(var3.hasNext()) {
            viewResolver = (ViewResolver)var3.next();
            if (this != viewResolver) {
                this.viewResolvers.add(viewResolver);
            }
        }
        ......

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

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

测试:自己写一个视图解析器,查看如何配置成功。

1.在文件夹 config 配置下进行设计一个视图解析器,这个文件夹名称是不能变的,

  • 编写MyMvcConfig 视图解析器,在继承保持原有的MVC配置时,又能增加我们想要的:
  • 扩展 springmvc,围绕着 DispatchServlet。 它去完成处理器调度分配的功能, mvc 模式下的核心
  • 视图解析器需要实现 ViewResolver 接口
package com.AL.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

// 如果你想要 div 一些定制的功能,只要写这个组件,然后把它交给springboot,springboot就会帮我们装配
// 扩展 springmvc  围绕着核心 dispatchservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    // ViewResolver 实现了视图解析器接口的类。 我们就可以把这个当作视图解析器
    @Bean //放到bean中。通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
    private static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }
}

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

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

image-20210409165521774

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

找到 this:

image-20210409165348703

然后找到视图解析器,就能看到我们定义的就在这里了:

image-20210409165328115

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

  • 向容器中添加我们自定义的视图解析器: MyViewResolver 。

  • Bean注入:@Bean //放到bean中【这里采用了注解开发完成bean注入】等价于。

    @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!

  • SpringBoot 会加载配置文件中 所有的 视图解析器信息:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

3.2、转换器和格式化器

格式化:我们试着对 format 格式化这里,对日期的默认格式进行修改。

在 WebMvcAutoConfiguration 这个webmvc自动配置类中 找到格式化转换器:

    @Bean
    public FormattingConversionService mvcConversionService() {
        Format format = this.mvcProperties.getFormat();
        // 拿到配置文件中的格式化规则
        WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
        this.addFormatters(conversionService);
        return conversionService;
    }

点击DateTimeFormatters() 这个类进去 查看默认配置的日期格式:

    public DateTimeFormatters() {
    }

    public DateTimeFormatters dateFormat(String pattern) {
        if (isIso(pattern)) {
   /**
Date format to use. For instance, `dd/MM/yyyy`. 默认的 。 这个是狂神说java中默认的
这里的为 "yyyy-MM-dd"
 */
            this.dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
            this.datePattern = "yyyy-MM-dd";
        } else {
            this.dateFormatter = formatter(pattern);
            this.datePattern = pattern;
        }
        return this;
        ......

修改的地方代码中提示是在: this.properties。 所以进行修改:在appilcation.properties中进行修改时,会有默认的提示:有两种方法,且这两种方法中 一种默认的是dd/MM/yyyy`;另外一种如下图所示:

image-20210409170922335

image-20210409170826656

#spring.web.resources.static-locations=/hello/,classpath:/AL/
#关闭默认图标
#spring.mvc.contentnegotiation.favor-parameter=false
#spring.mvc.favicon.enabled=false
#spring.mvc.favicon.enabled=false

#自定义的配置日期格式
#spring.mvc.format.date-time=

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

3.3、修改SpringBoot的默认配置

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

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

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

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

扩展使用SpringMVC 官方文档如下:

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.

扩展视图解析器:加入用户配置+springboot默认的

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

例子:将用户配置的视图解析器和自动配置的结合起来:

用户的解析器:

package com.AL.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

// 如果你想要 div 一些定制的功能,只要写这个组件,然后把它交给springboot,springboot就会帮我们装配
// 扩展 springmvc  围绕着核心 dispatchservlet
@Configuration
//@EnableWebMvc  // 这个就是导入了一个类: DelegatingWebMvcConfiguration: 从容器中获取所有的 webmvcConfig
public class MyMvcConfig implements WebMvcConfigurer {

    // ViewResolver 实现了视图解析器接口的类。 我们就可以把这个当作视图解析器
    @Bean //放到bean中
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/t1 , 就会跳转到test页面;
        registry.addViewController("/t1").setViewName("test");
    }
}

运行测试结果:

image-20210409172021194

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

WebMvcAutoConfiguration类中的 WebMvcAutoConfigurationAdapter:

// Defined as a nested config to ensure WebMvcConfigurer is not read when not
	// on the classpath
	@SuppressWarnings("deprecation")
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

		private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

		private final Resources resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

		private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;

		private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;

		private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

		private ServletContext servletContext;
...
    }

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

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

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

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

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

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }

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

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

    protected void addViewControllers(ViewControllerRegistry registry) {
        this.configurers.addViewControllers(registry);
    }

5、我们点进去看一下

    public void addViewControllers(ViewControllerRegistry registry) {
        Iterator var2 = this.delegates.iterator();

        while(var2.hasNext()) {
            // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
            WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
            delegate.addViewControllers(registry);
        }
    }

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

3.4、全面接管SpringMVC

官方文档:

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

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

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

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

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

image-20210409174110283

给配置类加上注解:@EnableWebMvc 这里有问题。清除浏览器的缓存,进行测试就没有问题了。

image-20210409174019865

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

当然,我们开发中,不推荐使用全面接管SpringMVC【就是表示由程序猿全面接管,不使用SpringBoot中自带默认的配置】

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

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

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

2、它继承了一个父类 WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { 
// ......
}

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

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
  ValidationAutoConfiguration.class })

public class WebMvcAutoConfiguration {
}

总结一句话:@EnableWebMvc 将 WebMvcConfigurationSupport 组件 导入进来了;而导入的 WebMvcConfigurationSupport 只是SpringMVC最基本的功能

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

9、整合JDBC

SpringData简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库。Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

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

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

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

https://docs.spring.io/spring-data/commons/docs/2.4.7/reference/html/#reference

spring boot中 数据库相关的启动器:

image-20220429102602195

整合JDBC

回顾JDBC

原先关于数据库连接的.

JDBC进行java 数据库连接:

  1. 导入相关依赖 pom 或者jar包

  2. 数据库的配置信息:url地址、数据库表、用户名和密码.【通过数据库配置文件建立数据库连接】

  3. 进行数据库连接。创建数据库连接对象,用于执行sql语句。

    //4.执行SQL的对象  Statement 表示执行SQL的对象,此时就相当于 school.student这个表
    Statement statement = connection.createStatement();
    //5.执行SQL的对象去执行 sql 可能存在结果,查看返回结果
    String sql="select * from student";
    ResultSet resultSet = statement.executeQuery(sql);
    
    conn= JdbcUtils.getConnection();//获取数据库连接
    //预编译sql 先写sql 然后不执行.     使用?占位符代替参数
    String sql="INSERT INTO `student` VALUES(?,?,?,?,?,?,?)";
    st=conn.prepareStatement(sql);//获取执行sql对象
    
  4. 执行具体的sql语句。数据库的事务。事务的ACID原则【原子性、一致性、隔离性、持久性】

  5. 释放连接。

通过数据库配置文件 建立数据库连接:

// 获取类加载器,然后得到数据库的资源
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");

Mybatis框架中的JDBC:

  • 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
  • SqlSessionFactory 的实例需要通过SqlSessionFactoryBuilder 来得到。
  • 这个SqlSessionFactoryBuilder 是要在xml配置文件中进行配置 config, 然后才能去构建SqlSessionFactory 实例。

通过SqlSessionFactory 获取 SqlSessionSqlSession 提供了在数据库执行 SQL 命令所需的所有方法

// sqlSessionFactory --> sqlSession
public class MybatisUtils {

// 创建这个sqlSessionFactory,是从资源文件resource中的去获得。
private static SqlSessionFactory sqlSessionFactory;

static {
    try {
        //利用mybatis第一步: 获取一个sqlSessionFactory对象
        String resource = "mybatis-config.xml";
        /**  注意:不需要修改,是因为导入的包不正确.不是 import javax.annotation.Resources
         * 将ResourcesgetResourceAsStream(resource);改为
         * Resources.class.getResourceAsStream(resource);
         *  //得到配置文件流
         * InputStream inputStream = Resources.class.getResourceAsStream(resource);
         */
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
    //SqlSession sqlSession = sqlSessionFactory.openSession();
    //return sqlSession;
    return sqlSessionFactory.openSession();
}

}

springboot-data-jdbc

JDBC:建立数据库连接

在这里:使用spring data 去完成这些工作任务。

  • 创建一个 springboot-04-data 项目: 如下所示,选择了 JDBC API和数据库连接 MySQL Driver 的maven依赖。

image-20220429162604371

在创建该项目的时候,发现出现错误。提示: spring boot connect timed out。解决方法:

https://blog.csdn.net/qq_34595352/article/details/103989417

在菜单栏中:

  • File > settings > Appearance & Behavior > System Settings > HTTP Proxy
  • 选择Auto-detect proxy setting,点击Check connection,输入url地址:https://start.spring.io
  • 成功连接后,去重新创建该spring boot项目。

image-20220429182535495

  • 项目建立之后,可以发现自动帮我们导入的启动器和其它依赖:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 编写数据库配置文件。 yaml 配置文件用于去连接数据库,application.yaml :
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    #?serverTimezone=UTC解决时区的报错

我们编写好配置文件之后,就可以直接去进行使用。因为SpringBoot已经去进行默认配置的时候完成了自动配置。【这就是Spring Boot的优势和创建的一部分意义/由来的原因吧。它并不是一个新的框架,它只是整合了spring、MVC和其它的MySQL、redis等配置。一个脚手架,能够快速地进行开发。开箱即用,“约定大于配置”】

  • 在Test中创建测试类进行测试。@SpringBootApplication 注解,Spring Boot完成自动配置。
package com.al;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04JdbcDataApplicationTests {

    // DI 注入数据源。从spring IOC 容器中获取数据配置信息
    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看默认的数据源: com.zaxxer.hikari.HikariDataSource
        System.out.println(dataSource.getClass());
        // 获得数据库连接:
        Connection connection = dataSource.getConnection();
        System.out.println(connection); // HikariProxyConnection@522082506 wrapping com.mysql.cj.jdbc.ConnectionImpl@57f847af

        connection.close(); // 关闭连接
    }
}

从结果中,可以看到spring boot中默认给我们配置的数据源是: com.zaxxer.hikari.HikariDataSource。我们并没有手动配置。

如果测试中出现 时区异常的错误,这是从 mysql8中开始有时区这个功能。对此,我们可以在 yaml配置中修改数据库配置信息:

# 如果时区报错了,则添加一个时区的配置。 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

我们进行全局搜索,找到数据源的所有自动配置在: 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 配置类中。默认使用的数据源是 Hikari.class。

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

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

和数据库建立了连接,就可以创建数据库sql语句执行对象,进行CRUD的操作。在此之前还需要了解一个JDBCTemplate 对象。

JDBCTemplate

spring中的数据库操作:

  1. 得到数据源(com.zaxxer.hikari.HikariDataSource),可以去建立数据库连接(java.sql.Connection),连接之后,就可以使用原生的JDBC 语句方式对数据库进行操作
  2. 不使用第三方的数据库操作框架,如Mybatis等。Spring本身也对原有的JDBC进行了轻量级的封装,即JDBCTemplate。
  3. JdbcTemplate中具有数据库操作的所有CRUD方法。
  4. Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
  5. JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JDBCTemplate中提供的主要操作方法:

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

demo:

编写一个控制器 controller,注入 jdbcTemplate,编写测试方法进行访问测试。

  • 在查询数据库信息的时候,我们这里没有创建对应的实体类(属性和字段对应)。那么如何获取这些数据库中的数据信息呢?
  • 使用 Map。Map 中的 key 对应数据库的字段名,value 对应数据库的字段值

返回的全部是json字符串:在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !

@ResponseBody // 它就不会走视图解析器,会直接返回一个字符串。

@Controller + @ResponseBody = @RestController

package com.al.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class JdbcController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询user表中所有数据. 当没有实体类时,使用 map 方式
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("userList")
    public List<Map<String, Object>> userList(){
        String sql = "select * from user";
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }
}

测试,查看能否在前端输出数据。【返回json 字符串】

[{"id":0,"name":"鑫仔","pwd":"1234567"},{"id":1,"name":"阿鑫","pwd":"7654321"},{"id":2,"name":"胖纸","pwd":"2333"},{"id":4,"name":"jack","pwd":"978654321"},{"id":5,"name":"clearlove","pwd":"77777"}]

image-20220429202655989

对于增删查改的代码:

package com.al.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class JdbcController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询user表中所有数据. 当没有实体类时,使用 map 方式
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("userList")
    public List<Map<String, Object>> userList(){
        String sql = "select * from user";
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }

    //新增一个用户
    @GetMapping("/addUser")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into mybatis.user(id, name, pwd)" +
                " values(6,'新新','654321')";
        jdbcTemplate.update(sql);
        //查询
        return "add-Ok";
    }

    //修改用户信息
    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update mybatis.user set name=?,pwd=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "船长";
        objects[1] = "23333333";
        jdbcTemplate.update(sql,objects);
        return "update-Ok";
    }

    // 删除一个用户
    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        String sql = "delete from mybatis.user where id = ?";// 预编译语句sql
        jdbcTemplate.update(sql, id);
        return "deleteUser-OK";
    }
}

测试,能够成功地完成CRUD的操作。关于数据库的CRUD操作,使用JDBC就可以完成了。

10、整合Druid数据源

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 基本配置参数如下:

通过 spring 配置文件application-context.xml中的dataSource配置说明各个参数的配置。【链接来源:https://www.cnblogs.com/halberd-lee/p/11304790.html

属性说明建议值
url数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下:
mysql : jdbc:mysql://ip:port/dbname?option1&option2&…
oracle : jdbc:oracle:thin:@ip:port:oracle_sid
username登录数据库的用户名
password登录数据库的用户密码
initialSize启动程序时,在连接池中初始化多少个连接10-50已足够
maxActive连接池中最多支持多少个活动会话
maxWait程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池100
没有可用连接,单位毫秒,设置-1时表示无限等待
minEvictableIdleTimeMillis池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将见说明部分
回收该连接,要小于防火墙超时设置
net.netfilter.nf_conntrack_tcp_timeout_established的设置
timeBetweenEvictionRunsMillis检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
keepAlive程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执true
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超
过minIdle指定的连接个数。
minIdle回收空闲连接时,将保证至少有minIdle个连接.与initialSize相同
removeAbandoned要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该false,当发现程序有未
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。正常close连接时设置为true
removeAbandonedTimeout设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此应大于业务运行最长时间
值后,druid将强制回收该连接,单位秒。
logAbandoned当druid强制回收连接后,是否将stack trace 记录到日志中true
testWhileIdle当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效)true
validationQuery检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果
正常返回,则表示连接可用,否则表示连接不可用
testOnBorrow程序 申请 连接时,进行连接有效性检查(低效,影响性能)false
testOnReturn程序 返还 连接时,进行连接有效性检查(低效,影响性能)false
poolPreparedStatements缓存通过以下两个方法发起的SQL:true
public PreparedStatement prepareStatement(String sql)
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency)
maxPoolPrepareStatementPerConnectionSize每个连接最多缓存多少个SQL20
filters这里配置的是插件,常用的插件有:stat,wall,slf4j
监控统计: filter:stat
日志监控: filter:log4j 或者 slf4j
防御SQL注入: filter:wall
connectProperties连接属性。比如设置一些连接池统计方面的配置。
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
比如设置一些数据库连接属性:

配置数据源

  • 添加Druid数据源依赖。因为我们想要在选择 Druid数据源时,此配置还选取日志 log4j,所以也导入这个依赖:
        <!-- Druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- log4j日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
  • 切换数据源

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

直接在配置文件中选择 druid 数据源:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    #?serverTimezone=UTC解决时区的报错. # 如果时区报错了,则添加一个时区的配置。 serverTimezone=UTC
    type: com.alibaba.druid.pool.DruidDataSource # 修改默认的数据源

测试获取数据源:

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

    package com.al;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    @SpringBootTest
    class Springboot04JdbcDataApplicationTests {
        // DI 注入数据源。从spring IOC 容器中获取数据配置信息
        @Autowired
        DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            // 查看默认的数据源: 
            System.out.println(dataSource.getClass());
            // 获得数据库连接:
            Connection connection = dataSource.getConnection();
            System.out.println(connection); 
            connection.close(); // 关闭连接
        }
    }
    

结果为:

class com.alibaba.druid.pool.DruidDataSource
com.mysql.cj.jdbc.ConnectionImpl@94e51e8

成功切换数据源为 druid。

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

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    #?serverTimezone=UTC解决时区的报错. # 如果时区报错了,则添加一个时区的配置。 serverTimezone=UTC
    type: com.alibaba.druid.pool.DruidDataSource # 修改默认的数据源


    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    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

绑定DruidDataSource 全局配置

我们在配置文件中定义了 Druid 数据源的配置参数信息,那么需要绑定自定义数据源配置 yaml文件。

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

DruidConfig :

package com.al.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@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();
    }
}

测试,获取 druid 数据源的配置参数:

package com.al;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04JdbcDataApplicationTests {

    // DI 注入数据源。从spring IOC 容器中获取数据配置信息
    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看默认的数据源: com.zaxxer.hikari.HikariDataSource
        System.out.println(dataSource.getClass());
        // 获得数据库连接:
        Connection connection = dataSource.getConnection();
        System.out.println(connection); // HikariProxyConnection@522082506 wrapping com.mysql.cj.jdbc.ConnectionImpl@57f847af

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        connection.close(); // 关闭连接
    }
}

结果:

image-20220429211814910

配置Druid数据源监控

Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。

所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;

package com.al.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@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();
    }

    //配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
    @Bean
    public ServletRegistrationBean statViewServlet() {
        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", "123456"); //后台管理界面的登录密码

        //后台允许谁可以访问
        //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 , 结果:

image-20220429212357422

登录后,里面的内容信息:

image-20220429212502969

我们可以去添加一个过滤器功能:

//配置 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;
}

11、整合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.1.1

创建一个新的项目 springboot-05-mybatis。 需要选择的依赖有: web、 MySQL中的 JDBC API和数据库连接 MySQL Drvier:【导入的依赖和前一节 springboot-04-data 一样,同样的,我们建立数据库的连接,关于JDBC的封装,便于对数据库进行CRUD的操作】

然后需要导入 mybatis的maven依赖: mybatis自己的是一个整合:

        <!--mybatis的maven依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

整合测试:

数据库连接

  • 数据库的配置信息,去建立连接。

在这里,使用application.properties 配置文件。

# 数据库配置信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.name=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

在测试类中,去测试数据库连接是否成功。

package com.al;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot05MybatisApplicationTests {

    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass()); //查看默认的数据源
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

}

测试结果,成功完成数据库连接。默认的数据源结果如下:

class com.zaxxer.hikari.HikariDataSource
HikariProxyConnection@716333944 wrapping com.mysql.cj.jdbc.ConnectionImpl@f9f3928

刚才出现了错误:

java.sql.SQLException: Access denied for user 'ALZN'@'localhost' (using password: YES)

但我写了数据库名字和密码啊。重新看一下代码,发现有些变量对应问题写错了。

  • 参数 名字 写错了,改成如下所示:
spring.datasource.username=root

成功地连接了数据库。

创建实体类

创建实体类,和数据库表相互对应。属性和字段一一对应。ORM(object relational mapping)

在这里,为了简化开发,导入lombok 依赖:

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

pojo包中的User 实体类:

package com.al.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

整合mybatis

在资源配置文件中,要先注意去整合 mybatis。

application.properties:

# 整合mybatis
mybatis.type-aliases-package=com.al.pojo
mybatis.mapper-locations=classpath:mybatis/dao/*.xml

image-20220430214357323

Dao层

在dao层创建操作底层数据库的 mapper 和 mapper.xml文件。

mapper接口和mapper接口实现类的映射文件。

UserMapper:

  • @Mapper注解表示这是一个 mybatis 的mapper 类
  • @Repository //注解@Repository表明把这个类注入到spring容器中,专用于dao层
package com.al.dao;

import com.al.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

// @Mapper注解表示这是一个 mybatis 的mapper 类
@Mapper
@Repository //@Repository的作用和@Controller一样,表明把这个类注入到spring容器中,专用于dao层
public interface UserMapper {
    List<User> queryUserList();
    User queryUserById(int id);
    int addUser(User user);
    int updateUser(User user);
    int deleteUser(int id);
}

@Component 和三个衍生注解:@Controller:web层、@Service:service层、

关于@Component和@Autowired 注解之间的区别

  • @Component、@Bean 告诉 Spring:“这是这个类的一个实例,请保留它,并在我请求时将它还给我”。 将这个类的一个实例注册交管给Spring容器中
  • @Autowired、@Qualifiter 说:“请给我一个这个类的实例,例如,一个我之前用@Bean注释创建的实例”。从Spring容器中获取这个类的实例

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
-->
<mapper namespace="com.al.dao.UserMapper">
    <!--select 查询语句 -->
    <select id="queryUserList" resultType="com.al.pojo.User">
    select * from mybatis.user
  </select>
    <select id="queryUserById" resultType="com.al.pojo.User">
    select * from mybatis.user where id = #{id}
  </select>

<!--    insert 插入语句-->
    <insert id="addUser" parameterType="User">
        insert into user (id,name pwd) values (#{id},#{name},#{pwd})
    </insert>

    <update id="updateUser" parameterType="User">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>

    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>
</mapper>

一下子自己想到的原先出现的一个错误:

在IDEA中开发时,出现的一个问题:

下面的 ref=“bookMapper” 爆红。

image-20220407104557263

<!--BookServiceImpl注入到IOC容器中-->
<bean id="BookServiceImpl" class="com.AL.service.BookServiceImpl">
<property name="bookMapper" ref="bookMapper"/>
</bean>

这个 ref 引用的是 bookMapper,即是dao层中BookMapper(它在spring-dao.xml中注册到了spring容器:)。

爆红的原因,这说明没有把这个 bookMapper给引入进去。 解决: 在设置中 Project Structure中,把这个几个 xml配置文件添加到 spring中:

image-20220407104932581

配置静态资源导出的问题:

    <!--静态资源导出问题-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

Controller层

编写一个控制器 UserController 去进行测试。

package com.al;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot05MybatisApplicationTests {

    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass()); //查看默认的数据源
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

}

启动程序,访问 http://localhost:8080/queryUserList 成功返回User的所有信息的 json 字符串。

对于增删改的代码:

    // 添加一个用户
    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(7, "阿毛", "7777777777"));
        return "ojbk";
    }

    // 更新一个用户
    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(5, "诀绝子", "hhhhhhhh"));
        return "update:- ok的啦";
    }

    //删除一个用户
    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        userMapper.deleteUser(id);
        return "delete---ok";
    }

进行测试,成功。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值