学静思语
Published on 2025-02-14 / 2 Visits
0
0

SpringBoot

Spring Boot学习

一、Spring Boot基本介绍

1.官方文档[以3.2.8为例]

  • 官网网址:https://spring.io/projects/spring-boot
  • 学习文档:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/
  • PDF:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/pdf/spring-boot-reference.pdf
  • API:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/api/

2.Spring Boot是什么

  • Spring Boot 可以轻松创建独立的、生产级的基于Spring的应用程序
  • Spring Boot 直接嵌入Tomcat、Jetty或Undertow,可以“直接运行” SpringBoot应用程序

3.Spring Boot快速入门

3.1 maven项目下的pom.xml文件配置

<?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.leon</groupId>
    <artifactId>spring-boot-quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <!--导入Spring Boot 父工程-规定写法-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <!--解决报错问题,错误:
        maven'parent.relativePath' of POM com.leon:spring-boot-quickstart:1.0-SNAPSHOT
         (C:\software\javacodelibrary\distributed-microservices\spring-boot-quickstart\pom.xml)
         points at com.leon:distributed-microservices instead of
         org.springframework.boot:spring-boot-starter-parent,
        please verify your project structure
        -->
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--导入web项目场景启动器:会自动导入和web开发相关的依赖[库/jar]-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.3</version>
        </dependency>
    </dependencies>

</project>

3.2 创建一个MainAPP.java

package com.leon.springboot;

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

/**
 * ClassName:MainApp
 * Package:com.leon.springboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/6 21:47
 * @Version: 1.0
 */
//@SpringBootApplication 表示这是一个Spring Boot应用/项目
@SpringBootApplication
public class MainApp {

   public static void main(String[] args) {

      //启动Spring Boot 应用程序/项目
      SpringApplication.run(MainApp.class,args);

   }

}

3.3 在MainApp.java包的子包下创建controller包,在创建HelloController.java

package com.leon.springboot.controller;

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

/**
 * ClassName:HelloController
 * Package:com.leon.springboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/6 21:51
 * @Version: 1.0
 */
@RequestMapping("/hello")
@Controller
public class HelloController {


    @RequestMapping("/test")
    @ResponseBody
    public String hello() {

        return "Hello World";

    }

}

3.4 快速入门小结

  • Spring Boot 比较传统的SSM开发,简化整合步骤,提高开发效率

  • 简化了Maven项目的pom.xml依赖导入,可以说是一键导入,如图:

    image-20240706220036970

  • 引入一个spring-boot-starter-web,到底发生了什么,如图:

    image-20240706220319303

  • 内置Tomcat,简化服务器的配置

4.Spring SpringMVC Spring Boot 的关系

4.1 梳理关系

  • 关系:Spring Boot > Spring > Spring MVC
  • Spring MVC 只是Spring处理WEB层请求的一个模块/组件,SpringMVC 的基石是Servlet
  • Spring 的核心是IOC和AOP,IOC提供了依赖注入的容器,AOP解决了面向切面编程
  • Spring Boot 是为了简化开发,推出的封神框架(约定优于配置[COC],简化了Spring项目的配置流程),Spring Boot包含很多组件/框架,Spring就是最核心的内容之一,也包含Spring MVC
  • Spring家族,有众多衍生框架和组件例如Boot、security、jpa等,它们的基础都是Spring

4.2 如何理解-约定优于配置

  • 约定优于配置(Convention over Configuration / COC),又称按约定编程,是一种软件设计规范,本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)
  • 例如在模型中存在一个名为User的类,那么对应到数据库会存在一个名为user的表,只有在偏离这个约定时才需要做相关的配置(例如你想将表名命名为t_user等非user时才需要写关于这个名字的配置)
  • 简单来说就是假如你所期望的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待值时,才需要对约定进行替换配置
  • 约定优于配置理念[为什么要搞一个约定优于配置]:约定其实就是一种规范,遵守了规范,那么就存在通用性,存在通用性,那么事情就会变得相对简单,程序员之间的沟通成本会降低,工作效率就会提升,合作也会变得更加简单
  • 所谓的约定优于配置并不是说约定的优先级比配置高,而是如果是符合约定,则按约定,如果不合符约定,则按配置来。需要通过规范性、通用性、效率等方面去理解,不要理解成做了配置,配置的优先级更低。

二、依赖管理和自动配置

1. 依赖管理

1.1 什么是依赖管理

  • spring-boot-starter-parent还有父项目,声明了开发中常用的依赖的版本号

  • 并且进行自动版本仲裁,即如果程序员没有指定某个依赖jar的版本,则父项目指定的版本为准

    image-20240707204926353

    image-20240707205738544

1.2 修改自动仲裁/默认版本号

  • 需求说明:将Spring Boot Mysql驱动修改成5.1.49

    image-20240707210214120

  • 查看spring-boot-dependencies.pom里面规定当前依赖的版本对应的key,这里是mysql.version

  • 方案一:修改项目的pom.xml配置文件,当刷新Maven时,就会更新到新的mysql驱动

    image-20240707212225446

  • 方案二:使用spring-boot-dependencies.pom.xml中的配置方式,因为Maven对于依赖的配置使用的是就近原则

    image-20240707212329868

2.starter场景启动器

2.1 starter场景启动器基本介绍

  • 开发中我们引入了相关场景的starter,这个场景中所有的相关依赖都引入进来,比如我们做WEB开发引入了,该starter将导入语WEB相关的所有包

    image-20240707212639597

    image-20240707212716288

  • 依赖树:可以看到spring-boot-starter-web,帮我们引入了spring-webmvc,spring-web开发模块,还引入了spring-boot-starter-tomcat场景,spring-boot-starter-json场景,这些场景下面又引入了一大堆相关的包,这些依赖可以快速启动和运行一个项目,提高开发效率

  • 所有场景启动器最基本的依赖就是spring-boot-starter,前面的依赖树分析可以看到,这个依赖也就是Spring Boot自动配置的核心依赖

    image-20240707213526575

2.2 官方提供的starter

  • 地址: https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/using.html#using.build-systems.starters

  • 介绍

  • 在开发中我们经常会用到spring-boot-starter-xxx,比如spring-boot-starter-web,该场景是用作web开发,也就是说xxx是某种开发场景

  • 只要引入starter,这个场景的所有常规需要的依赖我们都自动引入

  • Spring Boot 支持的场景如下:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/using.html#using.build-systems.starters

    image-20240707214903374

2.3 第三方starter

  • Spring Boot 也支持第三方starter
  • 第三方starter不是从spring-boot开始,因为这是官方spring-boot保留的命名方式的。第三方启动程序通常以项目名称开头。例如,名为thirdpartyproject的第三方启动程序项目被命名为thirdpartyproject-spring-boot-starter。
  • 也就是说:xxx-spring-boot-starter是第三方为我们提供的简化开发的场景启动器

3. 自动配置

3.1 自动配置基本介绍

  • 前面学习SSM整合时,需要配置Tomcat、配置SpringMVC、配置如何扫描包、配置字符过滤器、配置视图解析器、文件上传等[如下图],非常麻烦。而在Spring Boot中,存在自动【配置机制,提高开发效率。

  • 简单回顾以前SSM整合

    image-20240707215905376

3.2 Spring Boot 自动配置了哪些

  • 自动配置Tomcat

    image-20240708203326830

  • 自动配置Spring MVC

    image-20240708203418894

  • 自动配置Web常用功能:比如字符过滤器,提示:可以通过获取IOC容器,查看容器创建的组件来验证

  • 方式一:通过代码的方式查看

    package com.leon.springboot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    /**
    
  • ClassName:MainApp

  • Package:com.leon.springboot

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/6 21:47

  • @Version: 1.0
    */
    //@SpringBootApplication 表示这是一个Spring Boot应用/项目
    @SpringBootApplication
    public class MainApp {

    public static void main(String[] args) {

    //启动Spring Boot 应用程序/项目
    ConfigurableApplicationContext ioc =
            SpringApplication.run(MainApp.class, args);
    
    //获取注入的bean的名称
    String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
    //循环遍历数组
    for (String beanDefinitionName : beanDefinitionNames) {
       System.out.println( "beanDefinitionName---" +  beanDefinitionName);
    }
    

    }

    }

    ![image-20240708203956844](C:\Users\admin\Desktop\分布式-微服务\image-20240708203956844.png)
    
  • 方式二:Debug查看

    image-20240708204349929

  • 自动配置:默认扫描包结构,官方文档: https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/using.html#using.structuring-your-code.using-the-default-package

    image-20240708205228472

3.3 如何修改默认配置

3.3.1 如何修改默认扫描结构
  • 需求:要求能扫描com.leon 包下的HiController.java

  • 创建:在com.leon包下创建HiController.java,进行测试

    package com.leon;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
    
  • ClassName:HiController

  • Package:com.leon

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/8 20:55

  • @Version: 1.0
    */
    @RequestMapping(“/hi”)
    @Controller
    public class HiController {

    @RequestMapping(“/test”)
    @ResponseBody
    public String hi() {

    return "Hi, Spring Boot";
    

    }

    }

  • 修改MainAPP.java,增加扫描包

    package com.leon.springboot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    /**
    
  • ClassName:MainApp

  • Package:com.leon.springboot

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/6 21:47

  • @Version: 1.0
    /
    /

      1. @SpringBootApplication 表示这是一个Spring Boot应用/项目
      1. @SpringBootApplication(scanBasePackages = {“com.leon”})其中的scanBasePackages = {“com.leon”} 表示指定
    • Spring Boot 要扫描的包和子包,scanBasePackages属性是一个数组,可以有多个值,也就是说可以写多个要扫描的包,例如:
      
    • scanBasePackages={"com.leon","xxx.yyy.ccc",...}
      
    • */
      @SpringBootApplication(scanBasePackages = {“com.leon”})
      public class MainApp {

      public static void main(String[] args) {

      //启动Spring Boot 应用程序/项目
      ConfigurableApplicationContext ioc =

          SpringApplication.run(MainApp.class, args);
      

      //获取注入的bean的名称
      String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
      //循环遍历数组
      for (String beanDefinitionName : beanDefinitionNames) {
      System.out.println( “beanDefinitionName—” + beanDefinitionName);
      }

      }

    }

3.3.2 application.properties 配置大全
  • Sppring Boot 项目最重要也是最核心的配置文件就是application.properties,所有的框架配置都可以在这个配置文件中说明
  • 官方网址:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/application-properties.html#appendix.application-properties.core
  • 其它参考网址1:https://blog.csdn.net/u014135369/article/details/112172329 网址2:https://blog.csdn.net/pbrlovejava/article/details/82659702
3.3.3 application.properties 修改配置
  • 各种配置都有默认,可以在 resources/application.properties修改,application.properties文件我们可以手动创建

    #默认server.port=8080
    #设置Tomcat监听端口号
    server.port=8080
    #默认spring.servlet.multipart.max-file-size=1MB
    #该属性可以指定Spring Boot 上传文件大小的限制-体现约定优于配置
    #默认配置最终都是映射到某个类上,比如spring.servlet.multipart.max-file-size配置会映射到MultipartProperties
    #可以通过ctrl+b,然后单击就可以到映射类
    spring.servlet.multipart.max-file-size=10MB
    
3.3.4 application.properties 常用配置
  • 常用配置一览

    #端口号
    server.port=10000
    #应用的上下文路径(项目路径)
    server.servlet.context-path=/allModel
    韩顺平Java 工程师
    #指定POJO扫描包来让mybatis自动扫描到自定义的POJO
    mybatis.type-aliases-package=com.cxs.allmodel.model
    #指定mapper.xml 的路径
    #(application 上配置了@MapperScan(扫面 mapper 类的路径)和 pom.xml 中放行了mapper.xml后,
    #配置 mapper-locations 没有意义。如果 mapper 类和 mapper.xml 不在同一个路径下时,
    mapper-locations 就有用了)
    mybatis.mapper-locations=classpath:com/cxs/allmodel/mapper
    #session 失效时间(单位s)
    spring.session.timeout=18000
    #数据库连接配置
    #mysql 数据库url
    #mysql 数据库用户名
    mysql.one.jdbc-url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai&useSSL=false
    mysql.one.username=
    #数据库密码
    mysql.one.password=
    韩顺平Java 工程师
    #线程池允许的最大连接数
    mysql.one.maximum-pool-size=15
    #日志打印:日志级别 trace<debug<info<warn<error<fatal 默认级别为 info,即默认打印 info 及其以
    上级别的日志
    #logging.level 设置日志级别,后面跟生效的区域,比如root表示整个项目,也可以设置为某个包下,
    也可以具体到某个类名(日志级别的值不区分大小写)
    logging.level.com.cxs.allmodel.=debug
    logging.level.com.cxs.allmodel.mapper=debug
    logging.level.org.springframework.web=info
    logging.level.org.springframework.transaction=info
    logging.level.org.apache.ibatis=info
    logging.level.org.mybatis=info
    logging.level.com.github.pagehelper = info
    logging.level.root=info
    #日志输出路径
    logging.file=/tmp/api/allmodel.log
    #配置pagehelper 分页插件
    韩顺平Java 工程师
    pagehelper.helperDialect=mysql
    pagehelper.reasonable=true
    pagehelper.supportMethodsArguments=true
    pagehelper.params=count=countSql
    #jackson 时间格式化
    spring.jackson.serialization.fail-on-empty-beans=false
    #指定日期格式,比如yyyy-MM-ddHH:mm:ss,或者具体的格式化类的全限定名
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    #指定日期格式化时区,比如America/Los_Angeles或者GMT+10
    spring.jackson.time-zone=GMT+8
    #设置统一字符集
    spring.http.encoding.charset=utf8
    #redis 连接配置
    # redis 所在主机ip地址
    spring.redis.host=
    #redis 服务器密码
    韩顺平Java 工程师
    spring.redis.password=
    #redis 服务器端口号
    spring.redis.port=
    #redis 数据库的索引编号(0到15)
    spring.redis.database=14
    ## 连接池的最大活动连接数量,使用负值无限制
    #spring.redis.pool.max-active=8
    #
    ## 连接池的最大空闲连接数量,使用负值表示无限数量的空闲连接
    #spring.redis.pool.max-idle=8
    #
    #spring.redis.pool.max-wait=-1ms
    ## 连接池最大阻塞等待时间,使用负值表示没有限制
    #
    ## 最小空闲连接数量,使用正值才有效果
    #spring.redis.pool.min-idle=0
    韩顺平Java 工程师
    #
    ## 是否启用SSL连接.
    ##spring.redis.ssl=false
    #
    ## 连接超时,毫秒为单位
    #spring.redis.timeout= 18000ms
    #
    ## 集群模式下,集群最大转发的数量
    #spring.redis.cluster.max-redirects=
    #
    ## 集群模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
    #spring.redis.cluster.nodes=
    #
    ## 哨兵模式下,Redis主服务器地址
    #spring.redis.sentinel.master=
    #
    ## 哨兵模式下,逗号分隔的键值对(主机:端口)形式的服务器列表
    #spring.redis.sentinel.nodes= 127.0.0.1:5050,127.0.0.1:5060
    
3.3.5 application.properties 自定义配置
  • 还可以在application.properties文件中自定义配置,通过@Value(“{}“)获取对应属性值

    application.properties文件
    my.website=https://www.baidu.com
    
    某个Bean
    @Value("${my.website}")
    private String bdUrl;
    

3.4 解读Spring Boot读取配置文件application.properties文件

  • 打开ConfigFileApplicationListener.java,查看一下源码

    image-20240708220041195

3.5 自动配置遵守按需加载原则

3.5.1 基本说明
  • 自动配置遵守按需加载原则:也就是说,引入哪个场景starter就会加载该场景关联的jar包,没有引入的starter则不会加载其相关联jar

    image-20240709102546229

  • Spring Boot 所有的自动配置功能都在spring-boot-autoconfigure包里面

    image-20240709102947859

  • 在Spring Boot 的自动配置包,一般是XxxAutoConfiguration.java,对应XxxProperties.java,如图

    image-20240709103307289

3.5.2 案例演示
  • 以MultipartProperties,MultipartAutoConfiguration和application.properties来说明

    image-20240709105105498

    image-20240709105143646

三、容器功能

1. Spring注入组件的注解

  • @Component、@Controller、@Service、@Repository,说明:这些在Spring中的传统注解仍然有效,通过这些注解可以给容器注入组件

  • 案例演示

  • 创建一个A.java

    image-20240709110502745

  • 在MainApp.java中获取,A类的对象

    package com.leon.springboot;
    
    import com.leon.springboot.bean.A;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    /**
     * ClassName:MainApp
     * Package:com.leon.springboot
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/6 21:47
     * @Version: 1.0
     */
       /*
       *  1. @SpringBootApplication 表示这是一个Spring Boot应用/项目
       *  2. @SpringBootApplication(scanBasePackages = {"com.leon"})其中的scanBasePackages = {"com.leon"} 表示指定
       *     Spring Boot 要扫描的包和子包,scanBasePackages属性是一个数组,可以有多个值,也就是说可以写多个要扫描的包,例如:
       *     scanBasePackages={"com.leon","xxx.yyy.ccc",...}
       * */
    @SpringBootApplication(scanBasePackages = {"com.leon"})
    public class MainApp {
    
       public static void main(String[] args) {
    
          //启动Spring Boot 应用程序/项目
          ConfigurableApplicationContext ioc =
                  SpringApplication.run(MainApp.class, args);
    
          ////获取注入的bean的名称
          //String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
          ////循环遍历数组
          //for (String beanDefinitionName : beanDefinitionNames) {
          //   System.out.println( "beanDefinitionName---" +  beanDefinitionName);
          //}
    
          //======演示Spring中传统的注解在Spring Boot中依然可以使用@Controller @Service @Repository @Component start
    
          //Object a = ioc.getBean("a");
    
          A aBean = ioc.getBean(A.class);
    
          System.out.println(aBean );
    
          //======演示Spring中传统的注解在Spring Boot中依然可以使用@Controller @Service @Repository @Component end
    
    
    
       }
    
    }
    

2. @Configuration 注解

2.1 应用实例

  • @Configuration应用实例需求

  • 演示在Spring Boot ,如何通过@Configuration 创建配置类来注入组件

  • 回顾传统方式如何通过配置文件注入组件

  • 创建Monster.java

    package com.leon.springboot.bean;
    
    /**
     * ClassName:Monster
     * Package:com.leon.springboot.bean
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 11:15
     * @Version: 1.0
     */
    public class Monster {
    
        private Integer id;
    
        private String name;
    
        private Integer age;
    
        private String skill;
    
        public Monster() {
        }
    
        public Monster(Integer id, String name, Integer age, String skill) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.skill = skill;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        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 String getSkill() {
            return skill;
        }
    
        public void setSkill(String skill) {
            this.skill = skill;
        }
    
        @Override
        public String toString() {
            return "Monster{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", skill='" + skill + '\'' +
                    '}';
        }
    }
    
  • 创建beans.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="monster01" class="com.leon.springboot.bean.Monster">
            <property name="id" value="100"/>
            <property name="age" value="5000"/>
            <property name="name" value="牛魔王"/>
            <property name="skill" value="芭蕉扇"/>
        </bean>
    
    
    </beans>
    
  • MainApp.java

     //======演示在Spring Boot项目中,依然可以使用Spring的配置bean/注入bean/获取bean方式 start
    
          ApplicationContext springIOc =
                  new ClassPathXmlApplicationContext("beans.xml");
    
          Monster monster01 = springIOc.getBean("monster01", Monster.class);
    
          System.out.println("monster01--" + monster01);
    
          //======演示在Spring Boot项目中,依然可以使用Spring的配置bean/注入bean/获取bean方式 end
    
  • 使用Spring Boot 的@Configuration添加/注入组件

  • BeanConfig.java类

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Monster;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    
    /**
     * ClassName:BeanConfig
     * Package:com.leon.springboot.config
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 15:23
     * @Version: 1.0
     */
    /*
    * 1. 被@Configuration注解标识的类称之为配置类,等价于Spring的配置文件,例如beans.xml
    *    程序员可以通过@Bean注解,注入bean对象到容器中
    * 2. 当一个类被@Configuration 标识,该类-Bean也会被注入到容器中
    * 3. proxyBeanMethods:表示代理bean的方法,是Spring Boot2新增的特性,指定Full模式(属性值为true)和Lite模式(属性值为false)
    * 4. Full(proxyBeanMethods=true):保证每个@Bean方法无论被调用多少次返回的组件都是单实例的,是代理方式
    * 5. Lite(proxyBeanMethods=false):每个@Bean方法无论被调用多少次返回的组件都是新创建的,是非代理方式
    * 6. 特别说明:proxyBeanMethods是在调用@Bean方法才生效,因此,需要现先获取BeanConfig组件,再调用方法,而不是
    *    直接通过Spring Boot主程序得到的容器来获取Bean,如果直接通过ioc.getBean() 获取Bean,proxyBeanMethods值是并没有生效的
    *    如果没有被@Bean注解修饰也是不会生效的
    * 7. Lite模式也称轻量级模式,因为不检测依赖关系,运行速度快
    * */
    @Configuration(proxyBeanMethods = false)
    public class BeanConfig {
    
        /*
        * 1. @Bean注解 作用:给容器添加组件,就是本方法返回的Monster bean
        * 2. 如果没有指定名称,被标注的方法的名称,默认会被作为Bean在容器中的名称/id
        * 3. 被标注的方法的返回类型,就是注入Bean类型,
        * 4. new Monster(200,"牛魔王",500,"封魔拳") 这条语句可以理解为注入到容器中具体的Bean信息,类似于传统Spring的配置文件中的beans.xml中的
        *    <property name="id" value="100"/>
        *    <property name="age" value="5000"/>
        *    <property name="name" value="牛魔王"/>
        *    <property name="skill" value="芭蕉扇"/>
        * 5. @Bean(name="monster_nmw"):中的name或value属性值是用来指定注入容器中的Bean的名称/id
        * 6. 注入的Bean默认是单例的
        * 7. 如果想每次获取的都是新创建的对象,需要使用@Scope("prototype")设置value或scopeName属性值为prototype
        * */
        @Bean
        public Monster monster01(){
    
            return new Monster(200,"牛魔王",500,"封魔拳");
        }
    
        public Monster monster02(){
    
            return new Monster(300,"白骨精",500,"易容术");
        }
    
    }
    
  • MainApp.java

        //======演示@Configuration start
    
          Monster monster01 = ioc.getBean("monster01", Monster.class);
          Monster monster02 = ioc.getBean("monster01", Monster.class);
    
          System.out.println("monster01---"+monster01+"--"+monster01.hashCode());
          System.out.println("monster02---"+monster02+"--"+monster02.hashCode());
    
          //======演示@Configuration end
    
  • 通过Debug的方式来查看是否被注入

    image-20240709155539824

2.2 @Configuration注意事项和细节

  • 配置类本身也是组件,因此也可以获取

  • Spring Boot2 新增特性:proxyBeanMethods 指定Full模式和Lite模式

  • BeanConfig.java

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Monster;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    
    /**
    
  • ClassName:BeanConfig

  • Package:com.leon.springboot.config

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/9 15:23

  • @Version: 1.0
    /
    /

    1. 被@Configuration注解标识的类称之为配置类,等价于Spring的配置文件,例如beans.xml
  • 程序员可以通过@Bean注解,注入bean对象到容器中

    1. 当一个类被@Configuration 标识,该类-Bean也会被注入到容器中
    1. proxyBeanMethods:表示代理bean的方法,是Spring Boot2新增的特性,指定Full模式(属性值为true)和Lite模式(属性值为false)
    1. Full(proxyBeanMethods=true):保证每个@Bean方法无论被调用多少次返回的组件都是单实例的,是代理方式
    1. Lite(proxyBeanMethods=false):每个@Bean方法无论被调用多少次返回的组件都是新创建的,是非代理方式
    1. 特别说明:proxyBeanMethods是在调用@Bean方法才生效,因此,需要现先获取BeanConfig组件,再调用方法,而不是
  • 直接通过Spring Boot主程序得到的容器来获取Bean,如果直接通过ioc.getBean() 获取Bean,proxyBeanMethods值是并没有生效的

  • 如果没有被@Bean注解修饰也是不会生效的

    1. Lite模式也称轻量级模式,因为不检测依赖关系,运行速度快
  • */
    @Configuration(proxyBeanMethods = false)
    public class BeanConfig {

    /*

      1. @Bean注解 作用:给容器添加组件,就是本方法返回的Monster bean
      1. 如果没有指定名称,被标注的方法的名称,默认会被作为Bean在容器中的名称/id
      1. 被标注的方法的返回类型,就是注入Bean类型,
      1. new Monster(200,“牛魔王”,500,“封魔拳”) 这条语句可以理解为注入到容器中具体的Bean信息,类似于传统Spring的配置文件中的beans.xml中的
      1. @Bean(name=“monster_nmw”):中的name或value属性值是用来指定注入容器中的Bean的名称/id
      1. 注入的Bean默认是单例的
      1. 如果想每次获取的都是新创建的对象,需要使用@Scope(“prototype”)设置value或scopeName属性值为prototype
    • */
      @Bean
      public Monster monster01(){

      return new Monster(200,“牛魔王”,500,“封魔拳”);
      }

      public Monster monster02(){

      return new Monster(300,“白骨精”,500,“易容术”);
      }

    }

  • MainApp.java

      //======演示@Configuration(proxyBeanMethods = false) start
    
          //通过获取容器中的对象来检验是否生效
          //Monster monster01 = ioc.getBean("monster01", Monster.class);
          //Monster monster02 = ioc.getBean("monster01", Monster.class);
          //
          //System.out.println("monster01---"+monster01+"--"+monster01.hashCode());
          //System.out.println("monster02---"+monster02+"--"+monster02.hashCode());
    
          //通过先获取容器中的BeanConfig来检验是否生效
          BeanConfig beanConfig = ioc.getBean("beanConfig", BeanConfig.class);
    
          Monster monster01 = beanConfig.monster01();
          Monster monster02 = beanConfig.monster01();
          System.out.println("monster01---"+monster01+"--"+monster01.hashCode());
          System.out.println("monster02---"+monster02+"--"+monster02.hashCode());
    
          Monster monster03 = beanConfig.monster02();
          Monster monster04 = beanConfig.monster02();
          System.out.println("monster03---"+monster03+"--"+monster03.hashCode());
          System.out.println("monster04---"+monster04+"--"+monster04.hashCode());
          //======演示@Configuration(proxyBeanMethods = false) end
    
  • 配置类可以有多个,与Spring可以有多个ioc配置文件是一个道理

  • 创建BeanConfig2.java

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Monster;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * ClassName:BeanConfig2
     * Package:com.leon.springboot.config
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 16:36
     * @Version: 1.0
     */
    @Configuration
    public class BeanConfig2 {
    
    
        /*
        *   1.在配置其他配置类时,要注意Bean的名称/id不能重复,也就是在没指定名称时方法名不能重复,指定名称时也不能重复
        *     要保证名称/id是唯一的
        * */
        @Bean
        public Monster monster02(){
    
            return new Monster(800,"蚂蚁精",200,"咬人");
    
        }
    
    }
    
  • MainApp.java

      //======演示可以有多个配置类 start
    
          Monster monster02 = ioc.getBean("monster02", Monster.class);
          Monster monster01 = ioc.getBean("monster01", Monster.class);
    
          System.out.println("monster01---"+monster01+"--"+monster01.hashCode());
          System.out.println("monster02---"+monster02+"--"+monster02.hashCode());
          //======演示可以有多个配置类 end
    
  • Debug方式查看

    image-20240709164848234

3. @Import注解

3.1 应用实例

  • 创建两个类,分别是Cat.java和Dog.java

  • 修改BeanConfig类,使用Import将Cat和Dog注入到容器中

  • BeanConfig.java

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Cat;
    import com.leon.springboot.bean.Dog;
    import com.leon.springboot.bean.Monster;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.Scope;
    
    /**
     * ClassName:BeanConfig
     * Package:com.leon.springboot.config
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 15:23
     * @Version: 1.0
     */
    /*
    * 1. 被@Configuration注解标识的类称之为配置类,等价于Spring的配置文件,例如beans.xml
    *    程序员可以通过@Bean注解,注入bean对象到容器中
    * 2. 当一个类被@Configuration 标识,该类-Bean也会被注入到容器中
    * 3. proxyBeanMethods:表示代理bean的方法,是Spring Boot2新增的特性,指定Full模式(属性值为true)和Lite模式(属性值为false)
    * 4. Full(proxyBeanMethods=true):保证每个@Bean方法无论被调用多少次返回的组件都是单实例的,是代理方式
    * 5. Lite(proxyBeanMethods=false):每个@Bean方法无论被调用多少次返回的组件都是新创建的,是非代理方式
    * 6. 特别说明:proxyBeanMethods是在调用@Bean方法才生效,因此,需要现先获取BeanConfig组件,再调用方法,而不是
    *    直接通过Spring Boot主程序得到的容器来获取Bean,如果直接通过ioc.getBean() 获取Bean,proxyBeanMethods值是并没有生效的
    *    如果没有被@Bean注解修饰也是不会生效的
    * 7. Lite模式也称轻量级模式,因为不检测依赖关系,运行速度快
    * 8. @Import注解,有一个value属性其类型是一个Class数组,通过Class数组中的Class值,注入指定类型的Bean,例如Dog.class,注入的是Dog类型的Bean
    *   public @interface Import {
    *    Class<?>[] value();
    *   }
    * 9. 通过@Import方式注入组件,默认组件名称/id就是对应类型的全类名
    * */
    @Import({Dog.class, Cat.class})
    @Configuration
    public class BeanConfig {
    
        /*
        * 1. @Bean注解 作用:给容器添加组件,就是本方法返回的Monster bean
        * 2. 如果没有指定名称,被标注的方法的名称,默认会被作为Bean在容器中的名称/id
        * 3. 被标注的方法的返回类型,就是注入Bean类型,
        * 4. new Monster(200,"牛魔王",500,"封魔拳") 这条语句可以理解为注入到容器中具体的Bean信息,类似于传统Spring的配置文件中的beans.xml中的
        *    <property name="id" value="100"/>
        *    <property name="age" value="5000"/>
        *    <property name="name" value="牛魔王"/>
        *    <property name="skill" value="芭蕉扇"/>
        * 5. @Bean(name="monster_nmw"):中的name或value属性值是用来指定注入容器中的Bean的名称/id
        * 6. 注入的Bean默认是单例的
        * 7. 如果想每次获取的都是新创建的对象,需要使用@Scope("prototype")设置value或scopeName属性值为prototype
        * */
        @Bean
        public Monster monster01(){
    
            return new Monster(200,"牛魔王",500,"封魔拳");
        }
    
        public Monster monster02(){
    
            return new Monster(300,"白骨精",500,"易容术");
        }
    
    }
    
  • 在mainApp.java获取

  • MainApp.java

          //======演示通过Import将Bean注入到容器中 start
    
          Dog dogBean = ioc.getBean(Dog.class);
    
          Cat catBean = ioc.getBean(Cat.class);
    
          System.out.println("catBean---"+ catBean);
    
          System.out.println("dogBean---" + dogBean);
    
    
          //======演示通过Import将Bean注入到容器中 end
    
  • Debug查看

    image-20240709171204297

4.@Conditional注解

4.1 @Conditional介绍

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

  • @Conditional是一个根注解,下面有很多扩展注解

    image-20240709171722199

    | @Conditional扩展注解 | 作用(判断是否满足是当前指定条件) |
    | ——————————- | ———————————————— |
    | @ConditionalOnJava | 系统的java版本是否符合要求 |
    | @ConditionalOnBean | 容器中存在指定Bean |
    | @ConditionalOnMissingBean | 容器中不存在指定Bean |
    | @ConditionalOnExpression | 满足SpEL表达式指定 |
    | @ConditionalOnClass | 系统中有指定的类 |
    | @ConditionalOnMissingClass | 系统中没有指定的类 |
    | @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
    | @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
    | @ConditionalOnResource | 类路径下是否存在指定资源文件 |
    | @ConditionalOnWebApplication | 当前是web环境 |
    | @ConditionalOnNotWebApplication | 当前不是web环境 |
    | @ConditionalOnJndi | JNDI存在指定项 |

4.2 应用实例

  • 要求:演示在Spring Boot,如何通过@ConditionalOnBean来注入组件,只有在容器中有name=monster_nmw组件时才注入dog01。

  • BeanConfig.java

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Cat;
    import com.leon.springboot.bean.Dog;
    import com.leon.springboot.bean.Monster;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.Scope;
    
    /**
     * ClassName:BeanConfig
     * Package:com.leon.springboot.config
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 15:23
     * @Version: 1.0
     */
    /*
    * 1. 被@Configuration注解标识的类称之为配置类,等价于Spring的配置文件,例如beans.xml
    *    程序员可以通过@Bean注解,注入bean对象到容器中
    * 2. 当一个类被@Configuration 标识,该类-Bean也会被注入到容器中
    * 3. proxyBeanMethods:表示代理bean的方法,是Spring Boot2新增的特性,指定Full模式(属性值为true)和Lite模式(属性值为false)
    * 4. Full(proxyBeanMethods=true):保证每个@Bean方法无论被调用多少次返回的组件都是单实例的,是代理方式
    * 5. Lite(proxyBeanMethods=false):每个@Bean方法无论被调用多少次返回的组件都是新创建的,是非代理方式
    * 6. 特别说明:proxyBeanMethods是在调用@Bean方法才生效,因此,需要现先获取BeanConfig组件,再调用方法,而不是
    *    直接通过Spring Boot主程序得到的容器来获取Bean,如果直接通过ioc.getBean() 获取Bean,proxyBeanMethods值是并没有生效的
    *    如果没有被@Bean注解修饰也是不会生效的
    * 7. Lite模式也称轻量级模式,因为不检测依赖关系,运行速度快
    * 8. @Import注解,有一个value属性其类型是一个Class数组,通过Class数组中的Class值,注入指定类型的Bean,例如Dog.class,注入的是Dog类型的Bean
    *   public @interface Import {
    *    Class<?>[] value();
    *   }
    * 9. 通过@Import方式注入组件,默认组件名称/id就是对应类型的全类名
    * */
    @Import({Dog.class, Cat.class})
    @Configuration
    public class BeanConfig {
    
    
    
    
        /*
        * 1. @Bean注解 作用:给容器添加组件,就是本方法返回的Monster bean
        * 2. 如果没有指定名称,被标注的方法的名称,默认会被作为Bean在容器中的名称/id
        * 3. 被标注的方法的返回类型,就是注入Bean类型,
        * 4. new Monster(200,"牛魔王",500,"封魔拳") 这条语句可以理解为注入到容器中具体的Bean信息,类似于传统Spring的配置文件中的beans.xml中的
        *    <property name="id" value="100"/>
        *    <property name="age" value="5000"/>
        *    <property name="name" value="牛魔王"/>
        *    <property name="skill" value="芭蕉扇"/>
        * 5. @Bean(name="monster_nmw"):中的name或value属性值是用来指定注入容器中的Bean的名称/id
        * 6. 注入的Bean默认是单例的
        * 7. 如果想每次获取的都是新创建的对象,需要使用@Scope("prototype")设置value或scopeName属性值为prototype
        * */
        @Bean(name = "monster_nmw")
        public Monster monster01(){
    
            return new Monster(200,"牛魔王",500,"封魔拳");
        }
    
        @Bean
        @ConditionalOnBean(name = "monster_nmw")
        public Dog dog01(){
            return new Dog();
        }
    
    
    
        public Monster monster02(){
    
            return new Monster(300,"白骨精",500,"易容术");
        }
    
    }
    
  • MainApp.java

        //======演示 @ConditionalOnBean的使用 start
    
          Dog dogBean = ioc.getBean("dog01", Dog.class);
    
          System.out.println("dogBean--"+dogBean);
    
          //======演示 @ConditionalOnBean的使用 end
    

4.3 注意事项和细节

  • @ConditionalOnBean(name = “monster_nmw”) 表示当容器中存在一个Bean的名称/id是monster_nmw时,则注入目标Bean,如果不存在Bean的名称/id是monster_nmw时,则不注入目标Bean
  • @ConditionalOnMissingBean(name = “monster_nmw”)则与@ConditionalOnBean(name = “monster_nmw”)相反
  • 当@ConditionalOnBean(name = “monster_nmw”)标识一个类时,表示该类的所有要注入的组件都需要满足该约束条件
  • 注意@ConditionalOnBean(name = “monster_nmw”)标识的目标方法必须在满足约束条件的方法之后,否则目标Bean是没有被注入的,也就是说目标Bean被注入时IOC容器中必须要有满足约束条件的Bean,否则目标Bean是注入不进去的,标识一个类时也是如此。

5. @ImportResource注解

5.1 作用

  • 原生配置文件引入,也就是可以直接导入Spring传统的beans.xml,可以认为是Spring Boot对Spring容器文件的兼容

5.2 应用实例

  • 需求:将beans.xml导入到BeanConfig3.java配置类,并测试是否可以获得beans.xml注入/配置的组件

  • 在BeanConfig3.java使用@ImportResource导入beans.xml

  • BeanConfig3.java

    package com.leon.springboot.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.ImportResource;
    
    /**
     * ClassName:BeanConfig3
     * Package:com.leon.springboot.config
     * Description:
     *
     * @Author: leon-->ZGJ
     * @Create: 2024/7/9 21:46
     * @Version: 1.0
     */
    @Configuration
    @ImportResource(locations = "classpath:beans.xml")
    public class BeanConfig3 {
    }
    
  • MainApp.java

      //======演示@ImportResopurce的使用 start
    
          Monster monster04 = ioc.getBean("monster04", Monster.class);
    
          System.out.println("monster04==="+monster04);
    
          //======演示@ImportResopurce的使用 end
    

5.3 注意事项和细节

  • @ImportResource注解可以导入多个Spring的IOC容器文件,因为locations或value属性是一个字符串数组类型,查看源码可以知道读取配置文件的默认路径是:{classpath:},{file:}
  • 注意如果被导入的Spring的IOC容器文件中的Bean的id/名称,和Spring Boot容器的Bean重复了,会将已注入的数据覆盖掉
  • 要判断容器中是否存在指定名称/id的Bean时,可以使用containsBean()方法来判断,如果存在与之对应的id/名称的Bean时返回true,反之则为false

6. 配置绑定

6.1 介绍

  • 可以理解为使用java读取到SpringBoot核心配置文件application.properties的内容,并且把它封装到JavaBean中

6.2 应用实例

  • 需求:将application.properties指定的k-v键值对和javaBean绑定

    #设置Furn的属性k-v
    #前面的furn01是用来指定/区别不同的绑定对象,这样可以在绑定Furn Bean属性值时,通过furn01这个前缀进行区分
    #注意furn01.id中的id就是你要绑定的Furn Bean 的属性名
    furn01.id=100
    furn01.name=baidu
    furn01.price=1000.0
    
  • 创建Furn.java

    package com.leon.springboot.bean;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
    
  • ClassName:Furn

  • Package:com.leon.springboot.bean

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/9 22:11

  • @Version: 1.0
    /
    @Component
    /

    1. @ConfigurationProperties(prefix = “furn01”)表示获取哪个配置绑定的数据,通过前缀来区分,也就是prefix属性值来区分
  • */
    @ConfigurationProperties(prefix = “furn01”)
    public class Furn {

    private Integer id;

    private String name;

    private Double price;

    public Furn() {
    }

    public Furn(Integer id, String name, Double price) {

      this.id = id;
      this.name = name;
      this.price = price;
    

    }

    public Integer getId() {

      return id;
    

    }

    public void setId(Integer id) {

      this.id = id;
    

    }

    public String getName() {

      return name;
    

    }

    public void setName(String name) {

      this.name = name;
    

    }

    public Double getPrice() {

      return price;
    

    }

    public void setPrice(Double price) {

      this.price = price;
    

    }

    @Override
    public String toString() {

      return "Furn{" +
              "id=" + id +
              ", name='" + name + '\'' +
              ", price=" + price +
              '}';
    

    }
    }

  • 还有第二种配置绑定方式,就是在配置类的头部配置@EnableconfigurationProperties(Furn.class),如果这样配置,则要注销掉Furn类头部的@Component注解,因为不注销掉@Component注解容器中会有两个带有相同配置绑定数据的Furn Bean,一个id/名称为Frun的全类名,一个id/名称为Furn类名首字母小写(前提是没有设置名称/id),所以为了避免错误建议注销掉Furn头部的@Component注解。

  • BeanConfig2.java

    package com.leon.springboot.config;
    
    import com.leon.springboot.bean.Furn;
    import com.leon.springboot.bean.Monster;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
    
  • ClassName:BeanConfig2

  • Package:com.leon.springboot.config

  • Description:
    *

  • @Author: leon–>ZGJ

  • @Create: 2024/7/9 16:36

  • @Version: 1.0
    /
    @Configuration
    /

  • 1.@EnableConfigurationProperties({Furn.class}) 表示开启Furn配置绑定功能,把Furn组件自动注入到容器中

  • 2.@EnableConfigurationProperties 的value属性是一个数组,也就是说可以开启多个Bean

  • */
    @EnableConfigurationProperties({Furn.class})
    public class BeanConfig2 {

    /*

    • 1.在配置其他配置类时,要注意Bean的名称/id不能重复,也就是在没指定名称时方法名不能重复,指定名称时也不能重复

    • 要保证名称/id是唯一的
      
    • */
      @Bean
      public Monster monster02(){

      return new Monster(800,“蚂蚁精”,200,“咬人”);

      }

    }

6.3 注意事项和细节

  • Spring Boot 核心配置文件application.properties中的配置绑定数据,使用的是K-V键值对,其中K中包含有一个前缀是用来指定或区别不同的绑定对象的,这样可以在绑定Bean属性时,通过前缀进行区分。

  • 注意前缀之后的部分就是Bean的属性名,如果和Bean的属性名不一样会绑定失败,Bean中没被绑定到的属性则是默认值。

  • 在使用第二种配置方式时,注意要把Bean头部的@Component注解注销掉,不然会有两相同数据,但id/名称不同的Bean对象。

  • 在application.properties文件中会有中文乱码,需要转换成Unicode编码才可以

  • 如果在使用@ConfigurationProperties注解时,提示了警告或错误,则需要导入该依赖

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

四、分析Spring Boot底层机制【Tomcat启动分析+Spring容器初始化+Tomcat如何关联Spring容器】

1. SpringApplication.run()进行Debug

package com.leon.springboot;

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

/**
 * ClassName:MainApp
 * Package:com.leon.realizespringboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/14 20:42
 * @Version: 1.0
 */
@SpringBootApplication
public class MainApp {

    public static void main(String[] args) {

        /*
         *  启动spring boot 应用程序/项目
         *  1.提出问题:当执行run方法时,怎么就启动我们的内置的Tomcat?
         *  2.在分析run方法的底层机制的基础上,我们直接尝试实现
         * */
        ConfigurableApplicationContext
                ioc = SpringApplication.run(MainApp.class, args);

        /*
        *  这里开始追源码,也就是对SpringApplication.run()
        * 1.SpringApplication.java
        * public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        *    return run(new Class[]{primarySource}, args);
        *  }
        *
         2. SpringApplication.java :创建返回ConfigurableApplicationContext对象
         public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return (new SpringApplication(primarySources)).run(args);
          }
        3.SpringApplication.java
        public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        Throwable ex;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();//严重分析:创建容器
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);//严重分析:刷新应用程序的上下文,初始化默认设置/注入相关Bean/启动Tomcat
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            ex = var10;
            this.handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            ex = var9;
            this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(ex);
        }
    }
        4. SpringApplication.java 容器类型很多,会跟据你的this.webApplicationType创建对应的容器
            默认this.webApplicationType 是SERVLET也就是web容器/可以处理Servlet
        *   protected ConfigurableApplicationContext createApplicationContext() {
                return this.applicationContextFactory.create(this.webApplicationType);
            }
       5. ApplicationContextFactory.java
        ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            switch (webApplicationType) {
                case SERVLET: //默认进入该分支
                    return new AnnotationConfigServletWebServerApplicationContext();
                case REACTIVE:
                    return new AnnotationConfigReactiveWebServerApplicationContext();
                default:
                    return new AnnotationConfigApplicationContext();
            }
        } catch (Exception var2) {
            Exception ex = var2;
            throw new IllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", ex);
        }
    };
    * 6.SpringApplication.java
    *  private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }

        this.refresh(context);//严重分析:真正执行的方法
    }
    * 7.SpringApplication.java
    * protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }
    * 8.ServletWebServerApplicationContext.java
    * public final void refresh() throws BeansException, IllegalStateException {
        try {
            super.refresh();
        } catch (RuntimeException var3) {
            RuntimeException ex = var3;
            WebServer webServer = this.webServer;
            if (webServer != null) {
                webServer.stop();
            }

            throw ex;
        }
    }

    * 9.AbstractApplicationContext.java
    * @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh(); //严重分析:当父类完成通用的工作后,再重新动态绑定机制回到子类

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }
    *  10.ServletWebServerApplicationContext.java
    * protected void onRefresh() {
        super.onRefresh();

        try {
            this.createWebServer(); //创建webServer 可以理解成会创建指定web服务-Tomcat
        } catch (Throwable var2) {
            Throwable ex = var2;
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    * 11.ServletWebServerApplicationContext.java
    * private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            ServletWebServerFactory factory = this.getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); //严重分析:使用TomcatServletWebServerFactory创建一个Tomcat
            createWebServer.end();
            this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
            this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var5) {
                ServletException ex = var5;
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }

        this.initPropertySources();
    }
    * 12.TomcatServletWebServerFactory.java 会创建Tomcat 并设置Tomcat,然后启动Tomcat
    *  public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }

        Tomcat tomcat = new Tomcat(); //创建Tomcat对象
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        //对Tomcat进行一些设置
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat); //严重分析该方法
    }
        13.TomcatServletWebServerFactory.java 这里做了校验 创建TomcatWebServer
        * protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
    }
    * 14. TomcatWebServer.java
    *  public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
        this.initialize(); //严重分析该方法,做了初始化,并启动Tomcat
    }
    * 15. TomcatWebServer.java
    *   private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });
                this.tomcat.start(); //启动Tomcat
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {
                }

                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                Exception ex = var6;
                this.stopSilently();
                this.destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }

        }
    }
        * */
        System.out.println("==============================");

    }

}

2. 实现Spring Boot 部分机制【之前实现过Spring和SpringMVC底层机制所以简化了很多】

2.1 创建Tomcat

package com.leon.realizespringboot;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;

/**
 * ClassName:RealizeSpringBoot
 * Package:com.leon.realizespringboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 15:49
 * @Version: 1.0
 */
public class RealizeSpringBoot {

    //创建tomcat对象,并关联Spring容器,然后启动
    public static void run(){
        try {
            //创建Tomcat对象
            Tomcat tomcat = new Tomcat();
            //让Tomcat可以将请求,转发到Spring Web 容器,因此需要进行关联
            //"/hspboot" 就是项目的 application context,就是我们原来配置Tomcat时,指定的application context
            //"C:\\software\\javacodelibrary\\distributed-microservices\\realize-spring-boot"指定项目所在的目录
            tomcat.addWebapp("","C:\\software\\javacodelibrary\\distributed-microservices\\realize-spring-boot");

            //设置监听端口
            tomcat.setPort(9090);
            //启动Tomcat
            tomcat.start();
            //等待接入
            System.out.println("===========9090===========等待请求===========");
            //让Tomcat等待连接
            tomcat.getServer().await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

2.2 创建Spring 容器

package com.leon.realizespringboot;

import com.leon.realizespringboot.config.BeanConfig;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

/**
 * ClassName:RealizeWebApplicationInitializer
 * Package:com.leon.realizespringboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 16:12
 * @Version: 1.0
 */
public class RealizeWebApplicationInitializer implements WebApplicationInitializer {

    /*
    * 1.创建自己的Spring容器
    * 2.加载/关联Spring容器的配置-按照注解的方式
    * 3.完成Spring容器配置的Bean的创建,依赖注入
    * 4.创建前端控制器DispatcherServlet,并让其持有Spring容器
    * 5.当DispatcherServlet持有容器,就可以进行分发映射。可以回忆之前自己实现SpringMVC底层机制
    * 6.这里onStartup是Tomcat调用,并把ServletContext对象传入
    * */
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //加载Spring Web application configuration
        AnnotationConfigWebApplicationContext acwa
                = new AnnotationConfigWebApplicationContext();
        //在AnnotationConfigWebApplicationContext中注册BeanConfig配置类
        acwa.register(BeanConfig.class);
        //进行刷新,完成Bean的创建和配置
        acwa.refresh();
        //创建一个非常重要的前端控制器 DispatcherServlet
        /*
        * 1.让DispatcherServlet持有容器,这样就可以进行映射分发
        * */
        DispatcherServlet dispatcherServlet = new DispatcherServlet(acwa);
        //将DispatcherServlet放入到tomcat的ServletContext中,然后返回ServletRegistration.Dynamic对象
        ServletRegistration.Dynamic registration
                = servletContext.addServlet("app", dispatcherServlet);
        //设置加载优先级,也就是当Tomcat启动时加载DispatcherServlet
        registration.setLoadOnStartup(1);
        //拦截请求,并进行分发处理
        registration.addMapping("/");
    }
}

2.3 创建主程序

package com.leon.realizespringboot;

/**
 * ClassName:RealizeMainApp
 * Package:com.leon.realizespringboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 15:56
 * @Version: 1.0
 */
public class RealizeMainApp {

    public static void main(String[] args) {
        //启动RealizeSpringBoot项目/程序
        RealizeSpringBoot.run();

    }

}

2.4 创建Bean

package com.leon.realizespringboot.bean;

/**
 * ClassName:Dog
 * Package:com.leon.realizespringboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 16:06
 * @Version: 1.0
 */
public class Dog {
}

2.5 创建配置类

package com.leon.realizespringboot.config;

import com.leon.realizespringboot.bean.Dog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * ClassName:BeanConfig
 * Package:com.leon.realizespringboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 16:07
 * @Version: 1.0
 */
@Configuration
//在配置类中可以指定要扫描的包
@ComponentScan("com.leon.realizespringboot")
public class BeanConfig {

    @Bean
    public Dog dog(){
        return new Dog();
    }

}

2.6 创建Controller

package com.leon.realizespringboot.controller;

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

/**
 * ClassName:HiController
 * Package:com.leon.realizespringboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/15 16:06
 * @Version: 1.0
 */
@Controller
public class HiController {

    @RequestMapping("/hi")
    @ResponseBody
    public String hi() {
        return "hi realize-spring-boot-----";
    }

}

五、Lombok

1.Lombok介绍

  • Lombok作用
  • 简化JavaBean开发,可以使用Lombok的注解让代码更加简洁
  • java项目中,很多没有技术含量又必须存在的代码:POJO的getter/setter/toString;异常处理;I/o流的关闭操作等,这些代码既没有技术含量,又影响着代码的美观,Lombok应运而生
  • Spring Boot和IDEA官方支持
  • IDEA 2020以及内置了Lombok插件
  • Spring Boot 2.1.x之后的版本也在Starter中内置了Lombok依赖

2. Lombok常用注解

  • @Data :注解在类上:提供所有属性的getter和setter方法,此外还提供了equals、canEqual、hashCode、toString方法
  • @Setter:注解在属性上:为属性提供setter方法,注解在类上:为所有属性提供setter方法
  • @Getter:注解在属性上:为属性提供getter方法,注解在类上:为所有属性提供getter方法
  • @Log4j:注解在类上,为类提供一个属性名为log的log4j日志对象
  • @NoArgsConstructor:注解在类上,为类提供一个无参的构造器
  • @AllArgsConstructor:注解在类上,为类提供一个全参的构造器
  • @Cleanup:可以关闭流
  • @Builder:被注解的类加个构造者模式
  • @Synchronized:加个同步锁
  • @SneakyThrows:等同于try/catch捕获异常
  • @NonNull:如果给参数加上这个注解,参数为Null会抛出空指针异常
  • @Value注解和@Data注解类似,区别在于它会把所有成员变量默认定义为private final 修饰,并且不会生成setter方法
  • @RequiredArgsConstructor
  • 在我们写Controller或者Service层的时候,需要注入很多的mapper接口或者另外的Service接口,这时候就会写很多的@Autowired注解,代码看起来很乱,Lombok提供了一个注解:@RequiredArgsConstructor(onConstructor=@_(@Autowired))写在类上可以代替@Autowired注解,需要注意的是在注入时需要使用final定义,或者使用@notnull注解

3.lombok应用实例

package com.leon.springboot.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * ClassName:Furn
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/9 22:11
 * @Version: 1.0
 */
@Component
/*
* 1. @ConfigurationProperties(prefix = "furn01")表示获取哪个配置绑定的数据,通过前缀来区分,也就是prefix属性值来区分
* */
@ConfigurationProperties(prefix = "furn01")
//在编译时,生成toString,默认情况下,会生成一个无参构造器
//@ToString
/*
* Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}.
* 1.使用了Data注解等价使用了 如下注解:@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
*  */
//@Data
/*
*  1.@NoArgsConstructor在编译时,会生成无参构造器,但是在默认情况下,也会生成一个无参构造器
*  2.当使用其他构造器时,默认的无参构造器会被覆盖,如果希望仍然有无参构造器就需要使用@NoArgsConstructor指定一下,
*   否则就会因为无参构造器被覆盖,而产生一系列的错误
* */
@NoArgsConstructor
//1.@NoArgsConstructor在编译时,会生成无参构造器,但是在默认情况下,也会生成一个全参构造器
@AllArgsConstructor

public class Furn {

    private Integer id;

    private String name;

    private Double price;


}

4.idea中安装lombok插件,使用扩展功能

image-20240715212219873

六、Spring Initailizr

1. Spring Intitailizr介绍

  • Spring Initailzr作用
  • 程序员通过开发Maven Archetype 来生成Maven项目,这种项目的原型相对简陋,需要手动配置,比较灵活
  • 通过Spring官方提供的Spring Initailizr 来构建Maven项目,能完美支持IDEA和Eclipse,让程序员来选择需要的开发场景(starter),还能自动生成启动类和单元测试代码
  • Spring Initailizr对IDEA版本有要求同时还要走网络,本人比较喜欢使用Maven Archetype来生成Maven项目,因为比较灵活。

2. Spring Inlitailizr使用演示

2.1 方式1:IDEA创建

2.1.1 创建项目

image-20240730145410299

2.1.1 选择类型

image-20240730145241249

2.1.1 选择场景(starter)

image-20240730145743137

2.2 方式2:start.spring.io创建

2.2.1 网址
  • 网址:https://start.spring.io/
2.2.2 选择类型和场景

image-20240730151031097

七、yaml

1. yaml介绍

  • YAML是"YAML Ain`t a Markup Languaga”(YAML不是一种标记语言)的递归缩写。在开发这种语言时,YAML的意思其实是"Yet Another Markup Languaga”(仍是一种标记语言),是为了强调这种语言以数据作为中心,而不是标记语言为重点,而用反向缩略语重命名【百度百科】
  • 解读
  • YAML以数据作为中心,而不是以标记语言为重点
  • YAML仍然是一种标记语言,但是和传统的标记语言不一样,是以数据为中心的标记语言
  • YAML非常适合用来做以数据为中心的配置文件

2. 使用文档

  • 官方网址:https://yaml.org/
  • 关于java操作yaml:https://www.cnblogs.com/strongmore/p/14219180.html

3.YAML基本语法

  • 形式为key: value;注意:后面有空格
  • 区分大小写
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格【有些地方也识别tab,推荐使用空格】
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • 字符串无需加引号【也可以加上,不过意义不大】
  • YAML中,使用#注释

4.数据类型

4.1 字面量

  • 字面量:单个的、不可再分的值。例如:date、boolean、string、number、null
  • 保存形式为key: value

4.2 对象

  • 对象:键值对的集合,比如 map、hash、set、object

    行内写法:K: {k1: v1,k2: v2,k3: v3}
    #或者
    k: 
    k1: v1
    k2: v2
    k3: v3
    

4.3 数组

  • 数组:一组按次序排列的值,比如array、List、queue

    行内写法:k: {v1,v2,v3}
    #或者
    k: 
    
  • v1

  • v2

  • v3

5.yaml使用细节

  • 如果application.properties和application.yaml有相同的前缀绑定,则application.properties优先级高,开发时,应当避免

  • 字符串无需加引号,如果使用”“或者''包起来也可以

  • 解决YAML配置文件,不提示字段信息问题

  • 在pom文件中添加如下配置

    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
        </dependency>
    
  • 也可以在IDEA中安装YAML插件

    image-20240730174351050

  • 如果有提示功能的前提下,配置过的属性不会在提示

6. 代码

monster:
  id: 100
  name: 牛魔王
  age: 100
  is-married: true
  birth: 2024/7/30
#  对象
#  car: {name: 兰博基尼,price: 200} 行内风格
  car:
    name: 兰博基尼
    price: 200
#    数组
#  skill: [法天象地,芭蕉扇] #行内风格
  skill:
    - 法天象地
    - 芭蕉扇
#    数组
#  hobby: [泡妞,吹牛,吃肉] #行内风格
  hobby:
    - 泡妞
    - 吹牛
    - 吃肉
#    对象
#  wife: {no1: 铁扇公主,no2: 狐狸精} #行内风格
  wife:
    no1: 铁扇公主
    no2: 狐狸精
  salaries:
    - 1000
    - 20000
  cars:
    c1: [{name: 路虎,price: 3000},{name: 小米SUV,price: 3000},{name: suv,price: 2000}]#行内风格
    c2:
      - {name: 法拉利 ,price: 2000} #行内风格
      - name: 五菱宏光
        price: 300

八、WEB开发-静态资源访问

1. 官方文档

  • 网址:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/html/web.html#web.servlet.spring-mvc.auto-configuration

2. 基本介绍

  • 只要静态资源放在类路径下:/static、 /public、 /resources 、/META-INF/resources可以被直接访问-对应文件WebProperties.java

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
                  "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
    
  • 常见静态资源:JS、CSS、图片(.jpg .png .gif .bmp .svg)、字体文件(Fonts)等

  • 访问方式:默认:项目根路径/+静态资源。例如 http://localhost/hi.html。可以通过WebMvcProperties.java中的属性来设置访问映射

      /**
       * Path pattern used for static resources.
       */
      private String staticPathPattern = "/**";
    

3.快速入门

3.1 代码

package com.leon.springboot;

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

/**
 * ClassName:MainApp
 * Package:com.leon.springboot
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/30 20:04
 * @Version: 1.0
 */
@SpringBootApplication
public class MainApp {

   public static void main(String[] args) {

      /*
      * private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
      * { "classpath:/META-INF/resources/","classpath:/resources/",
      * "classpath:/static/", "classpath:/public/" };
      * */
      SpringApplication.run(MainApp.class,args);

   }

}

image-20240730201703356

4. 静态资源访问注意事项和使用细节

  • 静态资源访问原理:静态映射是 /** ,也就是对所有请求拦截,请求进来,先看Controller能不能处理,不能处理的请求再交给静态资源处理,如果静态资源找不到则响应404页面

  • 改变静态资源访问前缀,应用场景:静态资源访问前缀和控制器请求路径冲突

  • 创建一个yaml文件,来修改静态资源访问前缀

    spring:
      mvc:
        static-path-pattern: /resource/** #修改静态资源访问的路径/前缀
    
  • 改变默认的静态资源路径

  • 配置yaml文件,修改static-locations

    spring:
      mvc:
        static-path-pattern: /resource/** #修改静态资源访问的路径/前缀
      web:
        resources:
          #修改/指定 静态资源的访问路径/位置
          #如果新配置了static-locations,原来的访问路径就会被覆盖,如果需要保留,需要再次指定一下
          static-locations: [classpath:/META-INF/resources/,classpath:/resources/,
                              classpath:/static/, classpath:/public/,classpath:/img/] #这是一个字符串数组
    

九、Rest风格请求处理

1.基本介绍

  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
  • 举例说明:
  • GET-获取信息
  • DELETE-删除信息
  • PUT-修改信息
  • PSOT-保存信息

2. 快速入门

package com.leon.springboot.controller;

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

/**
 * ClassName:MonsterController
 * Package:com.leon.springboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/7/30 20:43
 * @Version: 1.0
 */
@RestController
public class MonsterController {

    @GetMapping("/monster")
    //等价于@RequestMapping(value = "/monster", method = RequestMethod.GET)
    public String getMonster() {
        return "GET-获取妖怪信息";
    }

    @DeleteMapping("/monster")
    public String deleteMonster() {
        return "DELETE-删除妖怪信息";
    }

    @PutMapping("/monster")
    public String putMonster() {
        return "PUT-修改妖怪信息";
    }

    @PostMapping("/monster")
    public String postMonster() {
        return "POST-保存妖怪信息";
    }

}

3.注意事项和细节

  • 客户端是PostMan可以直接发送PUT、DELETE等请求方式,可不设置Filter

  • 如果要SpringBoot支持页面表单的Rest功能,则需要注意如下细节

  • Rest风格请求核心Filter:HiddenHttpMethodFilter,表单请求会被HiddenHttpMethodFilter拦截,获取到表单_method的值,再判断是PUT/DELETE/PATCH(老师注释:PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新:https://segmentfault.com/q/1010000005685904)

  • 如果要SpringBoot 支持页面表单的Rest功能,需要在application.yml启用Filter功能,否则无效

  • 设置application.yml启用Filter功能

    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true #启用HiddenHttpMethodFilter,开启页面表单Rest功能
    
  • 配置视图解析器

    spring:
    mvc:
      #static-path-pattern: /resource/** #修改静态资源访问的路径/前缀
      view: #配置视图解析器
        suffix: .html
        #这里需要注意:
        # 1. 如果没有指定static-path-pattern: 的值 则 prefix: /
        # 2. 如果指定了static-path-pattern: 的值,则 prefix: 的值等于static-path-pattern: 的值
        # 去除后面的**,也就是需要和static-path-pattern的值保持一致
        # 3. 因为SpringBoot有一个约定由于配置的思想,所以建议不要指定static-path-pattern: 的值
        #prefix: /resource/
        prefix: /
    

十、接收参数相关注解

1. 基本介绍

  • Spring Boot接收客户端提交数据/参数会使用到相关注解
  • 详解@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@CookieValue、@RequestBody

2. 接收参数相关注解应用实例

2.1 @PathVariable

/*
    *  1. /monster/{id}/{name} 解读
    *   1.1 /monster/{id}/{name} 构成完整请求路径
    *   1.2 {id}{name} 就是占位变量
    *   1.3 @PathVariable("name"): 这里name和占位变量{name}必须相同,否则会报错
    *   1.4 而方法中的形参可以和注解中的value值不一样
    *   1.5 @PathVariable Map<String,String> map 就是把所有参数都封装进map集合中,
    *       key值是占位变量的名称,value是请求参数值
    * */
    @GetMapping("/monster/{id}/{name}")
    @ResponseBody
    public String pathVariable(@PathVariable("id") Integer id,
                               @PathVariable("name") String name,
                               @PathVariable Map<String, String> map){

        System.out.println("id---"+id);
        System.out.println("name---"+name);
        System.out.println("map---"+map);
        return "success";
    }

2.2 @RequestHeader

 @GetMapping("/requestHeader")
    @ResponseBody
    public String requestHeader(@RequestHeader("Host") String host,
                                @RequestHeader("Accept") String accept,
                                @RequestHeader Map<String, String> map){

        System.out.println("host---"+host);
        System.out.println("accept---"+accept);
        System.out.println("map---"+map);
        return "success";
    }

2.3 @ModelAttribute

    /*
     *  1. 当Handler的方法被标识@ModelAttribute,就视为一个前置方法,
     *     每当这个类中的其他方法被执行前都会执行这个方法
     *  2. 这个方法类似于AOP中的前置通知
     * */
    @ModelAttribute
    public void modelAttribute(){
        System.out.println("----------------modelAttribute被执行了----------------");
    }

2.4 @RequestParam

  /*
    *  1.如果是多个相同名称的请求参数,可以通过List集合进行封装
    *  2.如果使用Map集合来接收所有请求参数的话,相同参数名称的多个请求参数只能获取到一个,也就是说只有多个相同参数名称的请求参数中的一个被封装进map集合
    * */
    @GetMapping("/requestParam")
    @ResponseBody
    public String requestParam(@RequestParam(value = "name",required = false) String name,
                               @RequestParam(value = "job",required = false) String job,
                               @RequestParam(value = "hobby",required = false) List<String> hobby,
                               @RequestParam Map<String, String> map){

        System.out.println("name---"+name);
        System.out.println("job---"+job);
        System.out.println("hobby---"+hobby);
        System.out.println("hobbyMap---"+map);

        return "success";
    }

2.5 @CookieValue

/*
    *  1.因为浏览目前没有Cookie,可以自己在浏览器中设置Cookie
    *  2.@CookieValue("cookie_key") String cookieValue
    *    表示将接收到Cookie的key值为cookie_key的value值赋值给cookieValue
    *  3.@CookieValue("username") Cookie cookie 表示将接收到中cookie的key值为username的cookie对象赋值给cookie
    *  4.如果浏览器携带来对应的cookie,那么就会将对应的值或者封装好的cookie对象赋值给对应的形参
    * */
    @GetMapping("/cookie")
    @ResponseBody
    public String cookieValue(@CookieValue("cookie_key") String cookieValue,
                              @CookieValue("username") Cookie cookie,
                              HttpServletRequest request){

        System.out.println("cookieValue---"+cookieValue);
        System.out.println("cookie---[ "+cookie.getName()+"--"+cookie.getValue()+"  ]");
        //获取所有的cookie
        Cookie[] cookies = request.getCookies();
        //循环遍历所有的cookie
        for (Cookie cookie1 : cookies) {

            System.out.println("cookie---[ "+cookie1.getName()+"--"+cookie1.getValue()+"  ]");

        }

        return "success";
    }

2.6 @RequestBody

/*
    * 1.@RequestBody String content 将表单发出的Post请求中的请求数据封装赋值给content
    * */
    @PostMapping("/save")
    @ResponseBody
    public String requestBody(@RequestBody String content){

        System.out.println("content---"+content);


        return "success";
    }

2.7 @RequestAttribute和@SessionAttribute

 @GetMapping("/requestAttribute")
    @ResponseBody
    public String requestAttribute(@RequestAttribute(value = "user",required = false) String user,
                                   @RequestAttribute(value = "age",required = false) String age,
                                   HttpServletRequest request,
                                   @SessionAttribute(value = "id",required = false) String id){
        System.out.println("user--"+user);
        System.out.println("age---"+age);
        System.out.println("通过servlet API获取request域中的数据:"+request.getAttribute("user"));
        System.out.println("id--"+id);
        System.out.println("通过servlet API获取session域中的数据:"+request.getSession().getAttribute("id"));
        return "success";
    }


    @GetMapping("/login")
    public String login(HttpServletRequest request){

        request.setAttribute("user","牛魔王");
        request.setAttribute("age","100");

        request.getSession().setAttribute("id","109022");
      //这里需要启用视图解析器
      return "forward:/requestAttribute";
    }

2.7 前端测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>接收参数注解测试</title>
</head>
<body>
<h1>hello,欢迎来到SpringBoot接收参数注解测试</h1>
基本注解:
<hr/>
<a href="/monster/100/king">@PathVariable-路径变量 /monster/100/king</a><br/><br/>
<a href="/requestHeader">@RequestHeader-获取Http请求头</a><br/><br/>
<a href="/requestParam?name=jack&job=java&hobby=羽毛球&hobby=乒乓球">@RequestParam-获取请求参数</a><br/><br/>
<a href="/cookie">@CookieValue-获取Cookie值</a><br/><br/>
<a href="/login">@RequestAttribute-获取request域属性</a><br/><br/>

<form action="/save" method="post" >
    用户:<input type="text" name="username" value=""/><br/>
    年龄:<input type="text" name="age" value=""/><br/>
    <input type="submit" value="提交">

</form>
</body>
</html>

3. 复杂参数

3.1 基本介绍

  • 在开发中,Spring Boot 在响应客户端请求时,也支持复杂参数
  • Map、Model、Errors/BindingResult、RedirectAttributes、ServletResponse、SessionStatus、UriComponentBuilder、ServletUriComponentsBuilder、HttpSession
  • Map、Model数据会被放在request域
  • RedirectAttributes重定向携带数据

3.2 复杂参数应用

 @GetMapping("/register")
    public String register(Map<String, Object> map,
                           Model model,
                           HttpServletResponse response) {

        //Map中的数据会被放到,request域中
        map.put("user","jack");
        map.put("job","java工程师");
        map.put("age","32");
        map.put("hobby","乒乓球");
        //model中的数据也会被放入到request域中
        model.addAttribute("id","10091");
        //添加cookie
        response.addCookie(new Cookie("salary","10000"));


        return "forward:/registerOk";
    }

    @GetMapping("/registerOk")
    @ResponseBody
    public String registerOk(HttpServletRequest request) {

        System.out.println("user---"+request.getAttribute("user"));
        System.out.println("job---"+request.getAttribute("job"));
        System.out.println("age---"+request.getAttribute("age"));
        System.out.println("hobby---"+request.getAttribute("hobby"));
        System.out.println("id---"+request.getAttribute("id"));

        return "success";
    }

4.自定义对象参数-自动封装

4.1基本介绍

  • 在开发中,SpringBoot在响应客户端/浏览器请求时,也支持自定义对象参数
  • 完成自动类型转换与格式化
  • 支持级联封装

4.2自定义对象参数-应用实例

4.2.1 后端代码
 @PostMapping("/saveMonster")
    @ResponseBody
    public String saveMonster(Monster monster){

        System.out.println("monster---"+monster);

        return "success";
    }
4.2.2 前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义对象参数</title>
</head>
<body>
<h1>添加妖怪-坐骑[测试封装POJO]</h1>
        <form action="/saveMonster" method="post">
            编号:<input type="text"  name="id" value=""><br/>
            姓名:<input type="text"  name="name" value=""><br/>
            年龄:<input type="text"  name="age" value=""><br/>
            婚否:<input type="text"  name="isMarried" value=""><br/>
            生日:<input type="text"  name="birthdays" value=""><br/>
            坐骑名称:<input type="text"  name="car.name" value=""><br/>
            坐骑价格:<input type="text"  name="car.price" value="">
            <input type="submit" value="保存">
        </form>
</body>
</html>

十一、自定义转换器

1. 基本介绍

  • Spring Boot 在响应客户端请求时,将提交的数据封装成对象时,使用了内置的转换器
  • Spring Boot 也支持自定义转换器,其中Spring Boot提供了124个内置转换器(Spring Boot 版本为2.5.3),通过查看GenericConverter-ConvertiblePair

2.自定义转换器-应用实例

2.1 后端代码

package com.leon.springboot.config;

import com.leon.springboot.bean.Car;
import com.leon.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * ClassName:WebConfig
 * Package:com.leon.springboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/7 22:15
 * @Version: 1.0
 */
/*
*  1.proxyBeanMethods = false 表示使用Lite模式
* */
@Configuration(proxyBeanMethods = false)
public class WebConfig {


    @Bean
    public WebMvcConfigurer webMvcConfigurer() {

        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {

                /*
                * 1.在addFormatters 方法中,增加一个自定义的转换器,转换的类型是由String-->Car
                * 2.增加的自定义转换器会注册到converters容器中
                * 3.converters底层结构是ConcurrentHashMap内置有124个转换器
                * */
                //方式1
                //registry.addConverter(new Converter<String, Car>() {
                //
                //    /*
                //    *  1.source是通过表单请求过来的数据:避水金晶兽,666.6
                //    * */
                //    @Override
                //    public Car convert(String source) {
                //        //这里写主要业务代码
                //
                //        //判断source是否为空
                //        if(StringUtils.hasText(source)){
                //
                //            //创建一个Car对象
                //            Car car = new Car();
                //            //进行字符串操作
                //            String[] split = source.split(",");
                //            //设置名称
                //            car.setName(split[0]);
                //            //设置价格
                //            car.setPrice(Double.parseDouble(split[1]));
                //
                //            return car;
                //        }
                //
                //
                //        return null;
                //    }
                //});
                //方式2
                Converter<String, Car> converter = new Converter<String,Car>() {

                    @Override
                    public Car convert(String source) {

                        //这里写主要业务代码

                        //判断source是否为空
                        if (StringUtils.hasText(source)) {

                            //创建一个Car对象
                            Car car = new Car();
                            //进行字符串操作
                            String[] split = source.split(",");
                            //设置名称
                            car.setName(split[0]);
                            //设置价格
                            car.setPrice(Double.parseDouble(split[1]));

                            return car;
                        }

                        return null;
                    }
                };

                Converter<String, Monster> converter2 = new Converter<String, Monster>() {

                    @Override
                    public Monster convert(String source) {
                        return null;
                    }
                };

                //可以添加多个自定义转换器
                registry.addConverter(converter);
                registry.addConverter(converter2);

            }
        };



    }

}

2.2 前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义对象参数</title>
</head>
<body>
<h1>添加妖怪-坐骑[测试封装POJO]</h1>
        <form action="/saveMonster" method="post">
            编号:<input type="text"  name="id" value=""><br/>
            姓名:<input type="text"  name="name" value=""><br/>
            年龄:<input type="text"  name="age" value=""><br/>
            婚否:<input type="text"  name="isMarried" value=""><br/>
            生日:<input type="text"  name="birthdays" value=""><br/>
            坐骑:<input type="text" name="car" value="避水金晶兽,666.6">
<!--            坐骑名称:<input type="text"  name="car.name" value=""><br/>-->
<!--            坐骑价格:<input type="text"  name="car.price" value="">-->
            <input type="submit" value="保存">
        </form>
</body>
</html>

十二、处理JSON

1.应用实例

    @GetMapping("/getMonster")
    @ResponseBody
    public Monster getMonster() {
        Monster monster = new Monster();
        monster.setId(100);
        monster.setName("奔波霸");
        monster.setAge(1000);
        monster.setMarried(false);
        monster.setBirthdays(new Date());
        monster.setCar(new Car("拖拉机",10));
        return monster;
    }

十三、内容协商

1.基本介绍

  • 根据客户接收能力不同,Spring Boot返回不同媒体类型的数据
  • 比如:客户端Http请求Accept:application/xml则返回xml数据,客户端Http请求Accept:application/json则返回json数据

2.内容协商-应用实例

2.1 测试代码

@GetMapping("/getMonster")
    @ResponseBody
    public Monster getMonster() {
        Monster monster = new Monster();
        monster.setId(100);
        monster.setName("奔波霸");
        monster.setAge(1000);
        monster.setMarried(false);
        monster.setBirthdays(new Date());
        monster.setCar(new Car("拖拉机",10));
        return monster;
    }

3.注意细节

  • 如果想要让SpringBoot返回xml格式,需要导入xml处理包,版本号则使用SpringBoot的版本仲裁

    <!--导入处理返回xml类型的依赖-->
          <dependency>
              <groupId>com.fasterxml.jackson.dataformat</groupId>
              <artifactId>jackson-dataformat-xml</artifactId>
          </dependency>
    
  • 为什么通过浏览器请求,总是返回xml格式呢?(前提导入了依赖)

  • 因为浏览器的Http请求头中,Accept中的参数设置了权重【 text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng, / ;q=0.8】,其中text/html,application/xhtml+xml,application/xml的权重为0.9,而image/avif,image/webp,image/apng, * / *;q=0.8的权重为0.8,所以xml的权重比JSON格式的权重大,权重越大优先级越高,因而总是返回xml格式

  • Postman可以通过修改Accept的值,来返回不同的数据格式

  • 对于浏览器,我们无法修改其Accept的值,如何实现修返回不同数据格式

  • 解决方案:开启支持基于请求参数的内容协商功能

    • 设置application.yml,开启基于请求参数的内容协商功能

      spring:
      mvc:
        contentnegotiation:
          favor-parameter: true #开启基于请求参数的内容协商功能
      
    • 请求方式

      image-20240808164627823

  • 注意,参数format是规定好的,在开启请求参数的内容协商功能后,SpringBoot底层ParameterContentNegotiationStrategy会通过format来接收参数,然后返回对应的媒体类型/数据格式,当然format=媒体类型/数据类型,这个媒体类型/数据格式,是SpringBoot可以进行处理的才行,不能乱写

  • 如果要修改format这个参数名称,要在application.yml中进行设置

    spring:
      mvc:
        contentnegotiation:
          favor-parameter: true #开启基于请求参数的内容协商功能
          parameter-name: leonformat #指定一个内容协商的参数名
    
  • 请求方式

    image-20240808165318244

十四、Thymeleaf

1.官方文档

  • 在线文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
  • pdf文件网址:https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.pdf

2.基本介绍

  • Thymeleaf是什么
  • Thymeleaf是一个跟Velocity、FreeMarker类似的模板引擎,可完全替代JSP
  • Thymeleaf是一个java类库,他是一个xml/xhtml/html5的模板引擎,可以作为mvc的web应用的view层
  • Thymeleaf的优点
  • 实现JSTL、OGNL表达式效果,语法相似,java程序员上手快
  • Thymeleaf模板页面无需服务器渲染,也可以被浏览器运行,页面简洁
  • SpringBoot支持FreeMarker、Thymeleaf、veocity
  • Thymeleaf的缺点
  • Thymeleaf:Thymeleaf is a modern server-side Java template engine for both web and standlone environments
  • 缺点:并不是一个高性能的引擎,适用用于单体应用
  • 如果要做一个高并发的应用,选择前后端分离更好,但是作为SpringBoot推荐的模板引擎,还是推荐学习一下。

3.Thymeleaf机制说明

  • Thymeleaf是服务器渲染技术,页面数据是在服务器端进行渲染的
  • 比如:manage.html中一段Thymeleaf代码,是在用户请求该页面时,有Thymeleaf模板引擎完成处理的(在服务器端完成),并将结果页面返回
  • 因此使用Thymeleaf,并不是前后端分离

4.Thymeleaf语法

4.1表达式

  • 表达式一览

    | 表达式名字 | 语法 | 用途 |
    | ———- | —— | ————————————- |
    | 变量取值 | ${…} | 获取request域、Session域,对象等值 |
    | 选择变量 | *{…} | 获取上下文对象值 |
    | 消息 | #{…} | 获取国际化等值 |
    | 链接 | @{…} | 生成链接 |
    | 片段表达式 | ~{…} | 类似jsp:include作用,引入公共页面片段 |

  • 字面量

  • 文本值:'hello','hhh',….

  • 数字:10,23,6…

  • 布尔值:true,false

  • 空值:null

  • 变量:name,age,…[变量不能有空格]

  • 文本操作

  • 字符串拼接:+

  • 变量替换:|age=${age}|

4.2运算符

  • 数学运算符
  • 运算符:+,-,*,/,%
  • 布尔运算
  • 运算符:and,or
  • 一元运算符:!,not
  • 比较运算符
  • 比较:<,>,>= ,<= (gt,lt,ge,le)
  • 等式:==,!= (eq,ne)
  • 条件运算符
  • If-then:(if) ? (then)
  • If-then-else:(if) ? (then) : (else)
  • Default:(value) ?: (defaultvalue)

4.3th属性

  • html有的属性,Thymeleaf基本都有,而常用的属性大概有七八个。其中th属性执行的优先从1~8,数字越低优先级越高
  • th:text 设置当前元素的文本内容,相同功能的还有th:utext,两者的区别在于前者不会转义html标签,后者会。优先级不高:order=7
  • th:value 设置当前元素的value值,类似修改指定属性的还有th:src,th:href。优先级不高:order=6
  • th:each 遍历循环元素,和th:text或th:value一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
  • th:if 条件判断,类似的还有th:unless,th:switch,th:case。优先级较高:order=3
  • th:insert 代码块引入,类似的还有th:replace,th:include,三者区别较大,若使用不恰当会被破坏html结构,常用于公共代码块提取场景。优先级最高:order=1
  • th:fragment 定义代码块,方便被th:insert引用。优先级最低:order=8
  • th:object 声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
  • th:attr 修改任意属性,实际开发中用的较少,因为有丰富的其他th属性帮忙,类似的还有th:attrappend,th:attrprepend。优先级一般:order=5

4.4迭代

<tr th:each="prod:${prods}">
    <td th:text="${pord.name}">Onions</td>
    <td th:text="${pord.price}">2.14</td>
    <td th:text="${pord.inStock} ? #{true} : #{false}">yes</td>
<tr/>

<tr th:each="prod,iterStat:${prods}" th:class="${iterStat.odd} ? 'odd'">
    <td th:text="${pord.name}">Onions</td>
    <td th:text="${pord.price}">2.14</td>
    <td th:text="${pord.inStock} ? #{true} : #{false}">yes</td>
<tr/>

4.5条件运算符

<a href="comments.html" 
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #list.isEmpty(prod.comments)}">view</a>

<div th:swicth="${user.role}">
    <p th:case="'admin'">User is an administrator</p>
    <p th:case="#{roles.manager}">User is an manager</p>
    <p th:case="*">User is some other thing</p>
</div>

4.6使用Thymeleaf属性需要注意的点

  • 若要使用Thymeleaf语法,首先要先声明名称空间:xmlns:th=“http://www.thymeleaf.org”
  • 设置文本内容th:text,设置input的值th:value,循环输出th:each,条件判断th:if,插入代码块th:insert,定义代码块th:fragment,声明变量th:object
  • th:each 的用法需要格外注意,举个例子 :如果你要循环一个div中的p标签,则th;each属性必须放在p标签上。若你将th:each属性放在div上,则循环的是整个div
  • 变量表达式中提供了很多的内置方法,该内置方法使用#开头,请不要与#{}消息表达式弄混

5.应用实例

5.1 IndexController.java

package com.leon.springboot.controller;

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

/**
 * ClassName:IndexController
 * Package:com.leon.springboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/8 21:17
 * @Version: 1.0
 */
@Controller
public class IndexController {


    @GetMapping({"/","/login"})
    public String login(){
        //作为一个中转
        /*
        *  1.因为我们引入了starter-thymeleaf,这里就会直接使用视图解析到Thymeleaf下的模板文件adminLogin.html
        * */
        return "adminLogin";
    }

}

5.2 AdminController.java

package com.leon.springboot.controller;

import com.leon.springboot.bean.Admin;
import com.leon.springboot.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.http.HttpSession;
import java.util.ArrayList;

/**
 * ClassName:AdminController
 * Package:com.leon.springboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/8 21:28
 * @Version: 1.0
 */
@Controller
public class AdminController {

    @PostMapping("/login")
    public String manage(Admin admin, HttpSession session, Model model) {

        //判断用户是否合法
        if (StringUtils.hasText(admin.getUsername()) && "666".equals(admin.getPassword())) {
            //设置用户登录
            session.setAttribute("admin", admin);
            //跳转到目标方法
            return "redirect:/manage.html";
        }
        //设置错误信息
        model.addAttribute("error", "用户密码不正确");

        return "adminLogin";


    }

    @GetMapping("/manage.html")
    public String manage(HttpSession session,Model model) {
        //判断用户是否登录
        if(session.getAttribute("admin") != null){

            ArrayList<User> users = new ArrayList<>();

            users.add(new User(1,"关羽","6666666",20,"gy@shouhu.com"));
            users.add(new User(2,"张飞","6666666",20,"zf@shouhu.com"));
            users.add(new User(3,"赵云","6666666",20,"zy@shouhu.com"));
            users.add(new User(4,"马超","6666666",20,"mc@shouhu.com"));
            users.add(new User(5,"黄忠","6666666",20,"hz@shouhu.com"));

            model.addAttribute("users", users);

            //如果登录则跳转到管理页面
            return "manage";

        }

        //设置错误信息
        model.addAttribute("error","请先登录");

        return "adminLogin";


    }
}

5.3adminLogin.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>用户登陆</h1>
    <form action="#" th:action="@{/login}"  method="post">
        <label style="color: red" th:text="${error}"></label><br/>
        用户名:<input type="text" style="width:150px" name="username"/><br/><br/>
        密 码 :<input type="password" style="width:150px" name="password"/><br/><br/>
        <input type="submit" value="登录"/>
        <input type="reset" value="重新填写"/>
    </form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>

5.4manage.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>管理后台</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<a href='#'>返回管理界面</a> <a href='#' th:href="@{/login}">安全退出</a> 欢迎您:[[${session.admin.username}]]
<hr/>
<div style="text-align: center">

    <h1>管理雇员~</h1>

    <table border="1px" cellspacing="0" bordercolor="green" style="width:800px;margin:auto">
        <tr bgcolor="pink">
            <td>id</td>
            <td>name</td>
            <td>pwd</td>
            <td>age</td>
            <td>email</td>
        </tr>
        <tr bgcolor="#ffc0cb" th:each="user:${users}">
            <td th:text="${user.id}">a</td>
            <td th:text="${user.name}">b</td>
            <td th:text="${user.password}">c</td>
            <td th:text="${user.age}">d</td>
            <td th:text="${user.email}">e</td>
        </tr>
    </table>
    <br/>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>

5.5Admin.java

package com.leon.springboot.bean;

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

/**
 * ClassName:Admin
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/8 21:26
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Admin {
    //用户名
    private String username;
    //密码
    private String password;

}

5.6User.java

package com.leon.springboot.bean;

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

/**
 * ClassName:User
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/8 21:26
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;

    private String name;

    private String password;

    private Integer age;

    private String email;

}

十五、拦截器-HandlerInterceptor

1.基本介绍

  • 在Spring Boot 项目中,拦截器是开发中常用手段,要来做登录验证、性能检查、日志记录等。
  • 基本步骤
  • 编写一个拦截器实现HandlerInterceptor接口
  • 拦截器注册到配置类中(实现WebMvcConfig的addInterceptor)
  • 指定拦截规则

2.应用实例

2.1 LoginInterceptor.java

package com.leon.springboot.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * ClassName:LoginInterceptor
 * Package:com.leon.springboot.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/10 20:06
 * @Version: 1.0
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /*
     *  1.目标方法执行前被调用
     * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求的URI
        String requestURI = request.getRequestURI();

        log.info("requestURI: {}", requestURI);

        //进行登录校验
        Object admin = request.getSession().getAttribute("admin");
        //判断用户是否登录
        if (null != admin) {
            //放行
            return true;
        }

        //拦截,重新返回登录页面
        request.setAttribute("error", "请登录");

        //进行请求转发
        request.getRequestDispatcher("/").forward(request, response);

        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle被执行了");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion被执行了");
    }
}

2.2 WebConfig.java

package com.leon.springboot.config;

import com.leon.springboot.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * ClassName:WebConfig
 * Package:com.leon.springboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/10 20:20
 * @Version: 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //将自定的LoginInterceptor注册到容器中
        //添加拦截规则,和排除哪些拦截
        registry.addInterceptor(new LoginInterceptor()).
                addPathPatterns("/**").//这里表示拦截所有请求
                excludePathPatterns("/","/login","/images/**");//表示排除哪些请求,这里/images/**前面不用加/static的原因是静态资源默认请求

    }
}

3.注意事项

  • URI和URL

  • URI = Universal Resource Identifier

  • URL = Universal Resource Locator

  • Identifier:标识符,Localtor:定位器。从字面上来看,URI可以唯一标识一个资源,URL可以可提供到该资源的路径

  • 举例

    String requestURI = request.getRequestURI();
    String requestURL = request.getRequestURL().toString();
    
  • 第二种注册方式

     @Bean
      public WebMvcConfigurer webMvcConfigurer() {
    
          return new WebMvcConfigurer() {
    
              @Override
              public void addInterceptors(InterceptorRegistry registry) {
                          registry.addInterceptor(new LoginInterceptor()).
                        addPathPatterns("/**").
                        excludePathPatterns("/","/login","/images/**");
    
            }
        };
    
    }
    

十六、文件上传

1. 应用实例

1.1 后端代码

package com.leon.springboot.controller;

import com.leon.springboot.bean.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * ClassName:UploadController
 * Package:com.leon.springboot.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/10 21:06
 * @Version: 1.0
 */
@Controller
public class UploadController {


    private static final Logger log = LoggerFactory.getLogger(UploadController.class);

    @GetMapping("/upload.html")
    public String toUpload() {

        return "upload";

    }


    @PostMapping("/upload")
    @ResponseBody
    public String upload(Employee employee, @RequestParam(value = "header",required = false) MultipartFile header,
                         @RequestParam(value = "photos",required = false) MultipartFile[] photos) throws IOException {

        //获取项目的类路径
        String filePath = ResourceUtils.getURL("classpath:").getFile();

        //创建一个新的目录
        File file = new File(filePath + "static/images/upload/");
        //判断文件是否存在
        if(!file.exists()){
            //创建文件
            file.mkdirs();

        }



        //C:\software\javacodelibrary\distributed-microservices\springboot-usersys\target\classes
        //判断文件对象是否为空
        if(!header.isEmpty()){
            //获取文件名称
            String filename = header.getOriginalFilename();
            //保存文件
            //header.transferTo(new File("C:\\software\\javacodelibrary\\distributed-microservices\\springboot-usersys\\target\\classes\\"+filename));

            //log.info("绝对路径:{}",file.getAbsoluteFile());
            header.transferTo(new File(file.getAbsoluteFile() + "/" + filename));
        }
        //判断头像是否为空
        if(photos.length>0){
            //循环遍历
            for (MultipartFile photo : photos) {
                //判断文件对象是否为空
                if (!photo.isEmpty()){
                    //获取文件名
                    String filename = photo.getOriginalFilename();
                    ////保存文件
                    //photo.transferTo(new File("C:\\software\\javacodelibrary\\distributed-microservices\\springboot-usersys\\target\\classes\\" + filename));
                    photo.transferTo(new File(file.getAbsoluteFile() + "/" + filename));

                }

            }

        }
        return "success";
    }

}

1.2 前端代码

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>注册用户~</h1>
    <form action="#" method="post" th:action="@{/upload}" enctype="multipart/form-data">
        用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
        电 邮:<input type="text" style="width:150px" name="email"/><br/><br/>
        年 龄:<input type="text" style="width:150px" name="age"/><br/><br/>
        职 位:<input type="text" style="width:150px" name="job"/><br/><br/>
        头 像:<input type="file" style="width:150px" name="header"><br/><br/>
        宠 物:<input type="file" style="width:150px" name="photos" multiple><br/><br/>
        <input type="submit" value="注册"/>
        <input type="reset" value="重新填写"/>
    </form>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>

2. 注意事项

  • 注意如果单个文件上传超过1MB,批量上传超过10MB,需要设置application.yml文件,配置如下

    spring:
    servlet:
      multipart:
        max-file-size: 10MB #单个文件最大请求数
        max-request-size: 50MB #最大文件请求数
    

十七、异常处理

1. 基本介绍

  • 默认情况下,Spring Boot 提供 /error处理所有错误(异常)的映射,也就是说当出现错误(异常)时,Spring Boot底层会请求转发到/error这个映射路径。

  • 比如使用浏览器访问不存在接口(路径映射),响应一个“whitelabel”错误视图,以HTML格式呈现给用户

    image-20240824154951332

  • Spring Boot底层默认由DefaultErrorViewResolver处理错误(异常)

2. 拦截器VS过滤器

  • 使用范围不同

  • 过滤器:实现的是javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,Filter只能在web程序中使用

  • 拦截器(Interceptor):它是是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application等程序中

  • 过滤器和拦截器的出发时机也不同,如图

  • 过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后

  • 拦截器Interceptor是在请求进入servlet后,在进入Controller之前进行预处理的,Controller中渲染了对应的视图之后请求结束

  • 说明:过滤器不会处理请求转发,拦截器会处理请求转发

3. 自定义异常页面说明

3.1 学习文档

  • 学习网址:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/htmlsingle/#web.servlet.spring-mvc.error-handling

3.2 自定义异常页面说明

  • 如何找到目标位置

    image-20240824164937383

4. 自定义异常页面实现

4.1 需求

  • 自定义404.html,500.html,4xx.html,5xx.html当发生相应错误时,显示自定义的页面信息

4.2 代码实现

4.2.1 404.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>4o4 Not Found</h1>
    <a href='#' th:href="@{/}">返回主页面</a></br>
    状态码:<h1 th:text="${status}"></h1></br>
    异常信息:<h1 th:text="${error}"></h1>

</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
4.2.2 500.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>500 内部服务器出现了问题</h1><br/>
    状态码:<h1 th:text="${status}"></h1></br>
    异常信息:<h1 th:text="${error}"></h1>
    <a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
4.2.3 4xx.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>4xx 发生错误了:)</h1>
    错误状态码: <h1 th:text="${status}"></h1><br/>
    <a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>
4.2.4 5xx.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    <h1>5xx 发生错误了:)</h1><br/>
    错误状态码: <h1 th:text="${status}"></h1><br/>
    <a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>

5.全局异常

5.1 说明

  • @ControllerAdvice+@ExceptionHandler处理全局异常
  • 底层是ExceptionHandlerExceptionResolver支持的

5.2 全局异常-应用实例

需求:演示全局异常使用,当发生ArithmeticException、NullPointerException时,不使用默认异常机制匹配的xxx.html,而显示全局异常机制指定的错误页面

5.2.1 Controller
package com.leon.springboot.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.HandlerMethod;

/**
 * ClassName:GlobalExceptionHandler
 * Package:com.leon.springboot.exception
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/24 17:25
 * @Version: 1.0
 *
 * @ControllerAdvice :使用它标识一个全局异常处理器/对象,会注入到Spring容器
 */
//@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {


    /*
    *   1.这个方法是用来处理指定异常的,例如算数异常和空指针异常,这里处理的异常可以由程序员来指定
    *   2.Exception ex:表示异常发生后,传递的异常对象
    *   3.Model model:可以将我们的异常信息,放入model然后传递到显示页面
    * */
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
    public String  handleArithmeticException(Exception ex, Model model, HandlerMethod handlerMethod){
        //输出异常信息
        System.out.println("异常信息==="+ex.getMessage());

        //将异常信息放入到model,然后到错误页面显示
        model.addAttribute("err",ex.getMessage());
        //获取异常发生的方法
        System.out.println(handlerMethod.getMethod());

        return "/error/global";
    }

}
5.2.2 前端代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>全局异常</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
    韩顺平Java 工程师
    <h1>全局异常/错误 发生了:)</h1><br/>
    异常/错误信息:<h1 th:text="${err}"></h1><br/>
    <a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
<img src="images/logo.png"/>
</body>
</html>

6.自定义异常

6.1 说明

  • 如果Spring Boot提供的异常不能满足开发需求,程序员也可以自己定义异常
  • @ResponseStatus+自定义异常
  • 底层是ResponseStatusExceptionResolver,底层调用response.sendError(statusCode,resolvedReason)
  • 当抛出自定义异常后,仍然会根据状态码,去匹配使用xxx.html显示,当然也可以将自定义异常,放在全局异常处理器去处理

6.2 需求说明

  • 自定义一个异常AccessException,当用户访问某个无权访问的路径时,抛出该异常,显示自定义状态码

6.3 应用实例

6.3.1 代码
package com.leon.springboot.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * ClassName:AccessException
 * Package:com.leon.springboot.exception
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/24 22:08
 * @Version: 1.0
 */
/*
*  1.本类是一个自定义异常
*  2. value = HttpStatus.FORBIDDEN:表示发生AccessException异常,我们通过Http协议返回的状态码 403
* */
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException {

    //提供一个构造器可以指定错误信息
    public AccessException(String message) {
        super(message);
    }

    //提供一个无参构造器,
    public AccessException(){

    }









}

6.4 注意事项和细节

  • 如果把自定义异常类型,放在全局异常处理器,那么仍然走全局异常处理机制
  • 异常处理优先级:全局异常 > 默认异常机制

十八、Spring Boot中注入Servlet、Filter、Listenter

1. 官方文档

  • 官方学习网址:https://docs.spring.io/spring-boot/docs/3.2.8-SNAPSHOT/reference/htmlsingle/#web.servlet.embedded-container.servlets-filters-listeners

2. 基本介绍

  • 考虑到实际开发业务非常复杂和兼容,Spring Boot支持将Servlet、Filter、Listener注入Spring容器,成为Spring bean
  • 也就是说明Spring Boot开放了和原生WEB组件(Servlet、Filter、Listener)的兼容

3. 应用实例

3.1 注解方式注入

3.1.1 注入Servlet
package com.leon.springboot.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * ClassName:Servlet_
 * Package:com.leon.springboot.servlet
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/26 20:24
 * @Version: 1.0
 */
/*
*   1.通过继承HttpServlet来开发原生的Servlet
*   2.@WebServlet注解是将Servlet对象注入到Spring Boot容器中
*   3.(urlPatterns = {"/servlet01","/servlet02"}),对Servlet对象配置url-pattern,也就是配置映射路径
*   4.注意:注入的原生的Servlet被请求时,不会被Spring Boot的拦截器拦截
*   5.对于开发原生的Servlet,需要使用@ServletComponent注解来指定扫描原生Servlet,才会被
*     注入到Spring Boot容器中
* */
@WebServlet(urlPatterns = {"/servlet01","/servlet02"})
public class Servlet_ extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello Servlet_ ------");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

3.1.2 注入Filter

package com.leon.springboot.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * ClassName:Filter_
 * Package:com.leon.springboot.servlet
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/26 20:48
 * @Version: 1.0
 */
/*
*  1.@WebFilter注解表示被标识的是Filter,并注入到容器中
*  2.urlPatterns = {"/css/*","/images/*"},当请求/css/和/images/下的目录或资源时,会经过过滤器
*  3.在过滤器放行之后,拦截器是否放行,需要根据拦截器的规则来确定的
*  4.过滤器配置的urlPattern也会经过Spring Boot拦截器(要同时配置了相同的映射路径),在Servlet中匹配全部是[/单*],在Spring Boot中是[/双*]
*  5.
*/
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/images/*"})
public class Filter_ implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
         log.info("===Filter_init===");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //打印信息
        log.info("===Filter_doFilter===");

        //为了方便观察过滤器处理的资源,这里输出请求的uri
        HttpServletRequest request = (HttpServletRequest)servletRequest;

        log.info("请求的URI==={}",request.getRequestURI());
        //放行,在实际开发中按照自己的实际业务来决定如何处理
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        log.info("===Filter_destroy===");
    }
}

3.1.3 注入Listener

package com.leon.springboot.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * ClassName:Listener_
 * Package:com.leon.springboot.servlet
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/26 21:19
 * @Version: 1.0
 */
@Slf4j
@WebListener
public class Listener_ implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        log.info("---contextInitialized============");
    }

    public void contextDestroyed(ServletContextEvent sce) {
        log.info("---contextDestroyed============");
    }
}

3.2 使用RegistrationBean方式注入

3.2.1 注入Servlet
    //使用RegistrationBean方式注入
    //注入原生Servlet
    @Bean
    public ServletRegistrationBean servlet_(){
        //创建原生Servlet对象
        Servlet_ servlet = new Servlet_();
        /*
        *  1.把Servlet对象关联到ServletRegistrationBean对象
        *  2."/servlet01","/servlet02" 就是注入的Servlet的url-pattern
        * */
        return new ServletRegistrationBean(servlet,"/servlet01","/servlet02");
    }

3.2.2 注入Filter

 //注入Filter
    @Bean
    public FilterRegistrationBean filter_(){
        //创建原生的filter
        Filter_ filter = new Filter_();
        //创建FilterRegistrationBean对象,并将filter对象关联到FilterRegistrationBean对象
        FilterRegistrationBean filterRegistrationBean =
                new FilterRegistrationBean(filter);
        //添加过滤的映射路径
        filterRegistrationBean.addUrlPatterns("/css/*");

        return filterRegistrationBean;
    }

3.2.3 注入Listener

  //注入Listener
    @Bean
    public ServletListenerRegistrationBean listener_(){
        //创建一个listener对象
        Listener_ listener = new Listener_();
        //将Listener对象关联到ServletListenerRegistrationBean对象
        return new ServletListenerRegistrationBean(listener);

    }

4.请求Servlet为什么不会到拦截器

  • 请求原生Servlet时,不会到达DispatcherServlet,因此也不会到达拦截器,因为原生Servlet和DispatcherServlet都是Servlet接口的实现类,属于同一级别

  • 原因分析

  • 注入的原生Servlet和DispatcherServlet都会存在Spring Boot容器中

  • 说明

    • 多个Servlet都能处理同一个映射路径时,精确优先原则或最长前缀匹配原则

    • 优先级遵守:精确匹配 > 目录匹配 > 扩展名匹配 > /* > /

  • 在Spring Boot中,去调用@Controller目标方法是按照DispatcherServlet分发匹配的机制

十九、内置Tomcat配置和切换

1. 基本介绍

  • Spring Boot支持的WebServer:Tomcat,Jetty,Undertow
  • Spring Boot 应用启动Web应用时,需要导入Web场景-导入Tomcat
  • Spring Boot支持对Tomcat(也可以是Jetty、Undertow)的配置和切换

2. 内置Tomcat的配置

2.1 通过application.yml完成配置

server:
  #配置监听的端口号
  port: 9999
  tomcat: #对Tomcat进行配置
    threads:
      max: 10 #表示设置最大的工作线程,默认是200
      min-spare: 5 #表示设置最小工作线程,默认是10
    accept-count: 200 #表示设置Tomcat启动的线程达到最大值,接受排队的请求数,默认是100
    max-connections: 2000 #表示设置Tomcat最大并发数(连接数),默认是8192
    connection-timeout: 10000 #表示设置Tomcat建立连接的超时时间,默认单位是:毫秒
    #等等.....

2.2 通过类来配置Tomcat

package com.leon.springboot.config;

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

/**
 * ClassName:CustomizationBean
 * Package:com.leon.springboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 15:43
 * @Version: 1.0
 */
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
        //设置服务端口
        factory.setPort(9090);

    }
}

3.切换Tomcat和Undertow

3.1 修改pom.xml文件

    <dependencies>
        <!--导入web项目场景启动器:会自动导入和web开发相关的依赖[库/jar]-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.3</version>
            <!--如果想要切换其他的服务器,例如Undertow,需要将Tomcat场景排除-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--引入Thymeleaf-start:会进行默认配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--引入Undertow-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
    </dependencies>

3.2 注意细节

  • 排除掉Tomcat后,使用相关Servlet的类都会报错,可以注销掉或者引入新的服务器

二十、数据库操作

1. JDBC+HikarDataSource

1.1 应用实例-需求

  • 演示Spring Boot如何通过JDBC+HikariDataSource完成对Mysql操作
  • 说明:HikariDataSource是目前市面上非常优秀的数据源,是Spring Boot2默认的数据源

1.2应用实例-代码实现

1.2.1 数据库操作
--创建furns_ssm
 DROP DATABASE IF EXISTS spring_boot;
 CREATE DATABASE spring_boot;
 USE spring_boot;##创建家居表
CREATE TABLE furn(
 `id`INT(11)PRIMARY KEY AUTO_INCREMENT, ##id
 `name`VARCHAR(64)NOT NULL, ##家居名
`maker`VARCHAR(64)NOT  NULL, ##厂商
`price`DECIMAL(11,2)NOT NULL, ##价格
`sales`INT(11)NOT NULL, ##销量
`stock`INT(11)NOT NULL, ##库存
`img_path` VARCHAR(256) NOT NULL ## 照片路径
);
-- 初始化家居数据
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL,'北欧风格小桌子','熊猫家居' , 180 , 666 , 7 ,'assets/images/product-image/1.jpg');

INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 简约风格小椅子 ' , ' 熊 猫 家 居 ' , 180 , 666 , 7 ,'assets/images/product-image/2.jpg');

INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 典 雅 风 格 小 台 灯 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,'assets/images/product-image/3.jpg');

INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 温 馨 风 格 盆 景 架 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,'assets/images/product-image/4.jpg');
 SELECT * FROM furn;
1.2.2 pom.xml操作
 <!--进行数据库开发,引入JDBC开发场景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <!--引入mysql的驱动
            使用版本仲裁
        -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--引入测试场景启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
1.2.3 application.yml操作
  #配置数据源
  datasource:
    #说明:有的版本没有设置useSSL=true启动项目会报红警告
    url: jdbc:mysql://localhost:3306/spring_boot?useSSL=true&userUnicode=true&characterEncoding=UTF-8
    #配置连接用户
    username: root
    #配置连接密码
    password: root
    #配置驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
1.2.4 javaBean
package com.leon.springboot.bean;

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

import java.math.BigDecimal;

/**
 * ClassName:Furn
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 16:25
 * @Version: 1.0
 */
public class Furn {

    private Integer id;
    private String name;
    private String maker;
    private BigDecimal price;
    private Integer sales;
    private Integer stock;
    private String imgPath = "assets/images/product-image/1.jpg";


    public Furn() {
    }

    public Furn(Integer id, String name, String maker, BigDecimal price, Integer sales, Integer stock, String imgPath) {
        this.id = id;
        this.name = name;
        this.maker = maker;
        this.price = price;
        this.sales = sales;
        this.stock = stock;
        this.imgPath = imgPath;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getMaker() {
        return maker;
    }

    public void setMaker(String maker) {
        this.maker = maker;
    }

    public BigDecimal getPrice() {
        return price;
    }

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

    public Integer getSales() {
        return sales;
    }

    public void setSales(Integer sales) {
        this.sales = sales;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }

    @Override
    public String toString() {
        return "Furn{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", maker='" + maker + '\'' +
                ", price=" + price +
                ", sales=" + sales +
                ", stock=" + stock +
                ", imgPath='" + imgPath + '\'' +
                '}';
    }
}
1.2.5 测试
package com.leon.springboot.test;

import com.leon.springboot.bean.Furn;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName:HikariDataSourceTest
 * Package:com.leon.springboot.test
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 16:27
 * @Version: 1.0
 */
/*
* 1.在Spring Boot项目中,使用测试类,需要引入测试场景以及在测试类上标注@SpringBootTest注解
* */
@SpringBootTest
public class HikariDataSourceTest {


    @Resource
    private JdbcTemplate jdbcTemplate;

    @Test
    public void jdbcHikariDataSource(){
        //创建一个BeanPropertyRowMapper对象对查询到的数据封装到JavaBean
        BeanPropertyRowMapper<Furn> rowMapper =
                new BeanPropertyRowMapper<>(Furn.class);
        //查询所有数据
        List<Furn> query = jdbcTemplate.query("SELECT * FROM furn", rowMapper);
        //循环输出
        for (Furn furn : query) {

            System.out.println(furn);

        }
        //输出是什么数据源
        System.out.println(jdbcTemplate.getDataSource().getClass());
    }

}

2.整合Druid到Spring Boot

2.1 官方文档

  • 官方网址:https://github.com/alibaba/druid
  • 中文学习文档:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
  • Spring Boot中Druid场景启动器学习网址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

2.2 基本介绍

  • HikariCP:是目前市面上非常优秀的数据源,是Spring Boot2默认的数据源
  • Druid:性能优秀,Druid提供性能卓越的连接池功能外,还集成了SQL监控,黑名单拦截等功能,强大的监控特性,通过Druid提供的监控功能,可以清楚知道连接池和SQL的工作情况,所以根据项目需要,需要掌握Druid和SpringBoot整合
  • 整合Druid到Spring Boot有两种方式
  • 自定义方式
  • 引入starter方式

2.3 基本使用

2.3.1 pom.xml配置
 <!--引入Druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>
2.3.2 创建配置类
package com.leon.springboot.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;

/**
 * ClassName:DruidDataSourceConfig
 * Package:com.leon.springboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 17:04
 * @Version: 1.0
 */
@Configuration
public class DruidDataSourceConfig {

    /*
    * 1.为什么注入DruidDataSource后默认的HikariDataSource就会失效?
    *   1.1 @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    *       表示只有在Spring Boot容器中没有DataSource时才会注入HikariDataSource
    * */
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DruidDataSource druidDataSource() {

        /*
        * 1.配置了@ConfigurationProperties("spring.datasource")就可以读取到application.yml的配置
        * */
        //创建一个DruidDataSource对象
        DruidDataSource druidDataSource =
                new DruidDataSource();

        return druidDataSource;
    }

}

2.4 Druid监控功能-SQL监控

2.4.1 需求
  • 配置Druid的监控功能,包括SQL监控、SQL防火墙、Web应用、Session监控等
2.4.2 SQL监控
  • 增加Druid监控功能
  • 参考手册:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
2.4.2.1 配置Druid监控页和SQL监控
package com.leon.springboot.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 java.sql.SQLException;

/**
 * ClassName:DruidDataSourceConfig
 * Package:com.leon.springboot.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 17:04
 * @Version: 1.0
 */
@Configuration
public class DruidDataSourceConfig {

    /*
    * 1.为什么注入DruidDataSource后默认的HikariDataSource就会失效?
    *   1.1 @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    *       表示只有在Spring Boot容器中没有DataSource时才会注入HikariDataSource
    * */
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DruidDataSource druidDataSource() {

        /*
        * 1.配置了@ConfigurationProperties("spring.datasource")就可以读取到application.yml的配置
        * */
        //创建一个DruidDataSource对象
        DruidDataSource druidDataSource =
                new DruidDataSource();
        try {
            //添加SQL监控功能
            druidDataSource.addFilters("stat");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        return druidDataSource;
    }

    //配置Druid的监控页功能
    @Bean
    public ServletRegistrationBean statViewServlet() {

        //创建一个StatViewServlet对象
        StatViewServlet statViewServlet =
                new StatViewServlet();
        //将StatViewServlet对象关联到ServletRegistrationBean对象中
        ServletRegistrationBean servletRegistrationBean =
                new ServletRegistrationBean(statViewServlet, "/druid/*");
        //设置init-parameter
        //设置用户
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        //设置密码
        servletRegistrationBean.addInitParameter("loginPassword", "666");

        return servletRegistrationBean;

    }
}

2.5 Druid监控功能-Web关联监控

2.5.1 需求
  • 配置Web关联控制配置:Web应用、URI监控
  • 官方文档:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
2.5.2 Web关联监控配置:Web应用、URI监控
 @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        //创建一个WebStatFilter对象
        WebStatFilter webStatFilter = new WebStatFilter();
        //创建一个FilterRegistrationBean对象,然后将WebStatFilter关联
        FilterRegistrationBean filterRegistrationBean =
                new FilterRegistrationBean(webStatFilter);
        //设置过滤映射路径
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
        //设置排除哪里映射路径
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

        return filterRegistrationBean;

    }

2.6 Druid监控功能-SQL防火墙

2.6.1 需求
  • 配置SQL防火墙
  • 官方文档:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
2.6.2 SQL防火墙
   /*
    * 1.为什么注入DruidDataSource后默认的HikariDataSource就会失效?
    *   1.1 @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    *       表示只有在Spring Boot容器中没有DataSource时才会注入HikariDataSource
    * */
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DruidDataSource druidDataSource() {

        /*
        * 1.配置了@ConfigurationProperties("spring.datasource")就可以读取到application.yml的配置
        * */
        //创建一个DruidDataSource对象
        DruidDataSource druidDataSource =
                new DruidDataSource();
        try {
            //添加SQL监控功能,加入防火墙功能
            druidDataSource.addFilters("stat,wall");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        return druidDataSource;
    }

2.7 Druid监控功能-Session监控

2.7.1 需求
  • 配置Session监控
  • 官方文档:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
2.7.2 Session监控
  • 默认会开启不需要配置

2.8 Druid Spring Boot Stater

2.8.1 基本介绍
  • 前面我们使用的是自己引入druid+配置类方式整合Druid和监控
  • Druid Spring Boot Starter可以让程序员在Spring Boot项目中更加轻松集成Druid和监控
2.8.2 应用实例
2.8.2.1 需求
  • 使用Druid Spring Boot Starter方式完成Druid集成和监控
2.8.2.2 具体实现
  • application.yml文件配置
    #配置Druid
    druid:
      #开启监控页功能
      stat-view-servlet:
        enabled: true
        #设置登录账号
        login-username: admin
        #设置登录密码
        login-password: 666
        #关闭重置
        reset-enable: false
        #配置Web监控
      web-stat-filter:
        enabled: true
        #设置过滤映射路径
        url-pattern: /*
        #设置排除哪里映射路径
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      #配置SQL监控
      filter:
        stat:
          enabled: true
          #设置慢查询时间条件,如果低于1秒则就是慢查询,单位时间:毫秒秒
          slow-sql-millis: 1000
          #启用慢查询日志
          log-slow-sql: true
        #配置SQL防火墙
        wall:
          enabled: true
          #拦截配置
          config:
            #如果是删除语句就不执行
            drop-table-allow: false
            #如果是select * from furn 则不执行
            select-all-column-allow: false
  • pom.xml文件配置
<!--引入Druid spring boot starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

二十一、Spring Boot整合Mybatis

1. 代码实现

1.1 DruidDataSourceConfig.java

package com.leon.springboot.mybatis.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;

/**
 * ClassName:DruidDataSourceConfig
 * Package:com.leon.springboot.mybatis.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 22:26
 * @Version: 1.0
 */
@Configuration
public class DruidDataSourceConfig {


    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource druidDataSource(){

        return new DruidDataSource();

    }

}

1.2 application.yml配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_boot?useSSL=true&userUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
#配置Mybatis
#说明:配置Mybatis的两种方式的选择:如果配置比较简单,就直接在application.yml中配置
#如果非常复杂,就使用传统的方式,在mybatis-config.xml文件中配置
mybatis:
  #配置指定要扫描的Mapper.xml文件
  mapper-locations: classpath:/mapper/*.xml
#  #配置指定去扫描mybatis.xml文件,可以通过传统方式来配置mybatis
#  config-location: classpath:mybatis-config.xml
  #配置TypeAliases
  type-aliases-package: com.leon.springboot.mybatis.bean
  #配置日志输出
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #启用自动驼峰配置
    map-underscore-to-camel-case: true

1.3 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置Mybatis自带的日志
       1.注意这个标签要放在最上面
   -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>

    </settings>

    <!--配置别名-->
    <typeAliases>
        <!--
            1.如果一个包下面有很多的类,可以直接引入包,这样该包下的所有类,可以直接使用类名
        -->
        <package name="com.leon.springboot.mybatis.bean"/>
    </typeAliases>

</configuration>

1.4 FurnMapper.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.springboot.mybatis.dao.FurnMapper">

    <select id="queryFurnById" resultType="Furn"  parameterType="java.lang.Integer">
        select * from furn where id = #{id};
    </select>

</mapper>

1.5 Service

package com.leon.springboot.mybatis.service;

import com.leon.springboot.mybatis.bean.Furn;

/**
 * ClassName:FurnService
 * Package:com.leon.springboot.mybatis.service
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 11:01
 * @Version: 1.0
 */
public interface FurnService {

    public Furn queryFurnById(Integer id);
}
======================================================================================
package com.leon.springboot.mybatis.service.impl;

import com.leon.springboot.mybatis.bean.Furn;
import com.leon.springboot.mybatis.dao.FurnMapper;
import com.leon.springboot.mybatis.service.FurnService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * ClassName:FurnServiceImpl
 * Package:com.leon.springboot.mybatis.service.impl
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 11:02
 * @Version: 1.0
 */
@Service
public class FurnServiceImpl implements FurnService {

    @Resource
    private FurnMapper furnMapper;


    @Override
    public Furn queryFurnById(Integer id) {
        return furnMapper.queryFurnById(id);
    }
}

1.6 dao

package com.leon.springboot.mybatis.dao;

import com.leon.springboot.mybatis.bean.Furn;
import org.apache.ibatis.annotations.Mapper;

/**
 * ClassName:FurnMapper
 * Package:com.leon.springboot.mybatis.dao
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 10:33
 * @Version: 1.0
 */
/*
*
* 1.在Mapper接口使用@Mapper注解,Spring Boot就会将Mapper接口扫描并注入到容器中
* */
@Mapper
public interface FurnMapper {

    public Furn queryFurnById(Integer id);

}

1.7 javaBean

package com.leon.springboot.mybatis.bean;

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

import java.math.BigDecimal;

/**
 * ClassName:Furn
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 16:25
 * @Version: 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Furn {

    private Integer id;
    private String name;
    private String maker;
    private BigDecimal price;
    private Integer sales;
    private Integer stock;
    private String imgPath = "assets/images/product-image/1.jpg";
}

1.8 controller层

package com.leon.springboot.mybatis.controller;

import com.leon.springboot.mybatis.bean.Furn;
import com.leon.springboot.mybatis.service.FurnService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

/**
 * ClassName:FurnController
 * Package:com.leon.springboot.mybatis.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 11:04
 * @Version: 1.0
 */
@Controller
public class FurnController {


    @Resource
    private FurnService furnService;

    @GetMapping("/getFurn")
    @ResponseBody
    public Furn getFurn(@RequestParam("id") int id) {

        return  furnService.queryFurnById(id);

    }

}

1.9 注意事项和细节

  • 解决时间格式以及时间偏移

  • 在时间属性添加一个import com.fasterxml.jackson.annotation包下的注解:@JsonFormat

    //通过@JsonFormat注解来解决时区问题
        //GMT 就是格林尼治标准时间
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
        private Date birth;
    

二十二、Spring Boot整合Mybatis-plus

1. 官方文档

  • 官网:https://baomidou.com/

2. 基本介绍

  • Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发、提高效率而生
  • 强大的CRUD操作:内置通用Mapper、通用Service,通过少量配置即可以实现单表大部分CRUD操作,更有强大的条件构造器,满足各类使用需求

3.代码实现

3.1application.yml配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url:  jdbc:mysql://localhost:3306/spring_boot?useSSL=true&userUnicode=true&characterEncoding=UTF-8
#配置MybatisPlus
mybatis-plus:
  #配置输出日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2 javaBean类

package com.leon.springboot.mybatisplus.bean;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * ClassName:Furn
 * Package:com.leon.springboot.bean
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/27 16:25
 * @Version: 1.0
 */
/*
*  1.如果javaBean的类名和对应的数据库表名一致,则可以映射,@TableName注解可以省略
*  2.如果javaBean的类名和对应的数据库表名不一致,则不能映射,需要通过@TableName注解来指定
* */
@TableName(value = "furn")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Furn {

    private Integer id;
    private String name;
    private String maker;
    ////通过@JsonFormat注解来解决时区问题
    ////GMT 就是格林尼治标准时间
    //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    //private Date birth;
    private BigDecimal price;
    private Integer sales;
    private Integer stock;
    private String imgPath = "assets/images/product-image/1.jpg";
}

3.3 DruidDataSourceConfig.java

package com.leon.springboot.mybatisplus.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;

/**
 * ClassName:DruidDataSourceConfig
 * Package:com.leon.springboot.mybatisplus.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 15:26
 * @Version: 1.0
 */
@Configuration
public class DruidDataSourceConfig {

    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource druidDataSource() {

        return new DruidDataSource();
    }
}

3.4 Controller

package com.leon.springboot.mybatisplus.controller;

import com.leon.springboot.mybatisplus.bean.Furn;
import com.leon.springboot.mybatisplus.service.FurnService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName:FurnController
 * Package:com.leon.springboot.mybatis.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 11:04
 * @Version: 1.0
 */
@Controller
public class FurnController {


    @Resource
    private FurnService furnService;

    @GetMapping("/getFurn")
    @ResponseBody
    public Furn getFurn(@RequestParam("id") int id) {

        return  furnService.getById(id);

    }

    @GetMapping("/all")
    @ResponseBody
    public List<Furn> getAllFurn() {
        return furnService.list();
    }

}

3.5 Mapper

package com.leon.springboot.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leon.springboot.mybatisplus.bean.Furn;
import org.apache.ibatis.annotations.Mapper;

/**
 * ClassName:FrunMapper
 * Package:com.leon.springboot.mybatisplus.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 15:29
 * @Version: 1.0
 */
/*
* 1.BaseMapper已经默认提供了很多的crud方法,可以直接使用
* 2.如果BaseMapper提供的方法不能满足业务需求,可以在添加新的方法,并在FurnMapper.xml进行配置
* */
//@Mapper
public interface FurnMapper extends BaseMapper<Furn> {
    //自定义方法
}

3.6 Service

package com.leon.springboot.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.leon.springboot.mybatisplus.bean.Furn;

/**
 * ClassName:FurnService
 * Package:com.leon.springboot.mybatisplus.service
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 15:36
 * @Version: 1.0
 */
/*
* 1.传统方式在接口中定义方法或声明方法,然后在实现类中进行实现
* 2.在mybatis-plus中,只需要继承父接口IService,这个接口声明了很多crud的方法
* 3.如果提供的方法不能满足,则可以增加方法,然后在实现类中实现即可
* */
public interface FurnService extends IService<Furn> {
    //自定义方法
}
======================================================================================

package com.leon.springboot.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leon.springboot.mybatisplus.bean.Furn;
import com.leon.springboot.mybatisplus.mapper.FurnMapper;
import com.leon.springboot.mybatisplus.service.FurnService;
import org.springframework.stereotype.Service;

/**
 * ClassName:FurnServiceImpl
 * Package:com.leon.springboot.mybatisplus.service.impl
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/8/28 15:40
 * @Version: 1.0
 */
/*
* 1.传统方式:在实现类中直接进行实现 FurnService接口
* 2.在mybatis-plus中,开发Service实现类,需要继承ServiceImpl
* 3.ServiceImpl实现了IService接口,FurnService接口它继承了IService接口,因此这里可以认为
*   FurnServiceImpl实现了FurnService接口,这样FurnServiceImpl就可以使用IService的接口方法,也可以理解成可以
*   使用FurnService的方法
* 4.如果FurnService接口中声明了其他方法或自定义了方法,依然需要再FurnServiceImpl中实现
*
* */
@Service
public class FurnServiceImpl extends ServiceImpl<FurnMapper, Furn> implements FurnService  {
}

3.7 pom.xml

<?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.leon</groupId>
    <artifactId>springboot-mybatis-plus</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <!--导入Spring Boot 父工程-规定写法-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/>
    </parent>


    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--导入web项目场景启动器:会自动导入和web开发相关的依赖[库/jar]-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.3</version>
        </dependency>
        <!--引入Mysql启动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--引入配置处理器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--引入lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--引入Test starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--引入Druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>
        <!--引入Mybatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
    </dependencies>

</project>

4. 注意事项和细节

  • 如果javaBean的类名和对应的数据库表名一致,则可以映射,@TableName注解可以省略
  • 如果javaBean的类名和对应的数据库表名不一致,则不能映射,需要通过@TableName注解来指定

Comment