SpringBoot一条龙

1、SpringBoot简介

1.1、回顾什么是Spring

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

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

1.2、Spring是如何简化Java开发的

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

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

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

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

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

1.3、什么是SpringBoot

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

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

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

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

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

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

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

1.4、微服务

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)

  • 微服务是一种架构风格
  • 一个应用拆分为一组小型服务
  • 每个服务运行在自己的进程内,也就是可独立部署和升级
  • 服务之间使用轻量级HTTP交互
  • 服务围绕业务功能拆分
  • 可以由全自动部署机制独立部署
  • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

1.5、分布式

1.5.1、分布式的困难

  • 远程调用
  • 服务发现
  • 负载均衡
  • 服务容错
  • 配置管理
  • 服务监控
  • 链路追踪
  • 日志管理
  • 任务调度

在这里插入图片描述

1.6、云原生

原生应用如何上云。 Cloud Native

1.6.1、上云的困难

  • 服务自愈
  • 弹性伸缩
  • 服务隔离
  • 自动化部署
  • 灰度发布
  • 流量治理

1.6.2、上云的解决在这里插入图片描述

2、HelloWorld

2.1、搭建环境

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

我的环境准备:

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

开发工具:

  • IDEA

2.2、创建基础项目说明

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

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

2.2.1、项目创建方式一:

使用Spring Initializr 的 Web页面创建项目

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

2、填写项目信息

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

4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。

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

2.2.1、项目创建方式二:

使用 IDEA 直接创建项目

1、创建一个新项目

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

3、填写项目信息

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

5、填写项目路径

6、等待项目构建成功

项目结构分析:

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

1、程序的主启动类

2、一个 application.properties 配置文件

3、一个 测试类

4、一个 pom.xml

2.3、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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 父依赖   -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xiaoLong</groupId>
    <artifactId>helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>helloworld</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- web场景启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- springboot单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.4、编写一个http接口

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

2、在包中新建一个HelloController类


@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello World";
    }
    
}

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

图片

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

2.5、将项目打成jar包

点击 maven的 package

图片

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


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

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

图片

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

2.6、更改启动时显示的字符

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

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

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

图片

3、运行原理初探

3.1、pom.xml

3.1.1、父依赖

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

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.2.5.RELEASE</version>    <relativePath/> <!-- lookup parent from repository --></parent>

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

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-dependencies</artifactId>    <version>2.2.5.RELEASE</version>    <relativePath>../../spring-boot-dependencies</relativePath></parent>

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

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

3.2、启动器 spring-boot-starter


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

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

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

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

3.3、主启动类

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

3.3.1、默认的主启动类


//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {

   public static void main(String[] args) {
     //以为是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

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

  • 自动配好SpringMVC

    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景
  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)
      • 或者@ComponentScan 指定扫描路径

3.3.2、@SpringBootApplication

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

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


@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}
3.3.2.1、@ComponentScan

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

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

3.3.2.2、@SpringBootConfiguration

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

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
//----------------------------------------
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

  • @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
  • @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用
3.3.2.3、@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

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

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

@Import({Registrar.class})public @interface AutoConfigurationPackage {}

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

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

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

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

image-20220306205227055

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

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

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

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

    String factoryTypeName = factoryType.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

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

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();

        try {
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }

            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

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

3.2.2.4、spring.factories

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

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

image-20220306223628904

WebMvcAutoConfiguration

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

图片

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

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

3.3.3、总结

总览

springboot自动配置原理-思维笔记|ProcessOn

image-20220306234832233

结论:

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

3.4、SpringApplication.run

3.4.1、不简单的方法

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

@SpringBootApplicationpublic class SpringbootApplication {    public static void main(String[] args) {        SpringApplication.run(SpringbootApplication.class, args);    }}

SpringApplication.run分析

3.4.2、SpringApplication做的事情

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

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

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

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

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

查看构造器:


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

3.4.3、run方法流程分析

图片

3.5、组件添加

3.5.1、@Configuration

  • 基本使用

  • Full模式与Lite模式

    • 示例
    • 最佳实战
      • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
      • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 *
 *
 *
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

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

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


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

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

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

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

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

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

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


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

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


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

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



    }
}

3.5.2、@ComponentScan、@Import

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

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

3.6、原生配置文件引入

3.6.1、@ImportResource

======================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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

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

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

@ImportResource(“classpath:beans.xml”)导入资源

@ImportResource("classpath:beans.xml")
public class MyConfig {}

======================测试=================
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true

3.7、配置绑定

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

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

3.7.1、@ConfigurationProperties

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

    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

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

    public Integer getPrice() {
        return price;
    }

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

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

3.7.2、@EnableConfigurationProperties + @ConfigurationProperties

image-20220312164412071

3.7.3、@Component + @ConfigurationProperties

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

4、yaml配置注入

4.1、yaml语法学习

4.1.1、配置文件

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

  • application.properties

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

    • 语法结构 :key:空格 value

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

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

server.port=8081

4.1.2、yaml概述

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

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

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

传统xml配置:

<server>    <port>8081<port></server>

yaml配置:

server:  prot: 8080

4.1.3、yaml基础语法

说明:语法要求严格!

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

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

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

k: v

注意:

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

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

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

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

对象、Map(键值对)


#对象、Map格式
k: 
    v1:
    v2:

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


student:
    name: qinjiang
    age: 3

行内写法

student: {name: qinjiang,age: 3}

数组( List、set )

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

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

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


server:
  port: 8082

4.2、注入配置文件

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

4.2.1、配置提示

自定义的类和配置文件绑定一般没有提示。

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


 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

4.2.2、yaml注入配置文件

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

2、编写一个实体类 Dog;

package com.kuang.springboot.pojo;

@Component  //注册bean到容器中
public class Dog {
    private String name;
    private Integer age;
    
    //有参无参构造、get、set方法、toString()方法  
}

3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:

@Component //注册bean
public class Dog {
    @Value("阿黄")
    private String name;
    @Value("18")
    private Integer age;
}

4、在SpringBoot的测试类下注入狗狗输出一下;


@SpringBootTest
class DemoApplicationTests {

    @Autowired //将狗狗自动注入进来
    Dog dog;

    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗对象
    }

}

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

image-20220308233409116

5、我们在编写一个复杂一点的实体类:Person 类


@Component //注册bean到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    
    //有参无参构造、get、set方法、toString()方法  
}

6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!


person:
  name: qinjiang
  age: 3
  happy: false
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - code
   - girl
   - music
  dog:
    name: 旺财
    age: 1

7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!


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

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

图片

图片


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

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


@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person; //将person自动注入进来

    @Test
    public void contextLoads() {
        System.out.println(person); //打印person信息
    }

}

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

image-20220308233849380

4.2.3、加载指定的配置文件

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

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

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


name=kuangshen

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


@PropertySource(value = "classpath:person.properties",encoding = "utf-8")
@Component //注册bean
public class Person {

    @Value("${name}")
    private String name;

    ......  
}

4.2.4、配置文件占位符

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


person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺财
      age: 1

4.2.5、回顾properties配置

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

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

settings–>FileEncodings 中配置;

图片

1、新建一个实体类User

@Component //注册bean
public class User {
    private String name;
    private int age;
    private String sex;
}

2、编辑配置文件 user.properties

user.name=kuangshen
user.age=18
user.sex=

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


@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
    //直接使用@value
    @Value("${user.name}") //从配置文件中取值
    private String name;
    @Value("#{9*2}")  // #{SPEL} Spring表达式
    private int age;
    @Value("男")  // 字面量
    private String sex;
}

4、Springboot测试


@SpringBootTest
class DemoApplicationTests {

    @Autowired
    User user;

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

}

结果正常输出:

图片

4.2.6、对比小结

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

图片

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

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

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

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

结论:

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

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

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

5、JSR303数据校验及多环境切换

5.1、JSR303数据校验

5.1.1、先看看如何使用

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


@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

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

image-20220309204034347

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

5.1.1、常见参数


@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 DateCalendar 对象是否在当前时间之前  
@Future     验证 DateCalendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

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

5.2、多环境切换

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

5.2.1、多配置文件

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

例如:

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

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

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

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


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

5.2.2、yaml的多文档块

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


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

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


---

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

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

5.2.3、配置文件加载位置

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

图片

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


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

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

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

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

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

5.2.4、运维小技巧

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

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


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

6、自动配置原理

6.1、自动配置原理探索

springboot自动配置原理 ProcessOn Mind脑图

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

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

图片

6.1.1、自动配置注解

6.1.1.1、@SpringBootConfiguration

@Configuration。代表当前是一个配置类

6.1.1.2、@ComponentScan

指定扫描哪些,Spring注解;

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

自动配置包?指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

2、@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
	默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
    

6.2、分析自动配置原理

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


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

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

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

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

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


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

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

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)

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

image-20220311154115575

这就是自动装配的原理!

6.3、了解:@Conditional

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

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

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

图片

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

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

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

#开启springboot的调试类
debug=true

image-20220311155207858

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

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

Unconditional classes: (没有条件的类)

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

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

6.4、按需开启自动配置项

文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。

6.5、修改默认配置

    @Bean
	@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
	@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
	public MultipartResolver multipartResolver(MultipartResolver resolver) {
        //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
        //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
		// Detect if the user has created a MultipartResolver but named it incorrectly
		return resolver;
	}

给容器中加入了文件上传解析器;

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先@ConditionalOnMissingBean

@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
    }

6.6、最佳实践

  • 引入场景依赖

    • https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
  • 查看自动配置了哪些(选做)

    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
  • 是否需要修改

    • 参照文档修改配置项
      • https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
      • 自己分析。xxxxProperties绑定了配置文件的哪些。
    • 自定义加入或者替换组件
      • @Bean、@Component。。。
    • 自定义器 XXXXXCustomizer

6.7、总结

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties

7、自定义starter

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

命名归约:

官方命名:

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

自定义命名:

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

7.1、编写启动器

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

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

图片

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

图片

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

image-20220311224455738

5、在我们的 Long-spring-boot-starter 中 导入 Long-spring-boot-starter-autoconfiguration的依赖!

<?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>org.example</groupId>
    <artifactId>Long-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- 启动器 -->
    <dependencies>
        <!--  引入自动配置模块 -->
        <dependency>
            <groupId>com.Long</groupId>
            <artifactId>Long-spring-boot-starter-autoconfiguration</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

6、将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是所有的启动器基本配置!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.Long</groupId>
    <artifactId>Long-spring-boot-starter-autoconfiguration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Long-spring-boot-starter-autoconfiguration</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>

7、在service层我们编写一个自己的服务

public class HelloService {

    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello() {
        return helloProperties.getName() + helloProperties.getSex() + helloProperties.getPwd() + helloProperties.getUsername();
    }

}

8、在pojo层编写HelloProperties 配置类

@ConfigurationProperties(prefix = "long")
public class HelloProperties {

    private String name;
    private Integer sex;
    private String pwd;
    private String username;

    public String getName() {
        return name;
    }

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

    public Integer getSex() {
        return sex;
    }

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

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}

9、在configuration层编写我们的自动配置类并注入bean

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService() {
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}

10、在resources编写一个自己的 META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.Long.configuration.HelloServiceAutoConfiguration

11、测试

启动启动类测试是否成功

@SpringBootApplication
public class LongSpringBootStarterAutoconfigurationApplication {

    public static void main(String[] args) {
        SpringApplication.run(LongSpringBootStarterAutoconfigurationApplication.class, args);
    }

}

12、编写完成后,可以安装到maven仓库中!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgQU9eJA-1649560184769)(https://gitee.com/wxl2ld/cloudimages/raw/master/img/image-20220311225354414.png)]

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

1、新建一个SpringBoot module项目

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

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

    <dependency>
        <groupId>org.example</groupId>
        <artifactId>Long-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

3、编写一个 HelloController 进行测试我们自己的写的接口!

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello();
    }

}

4、编写配置文件 application.properties

long.name= "wxl"
long.sex= 1
long.pwd= "12121"
long.username= "aabab"
#    private String name;
#    private Integer sex;
#    private String pwd;
#    private String username;

5、启动项目进行测试,结果成功 !

image-20220311232339360

8、web开发

8.1、SpringMVC自动配置概览

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).

    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.

    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document).

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

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

不用@EnableWebMvc注解。使用 **@Configuration** + **WebMvcConfigurer** 自定义规则

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

声明 **WebMvcRegistrations** 改变默认底层组件

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

使用 `**@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC*

8.2、功能分析

8.2.3、静态资源访问

1、静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源路径

spring:
  mvc:
    static-path-pattern: /res/**

  resources:
    static-locations: [classpath:/haha/]
2、静态资源访问前缀

默认无前缀

spring:
  mvc:
    static-path-pattern: /res/**

3、webjar

自动映射 /webjars/**

https://www.webjars.org/


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

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

8.3、index页支持

  • 静态资源路径下 index.html

    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效

  resources:
    static-locations: [classpath:/haha/]
  • controller能处理/index

8.4、自定义 Favicon(小图标)

favicon.ico 放在静态资源目录下即可。

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

8.5、静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么。
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources

8.5.1、配置类只有一个有参构造器

	//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}

8.5.2、资源处理的默认规则

@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
			//webjars的规则
            if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
            
            //
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
		}
spring:
#  mvc:
#    static-path-pattern: /res/**

  resources:
    add-mappings: false   禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

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

	/**
	 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
	 * /resources/, /static/, /public/].
	 */
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

8.5.3、欢迎页的处理规则

	HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。	

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

	WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
			ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
		if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            //要用欢迎页功能,必须是/**
			logger.info("Adding welcome page: " + welcomePage.get());
			setRootViewName("forward:index.html");
		}
		else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
            // 调用Controller  /index
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

8.5.4、favicon

由于favicon.ico图标是由浏览器自动发送请求/favicon.ico获取并保存在session域中的。因此,如果我们在配置文件中设置了静态资源访问前缀,那么浏览器发送的/favicon.ico由于不符合访问前缀要求,就会获取不到相对应的图标了(图标也是静态资源的一种)。

8.6、请求参数处理

8.6.1、请求映射

1、rest使用与原理
  • @xxxMapping;

  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作

    • 以前:**/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    • 现在: /user *GET-*获取用户 *DELETE-*删除用户 *PUT-*修改用户 *POST-*保存用户
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put
      • SpringBoot中手动开启
    • 扩展:如何把_method 这个名字换成我们自己喜欢的。
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }


//================WebMvcAutoConfiguration=================
	@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}


//自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上**_method=PUT**

  • 请求过来被HiddenHttpMethodFilter拦截

    • 请求是否正常,并且是POST
      • 获取到**_method**的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用****requesWrapper的。

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能
2、请求映射原理

image.png

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

image.png

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

image.png

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

8.6.2、普通参数与基本注解

1、注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

@RequestParam

@GetMapping("/getEmp")
    public Employee getEmp(@RequestParam("id") Integer id) {
        Employee employeeById = employeeService.getEmployeeById(id);
        return employeeById;
    }

image-20220327222649168

@PathVariable

@GetMapping("/getEmp/{id}")
public Employee getEmp2(@PathVariable("id") Integer id) {
    Employee employeeById = employeeService.getEmployeeById(id);
    return employeeById;
}

image-20220327222800973

总览

@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){


        Map<String,Object> map = new HashMap<>();
        
        /*
        @PathVariable:路径变量
        @RequestHeader:获取请求头
        @RequestParam:获取请求参数
        @CookieValue:获取cookie的值
        @RequestBody:获取请求体[POST]

        */

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }


    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

    // /boss/1;age=20/2;age=10

    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }

}
2、Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}
3、复杂参数:

Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map

mavContainer.getModel(); 获取到值的

image.png

image.png

image.png

4、自定义对象参数:

可以自动类型转换与格式化,可以级联封装。

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}

result

8.6.3、POJO封装过程

  • ServletModelAttributeMethodProcessor

8.6.4、参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值
1、HandlerAdapter

image.png

0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx

2、执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法


//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

3、参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

image.png

image.png

当前解析器是否支持解析这种参数

●支持就调用 resolveArgument

4、返回值处理器

image.png

5、如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}
5.1、挨个判断所有参数解析器那个支持解析这个参数
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
5.2、解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
5.3、自定义类型参数 封装POJO

ServletModelAttributeMethodProcessor 这个参数处理器支持
是否为简单类型。isSimpleValueType()判断是否为简单类型

public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) ||
				Temporal.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}
@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)

byte – > file

简而言之:就是创建空的pojo对象然后再将页面的值封装进去

Converter的总接口

@FunctionalInterfacepublic interface Converter<S, T>

image.png

image.png

未来我们可以给WebDataBinder里面放自己的Converter;

private static final class StringToNumber<T extends Number> implements Converter<String, T>

自定义 Converter

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
6、目标方法执行完成

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据

image.png

7、处理派发结果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

InternalResourceView@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, request);

		// Expose helpers as request attributes, if any.
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}
暴露模型作为请求域属性
// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

    //model中的所有数据遍历挨个放在请求域中
		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

8.7、数据响应与内容协商

8.7.1、响应JSON

1、jackson.jar+@ResponseBody
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景
   		 <dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-json</artifactId>
     		 <version>2.3.4.RELEASE</version>
     		 <scope>compile</scope>
   		 </dependency>

image.png

给前端自动返回json数据;

1.1、返回值解析器

img

try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}

//返回值处理器

------------------------ HandlerMethodReturnValueHandlerComposite.java-----------------------------

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

//返回值处理器

决定returnValueHandlers使用那个类

-------RequestResponseBodyMethodProcessor.java-----

    
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
        // 使用消息转换器进行写出操作
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}
1.2、返回值解析器原理

image.png

总结步骤:

  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType

  • 2、返回值处理器调用 handleReturnValue 进行处理

  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

    • \1. 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去

image-20220316161546571

2、SpringMVC到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask@ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor
3、HTTPMessageConverter原理
3.1、MessageConverter规范

image.png

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON。或者 JSON转为Person

3.2、默认的MessageConverter

image.png

0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true 
8 - true
9 - 支持注解方式xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

image.png

8.7.2、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

1、引入xml依赖
 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2、postman分别测试返回json和xml

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

image-20220316213236655

3、开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml

image.png

确定客户端接收什么样的内容类型;

1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

image.png

2、最终进行内容协商返回给客户端json即可。

4、内容协商原理
  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType

  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】

    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • img
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
    • img
  • 3、在AbstractMessageConverterMethodProcessor中遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

  • 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

  • 5、客户端需要【application/xml】。服务端能力【10种、json、xml】

  • img

  • 6、进行内容协商的最佳匹配媒体类型

  • List<MediaType> mediaTypesToUse = new ArrayList<>();
    			for (MediaType requestedType : acceptableTypes) {
    				for (MediaType producibleType : producibleTypes) {
    					if (requestedType.isCompatibleWith(producibleType)) {
    						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
    					}
    				}
    			}
    
  • 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。(两次:1次、解决看谁支持处理Person

    ​ 2次、看能解决转换为最佳匹配媒体类型)

image.png

导入了jackson处理xml的包,xml的converter就会自动进来

---WebMvcConfigurationSupport----

jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

if (jackson2XmlPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
5、自定义 MessageConverter

实现多协议数据兼容。json、xml、x-guigu

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

1、Processor 处理方法返回值。通过 MessageConverter 处理

2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

3、内容协商找到最终的 messageConverter

SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer

 @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new BeastMessageConverter());
            }
        }
    }
package com.obeast.boot.converter;

import com.obeast.boot.pojo.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
     * 功能描述:
     * 1.浏览器发送请求直接返回xml [application/xml] jacksonXmlConverter
     * 2.如果是ajax请求 返回 json [application/json] jacksonJsonConverter
     * 3.如果是app发请求, 返回自定义协议数据  [application/beast]   xxxConverter
     *
     * step:
     * 1、系统自定义的MessageConverter进入系统底层
     * 2、系统底层就会统计出所有MessageConverter能操作那些类型
     * 3、客户端内容协商[xxx -> xxx]
**/
public class BeastMessageConverter implements HttpMessageConverter<Person> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }
    /**
     * 功能描述:服务器要统计所有MessageConverter都能干什么
     * @Description: 
     * @Date: 2022/3/16 23:35 
     * @Param:
     * @return: 
     **/
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        //自定义类型解析成MessageConverter
        return MediaType.parseMediaTypes("application/beast");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议的写出
        String data = person.getAge() + ";" + person.getDate() + ";" + person.getUsername();

        //写数据
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

image.png

6、自定义format
/**
             * 功能描述:自定义内容协商策略
             * @Description:
             * @Date: 2022/3/17 14:38
             * @Param:
             * @return: org.springframework.web.servlet.config.annotation.WebMvcConfigurer
             **/
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //(Map<String, MediaType > mediaTypes)
                Map<String, MediaType > mediaTypes = new HashMap<>();
                //定义媒体类型
                mediaTypes.put("json", MediaType.APPLICATION_JSON);
                mediaTypes.put("xml", MediaType.APPLICATION_XML);
                mediaTypes.put("beast", MediaType.parseMediaType("application/beast"));


                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
                configurer.strategies(Arrays.asList(parameterStrategy));
            }

image-20220317144252329

因为自定义configureContentNegotiation

所以请求头不能使用

解决

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //(Map<String, MediaType > mediaTypes)
                Map<String, MediaType > mediaTypes = new HashMap<>();
                //定义媒体类型
                mediaTypes.put("json", MediaType.APPLICATION_JSON);
                mediaTypes.put("xml", MediaType.APPLICATION_XML);
                mediaTypes.put("beast", MediaType.parseMediaType("application/beast"));

                HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
                configurer.strategies(Arrays.asList(parameterStrategy, headerStrategy));
            }
总结

有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

8.8、视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。

8.8.1、视图解析

image-20220317145634705

1、视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。

**4、**processDispatchResult 处理派发结果(页面改如何响应)

  • 1、render(mv, request, response); 进行页面渲染逻辑

    • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
      • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
        • RedirectView 如何渲染【重定向到一个页面】
        • 1、获取目标url地址
        • **2、**response.sendRedirect(encodedURL);

视图解析:

    • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发****request.getRequestDispatcher(path).forward(request, response);
    • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
    • 返回值是普通字符串: new ThymeleafView()—>

自定义视图解析器+自定义视图

8.8.2、模板引擎-Thymeleaf

1、thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

2、基本语法
1、表达式
表达式名字语法用途
变量取值${…}获取请求域、session域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段
2、字面量

文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false

空值: null

变量: one,two,… 变量不能有空格

3、文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4、数学运算

运算符: + , - , * , / , %

5、布尔运算

运算符: and , or

一元运算: ! , not

6、比较运算

比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

7、条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

8、特殊操作

无操作: _

3、设置属性值-th:attr

设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

4、迭代
<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
5、条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
6、属性优先级

image.png

8.9、thymeleaf使用

8.9.1、引入Starter

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

8.9.2、自动配置好了thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面
	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

8.9.3、页面开发

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

8.9.4、构建后台管理系统

1、项目创建

thymeleaf、web-starter、devtools、lombok

2、静态资源处理

自动配置好,我们只需要把所有静态资源放到 static 文件夹下

3、路径构建

th:action=“@{/login}”

4、模板抽取

th:insert/replace/include

5、页面跳转
    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){

        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }

    }
6、数据渲染
    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }
        <table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}">
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
        </tbody>
        </table>

8.10、拦截器

编写步骤 :

  • 1、编写一个拦截器实现HandlerInterceptor接口
  • 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
  • 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】

8.10.1、HandlerInterceptor 接口

/**
 * 登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2、把这些配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
//        re.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

8.10.2、配置拦截器

方法1:设置拦截路径
方法2:去application.yaml设置#spring:mvc:static-path-pattern:/static/**,并修改每一个static目录下引用 
/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

8.10.3、拦截器原理

1、根据当前请求,找到**HandlerExecutionChain【**可以处理请求的handler以及handler的所有 拦截器】

image.png

2、先来顺序执行 所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

image.png

8.11、文件上传

8.11.1、页面表单

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
    

<div class="form-group">
   <label for="exampleInputFile">生活照</label>
   <input type="file" name="photos" multiple>
</div>
1、标注文件上传表单:
<form method="post" action="/upload" enctype="multipart/form-data">
2、多文件上传:

8.11.2、文件上传代码

设置文件上传的大小

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
    /**
     * MultipartFile 自动封装上传过来的文件
     * @param email
     * @param username
     * @param headerImg
     * @param photos
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {

        log.info("上传的信息:email={},username={},headerImg={},photos={}",
                email,username,headerImg.getSize(),photos.length);

        if(!headerImg.isEmpty()){
            //保存到文件服务器,OSS服务器
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
        }

        if(photos.length > 0){
            for (MultipartFile photo : photos) {
                if(!photo.isEmpty()){
                    String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("H:\\cache\\"+originalFilename));
                }
            }
        }


        return "main";
    }

8.11.3、自动配置原理

**文件上传自动配置类-MultipartAutoConfiguration-**MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】

  • 原理步骤

    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • **3、将request中文件信息封装为一个Map;**MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos)

image.png

8.12、异常处理

8.12.1、错误处理

1、默认规则
  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

image.png

image.png

  • 要对其进行自定义,添加**View**解析为**error**``** **
  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析

image.png

2、定制错误处理逻辑
  • 自定义错误页

    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
    2.1、@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
    2.2、@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
    2.3、Spring底层的异常:如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。**
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
    • img

image.png

  • ErrorViewResolver 实现自定义处理异常;

    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver
3、异常处理自动配置原理
  • ErrorMvcAutoConfiguration 自动配置异常处理规则

    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver

      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

      • img

      • img

    • **容器中的组件:类型:**BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
      -----------------BeanNameViewResolver-------------------
      private ModelAndView resolve(String viewName, Map<String, Object> model) {
      		String errorViewName = "error/" + viewName;
      		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
      				this.applicationContext);
      		if (provider != null) {
      			return new ModelAndView(errorViewName, model);
      		}
      		return resolveResource(errorViewName, model);
      	}
      
    • **容器中的组件:**类型:**DefaultErrorViewResolver -> id:**conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

private static class StaticView implements View {

		private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

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

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
				throws Exception {
			if (response.isCommitted()) {
				String message = getMessage(model);
				logger.error(message);
				return;
			}
			response.setContentType(TEXT_HTML_UTF8.toString());
			StringBuilder builder = new StringBuilder();
			Object timestamp = model.get("timestamp");
			Object message = model.get("message");
			Object trace = model.get("trace");
			if (response.getContentType() == null) {
				response.setContentType(getContentType());
			}
			builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
					"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
					.append("<div id='created'>").append(timestamp).append("</div>")
					.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
					.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
			if (message != null) {
				builder.append("<div>").append(htmlEscape(message)).append("</div>");
			}
			if (trace != null) {
				builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
			}
			builder.append("</body></html>");
			response.getWriter().append(builder.toString());
		}
    
    .......

写出去json

img

错误页

image.png

4、异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染?)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器

  • img

  • 2、系统默认的 异常解析器;

  • img

    • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    • image-20220321211955990
    • 2、默认没有任何人能处理异常,所以异常会被抛出
      • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
      • 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
      • img
      • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
      • 4、模板引擎最终响应这个页面 error/500.html

8.13、Web原生组件注入(Servlet、Filter、Listener)

1、使用Servlet API

指定原生Servlet组件都放在那里

@ServletComponentScan(basePackageClasses = MyServlet.class)//扫描原生servlet方法1
//@ServletComponentScan(basePackages = "com.beast.boot")//扫描原生servlet方法2

@WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring的拦截器?

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.getWriter().write("6666");
    }
}

@WebFilter(urlPatterns={“/css/*”,“/images/*”})

@Slf4j
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyFilter工作");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");

    }

    @Override
    public void destroy() {
        log.info("MyFilter销毁");

    }
}

@WebListener

@Slf4j
@WebListener
public class MyServletListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {

        log.info("MyServletListener监听项目启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyServletListener监听项目销毁");


    }
}

推荐可以这种方式;

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
  • 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。
  • 默认映射的是 / 路径。

image.png

Tomcat-Servlet;

多个Servlet都能处理到同一层路径,精确优选原则

A: /my/

B: /my/1

2、使用RegistrationBean

记得注释掉Servlet的API注解

ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean

@Configuration
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

8.14、嵌入式Servlet容器

1、切换嵌入式Servlet容器

  • 默认支持的webServer

    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

image.png

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

内嵌的tomcat

-------------------TomcatServletWebServerFactory-------------------------

public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }

        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Iterator var4 = this.serverLifecycleListeners.iterator();

        while(var4.hasNext()) {
            LifecycleListener listener = (LifecycleListener)var4.next();
            tomcat.getServer().addLifecycleListener(listener);
        }

        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 var8 = this.additionalTomcatConnectors.iterator();

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

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }
  • 原理

    • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
    • ServletWebServerApplicationContext 启动的时候寻找 **ServletWebServerFactory**``(Servlet 的web服务器工厂---> Servlet 的web服务器)
    • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
    • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
    • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
    • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

2、定制Servlet容器

  • 实现 WebServerFactoryCustomizer

  • public class ServletWebServerFactoryAutoConfiguration {
    
    	@Bean
    	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
    			ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
    			ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
    		return new ServletWebServerFactoryCustomizer(serverProperties,
    				webListenerRegistrars.orderedStream().collect(Collectors.toList()),
    				cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
    	}
    
    • 把配置文件的值和**ServletWebServerFactory 进行绑定**
  • 修改配置文件 server.xxx

  • 直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

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

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

8.15、定制化原理

1、定制化的常见方式

  • 修改配置文件;
  • xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
        @Bean
    public WebMvcRegistrations webMvcRegistrations() {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return WebMvcRegistrations.super.getRequestMappingHandlerMapping();
            }
        };
    }
}
  • @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能

    @EnableWebMvc
    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer {
    
        /**
         * 功能描述:定义静态资源行为
         * @Description:
         * @Date: 2022/3/23 16:42
         * @Param:
         * @return:
         **/
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
            /**
             * 功能描述:访问  /aa/** 所以请求都去 classpath:/static/ 下面进行匹配
             * @Description:
             * @Date: 2022/3/23 16:40
             * @Param:
             * @return: void
             **/
            registry.addResourceHandler("/aa/**")
                    .addResourceLocations("classpath:/static/");
        }
    }
    
    • 原理
    • 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
    • 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
    • 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
      • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
      • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
  • … …

2、原理分析套路

7、自定义starter

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项

9、Docker

9.1、简介

定义

Docker是一个开源的应用容器引擎;是一个轻量级容器技术;

Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;

image-20220322195546825

运行中的这个镜像称为容器,容器启动是非常快速的。

image-20220322134720628

9.2、核心概念

**docker主机(Host):**安装了Docker程序的机器(Docker直接安装在操作系统之上);

**docker客户端(Client):**客户端通过命令行或者其他工具使用 Docker

API(https://docs.docker.com/reference/api/docker_remote_api)

**docker仓库(Registry):**用来保存各种打包好的软件镜像;

Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。

**docker镜像(Images):**软件打包好的镜像;放在docker仓库中;

**docker容器(Container):**镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用

image-20220322195659507

使用Docker的步骤:

1)、安装Docker

2)、去Docker仓库找到这个软件对应的镜像;

3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;

4)、对容器的启动停止就是对软件的启动停止;

9.2、安装Docker

1、安装linux虚拟机

1)、VMWare、VirtualBox(安装);

2)、导入虚拟机文件centos7-atguigu.ova;

3)、双击启动linux虚拟机;密码123456登陆

4)、使用客户端连接linux服务器进行命令操作;

5)、设置虚拟机网络; 桥接网络=选好网卡==接入网线;

6)、设置好网络以后使用命令重启虚拟机的网络

service network restart

7)、查看linux的ip地址

ip addr

2、在linux虚拟机上安装docker

步骤:

1、检查内核版本,必须是3.10及以上
uname ‐r

2、安装docker
yum install docker

3、输入y确认安装

4、启动docker
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker ‐v
Docker version 1.12.6, build 3e8e77d/1.12.6

5、开机启动docker
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi‐user.target.wants/docker.service to
/usr/lib/systemd/system/docker.service.

6、停止docker
systemctl stop docker

3、Docker常用命令&操作

1)、镜像操作
操作命令说明
检索docker search 关键字 eg:docker search redis我们经常去docker hub上检索镜像的详细信息,如镜 像的TAG。
拉取docker pull 镜像名:tag:tag是可选的,tag表示标签,多为软件的版本,默认 是latest
列表docker images查看所有本地镜像
删除docker rmi image-id 删除相同的image-id 则docker rmi -f image-id删除指定的本地镜像

https://hub.docker.com/

2)、容器操作

软件镜像(QQ安装程序)----运行镜像----产生一个容器(正在运行的软件,运行的QQ); 步骤:

1、搜索镜像
[root@localhost ~]# docker search tomcat

2、拉取镜像
[root@localhost ~]# docker pull tomcat

3、根据镜像启动容器
docker run ‐‐name mytomcat ‐d tomcat:latest

4、docker ps
查看运行中的容器

5、 停止运行中的容器
docker stop 容器的id

6、查看所有的容器
docker ps ‐a

7、启动容器
docker start 容器id

8、删除一个容器
docker rm 容器id

9、启动一个做了端口映射的tomcat
[root@localhost ~]# docker run ‐d ‐p 8888:8080 tomcat
‐d:后台运行
‐p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口

10、为了演示简单关闭了linux的防火墙
service firewalld status ;查看防火墙状态
service firewalld stop:关闭防火墙

11、查看容器的日志
docker logs container‐name/container‐id

12、进入容器
docker exec -it 容器ID/名称 /bin/bash

13、进入redis控制台
docker exec -it 容器ID/名称 redis-cli

更多命令参看
https://docs.docker.com/engine/reference/commandline/docker/
可以参考每一个镜像的文档

3)、安装MySQL示例
docker pull mysql
a.错误的启动
[root@localhost ~]# docker run --name mysql01 -d mysql
Unable to find image 'mysql:latest' locally
Trying to pull repository docker.io/library/mysql ... 
latest: Pulling from docker.io/library/mysql
Digest: sha256:b2ae0f527005d99bacdf3a220958ed171e1eb0676377174f0323e0a10912408a
Status: Downloaded newer image for docker.io/mysql:latest
30797ffc93e7d4756d0875fcab443515e4c0f61275883558a68cb1917b9bf93c
[root@localhost ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
30797ffc93e7        mysql               "docker-entrypoint..."   31 seconds ago      Exited (1) 30 seconds ago                       mysql01


[root@localhost ~]# docker logs 30797ffc93e7
2022-03-22 14:31:19+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.
2022-03-22 14:31:19+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
    You need to specify one of the following:
    - MYSQL_ROOT_PASSWORD
    - MYSQL_ALLOW_EMPTY_PASSWORD
    - MYSQL_RANDOM_ROOT_PASSWORD

b.正确的启动
[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=mysql1234 -d mysql
13a7c4b6274f2a194beaae2bcb5e0e788a4c0d79d021014496d7b3586a266835
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
13a7c4b6274f        mysql               "docker-entrypoint..."   14 seconds ago      Up 13 seconds       3306/tcp, 33060/tcp   mysql01

c.做了端口映射
[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=mysql1234 -d mysql
12d52f56336175504a519f0224f02e50b860c1a30f021a32b4b8db24747e021a
d.端口被用

3306端口已经开启

[root@localhost ~]# systemctl stop mysqld
[root@localhost ~]# netstat -tanlp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      5657/./redis-server 
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      797/rpcbind         
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1284/sshd           
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      1287/cupsd          
tcp        0     52 192.168.2.136:22        192.168.2.1:60342       ESTABLISHED 15021/sshd: root@pt 
tcp        0      0 127.0.0.1:6379          127.0.0.1:46334         ESTABLISHED 5657/./redis-server 
tcp        0      0 127.0.0.1:46334         127.0.0.1:6379          ESTABLISHED 5675/./redis-cli    
tcp        0      0 192.168.2.136:22        192.168.2.1:60345       ESTABLISHED 15049/sshd: root@no 
tcp6       0      0 :::6379                 :::*                    LISTEN      5657/./redis-server 
tcp6       0      0 :::111                  :::*                    LISTEN      797/rpcbind         
tcp6       0      0 :::22                   :::*                    LISTEN      1284/sshd           
tcp6       0      0 ::1:631                 :::*                    LISTEN      1287/cupsd   
e.EBeaver错误

出现Public Key Retrieval is not allowed

allowPublicKeyRetrieval:允许从服务器上获取公钥

image-20220322231925380

f.几个其他的高级操作
docker run ‐‐name mysql03 ‐v /conf/mysql:/etc/mysql/conf.d ‐e MYSQL_ROOT_PASSWORD=my‐secret‐pw
‐d mysql:tag
把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面
改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)


docker run ‐‐name some‐mysql ‐e MYSQL_ROOT_PASSWORD=my‐secret‐pw ‐d mysql:tag ‐‐character‐set‐
server=utf8mb4 ‐‐collation‐server=utf8mb4_unicode_ci
指定mysql的一些配置参数
g.登录容器
docker exec -it (名字--name) bash
h.超时错误

image-20220323204501821

开发端口

image-20220323204532549

4)、部署redis
docker run -d --name redis -p 6379:6379 redis --requirepass redis1234

注意末尾的--requirepass 123456表示为redis设置密码,并且该参数必须放在最后,否则不生效。

启动容器:

docker exec -it redis redis-cli

出现问题

(error) NOAUTH Authentication required.

输入密码

 auth redis1234

10、数据访问

10.1、SQL

10.1.1、数据源的自动配置-HikariDataSource

1、导入JDBC场景
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        

image.png

数据库驱动?

为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库。

数据库版本和驱动版本对应

想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)

默认版本:<mysql.version>8.0.22</mysql.version>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
<!--            <version>5.1.49</version>-->
        </dependency>

2、重新声明版本(maven的属性的就近优先原则)


    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.49</mysql.version>
    </properties>
2、分析自动配置
1)、自动配置的类
  • DataSourceAutoConfiguration : 数据源的自动配置

    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

    • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
    • @Bean@Primary JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置

  • XADataSourceAutoConfiguration: 分布式事务相关的

3、修改配置项
spring:
	datasource:
    	url: jdbc:mysql://175.178.183.32:3306/test
    	username: root
    	password: mysql1234
    	driver-class-name: com.mysql.cj.jdbc.Driver
#    	#默认
#    	type: com.zaxxer.hikari.HikariDataSource
4、测试
@Slf4j
@SpringBootTest
class BootWebAdminApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {

        Long queryForObject = jdbcTemplate.queryForObject("select count(*) from table_name", Long.class);
        log.info("记录总数:{}",queryForObject);
    }

}

10.1.2、使用Druid数据源

Druid数据源和HikariDataSource数据源选择前者的原因:

数据全套解决方案

1、druid官方github地址

https://github.com/alibaba/druid

整合第三方技术的两种方式

  • 自定义
  • 找starter
2、自定义方式
1)、创建数据源

spinrg的操作

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="poolPreparedStatements" value="true" />
		<property name="maxOpenPreparedStatements" value="20" />

springboot的操作绑定数据源即可

@Configuration
public class MyDataSourceConfig {

//    默认的自动配置是判断容器中没有才会去配 @ConditionalOnMissingBean(DataSource.class)
    @ConfigurationProperties("spring.datasource")//绑定spring的配置属性
    @Bean
    public DataSource dataSource() throws SQLException {

        DruidDataSource druidDataSource = new DruidDataSource();
        //加入监控功能
        druidDataSource.setFilters("stat");

//        druidDataSource.setUrl();
//        druidDataSource.setUsername();
//        druidDataSource.setPassword();
        return druidDataSource;
    }

    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
        return servletRegistrationBean;
    }
}


2)、StatViewServlet

StatViewServlet的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API

spring的操作

	<servlet>
		<servlet-name>DruidStatView</servlet-name>
		<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>DruidStatView</servlet-name>
		<url-pattern>/druid/*</url-pattern>
	</servlet-mapping>

springboot的操作

在数据源设置filters属性即可

//加入监控功能
        druidDataSource.setFilters("stat");
3)、防火墙结合监控

spring

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
      ...
      <property name="filters" value="wall,stat"/>
  </bean>

springboot

//加入监控功能和防火墙
druidDataSource.setFilters("stat,wall");
4)、yaml配置版
datasource:
    url: jdbc:mysql://175.178.183.32:3306/test
    username: root
    password: mysql1234
    driver-class-name: com.mysql.cj.jdbc.Driver
    max-active: 12
    filter: stat,wall
3、StatFilter

用于统计监控信息;如SQL监控、URI监控

需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:

<property name="filters" value="stat,slf4j" />

系统中所有filter:

别名Filter类名
defaultcom.alibaba.druid.filter.stat.StatFilter
statcom.alibaba.druid.filter.stat.StatFilter
mergeStatcom.alibaba.druid.filter.stat.MergeStatFilter
encodingcom.alibaba.druid.filter.encoding.EncodingConvertFilter
log4jcom.alibaba.druid.filter.logging.Log4jFilter
log4j2com.alibaba.druid.filter.logging.Log4j2Filter
slf4jcom.alibaba.druid.filter.logging.Slf4jLogFilter
commonloggingcom.alibaba.druid.filter.logging.CommonsLogFilter
别名Filter类名
defaultcom.alibaba.druid.filter.stat.StatFilter
statcom.alibaba.druid.filter.stat.StatFilter
mergeStatcom.alibaba.druid.filter.stat.MergeStatFilter
encodingcom.alibaba.druid.filter.encoding.EncodingConvertFilter
log4jcom.alibaba.druid.filter.logging.Log4jFilter
log4j2com.alibaba.druid.filter.logging.Log4j2Filter
slf4jcom.alibaba.druid.filter.logging.Slf4jLogFilter
commonloggingcom.alibaba.druid.filter.logging.CommonsLogFilter

慢SQL记录配置

<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
    <property name="slowSqlMillis" value="10000" />
    <property name="logSlowSql" value="true" />
</bean>

使用 slowSqlMillis 定义慢SQL的时长
4、使用官方starter方式
1、引入druid-starter
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
2、分析自动配置
  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
    private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
    private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
    private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
    private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
    private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
    private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
    private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
    private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
3、配置示例
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.atguigu.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

SpringBoot配置示例

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置项列表https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

10.1.3、整合MyBatis操作

https://github.com/mybatis

starter

SpringBoot官方的Starter:spring-boot-starter-*

第三方的: *-spring-boot-starter

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

image.png

1、配置模式
  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来

错误:Field tableMapper in com.beast.boot.service.TableService required a bean of type ‘com.beast.boot.mapper.TableMapper’ that could not be found.

解决:需要在Mapper接口上放@Mapper

错误:Error resolving template [getTable]

解决:在getTable()方法上不能解析(template)模板

错误:发现image-20220324210151118没有封装

解决:因为默认mybatis没有开启驼峰命名规则,通过在mybait-config.xml配置文件mapUnderscoreToCamelCase=true或者 在yaml的

注意两者不能同时存在

configuration:
  map-underscore-to-camel-case: true
@EnableConfigurationProperties(MybatisProperties.class)MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

可以修改配置文件中 mybatis 开始的所有;

# 配置mybatis规则
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置
  
Mapper接口--->绑定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.atguigu.admin.mapper.AccountMapper">
<!--    public Account getAcct(Long id); -->
    <select id="getAcct" resultType="com.atguigu.admin.bean.Account">
        select * from  account_tbl where  id=#{id}
    </select>
</mapper>

配置 private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值

# 配置mybatis规则
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    
 可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可
  • 导入mybatis官方starter
  • 编写mapper接口。标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议;配置在mybatis.configuration
2、注解模式

错误:如果加了@ResponseBody还出现模板解析错误

解决:重启项目

@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")
    public City getById(Long id);

    public void insert(City city);

}

3、混合模式
@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")
    public City getById(Long id);

    public void insert(City city);

}

最佳实战:

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解

10.1.4、整合 MyBatis-Plus 完成CRUD

1、什么是MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装 MybatisX 插件

2、整合MyBatis-Plus
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对****mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • **mapperLocations 自动配置好的。有默认值。*classpath*:/mapper/*/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行

优点:

  • 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力
3、CRUD功能
    @GetMapping("/user/delete/{id}")
    public String deleteUser(@PathVariable("id") Long id,
                             @RequestParam(value = "pn",defaultValue = "1")Integer pn,
                             RedirectAttributes ra){

        userService.removeById(id);

        ra.addAttribute("pn",pn);
        return "redirect:/dynamic_table";
    }


    @GetMapping("/dynamic_table")
    public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
        //表格内容的遍历
//        response.sendError
//     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
//                new User("lisi", "123444"),
//                new User("haha", "aaaaa"),
//                new User("hehe ", "aaddd"));
//        model.addAttribute("users",users);
//
//        if(users.size()>3){
//            throw new UserTooManyException();
//        }
        //从数据库中查出user表中的用户进行展示

        //构造分页参数
        Page<User> page = new Page<>(pn, 2);
        //调用page进行分页
        Page<User> userPage = userService.page(page, null);


//        userPage.getRecords()
//        userPage.getCurrent()
//        userPage.getPages()


        model.addAttribute("users",userPage);

        return "table/dynamic_table";
    }
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {


}

public interface UserService extends IService<User> {

}

10.2、NoSQL

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

10.2.1、Redis自动配置

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

image.png

自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 **StringRedisTemplate、**RedisTemplate就可以操作redis

redis环境搭建

1、docker安装redis镜像

docker pull redis

2、安装容器

docker run -itd --name docker-redis -p 6379:6379 redis --requirepass redis1234

3、开端口!!!!

image-20220325213801378

10.2.2、RedisTemplate与Lettuce

yaml配置

spring:
  redis:
    host: 175.178.183.32
    port: 6379
    password: redis1234
    #选用客户端
    client-type: jedis
    @Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello","world");

        String hello = operations.get("hello");
        System.out.println(hello);
    }

10.2.3、切换至jedis

导入jedis即可

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

<!--        导入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

测试

@Autowired
    RedisConnectionFactory redisConnectionFactory;
    
    @Test
    void testRedis(){
     System.out.println(redisConnectionFactory.getClass());
    }
1)、用redis记录访问次数
  • 定义Interceptor
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {


    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        //默认每次访问当前uri
        redisTemplate.opsForValue().increment(uri);
        return true;
    }
}
  • 在WebMvcConfigurer配置

  • filter、Interceptor 几乎拥有相同的功能,两者的好处是什么?

    1、Filter 是Servlet定义的原生组件。 好处,脱离spring应用也能使用
    2、Interceptor 是spring定义的接口。 可以使用spring的自动配置等功能

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    /**
     * filter、Interceptor 几乎拥有相同的功能,两者的好处是什么?
     * 1、Filter 是Servlet定义的原生组件。 好处,脱离spring应用也能使用
     * 2、Interceptor 是spring定义的接口。 可以使用spring的自动配置等功能
     */
    @Autowired
    RedisUrlCountInterceptor redisUrlCountInterceptor;



    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       
//        不能直接new,要从容器的中拿
//        registry.addInterceptor(new RedisUrlCountInterceptor())
          registry.addInterceptor(redisUrlCountInterceptor)
                  .addPathPatterns("/**")
                  .excludePathPatterns("/",  "/login", "/css/**", "/fonts/**", "/images/**", "/js/**", "/aa/**");
    }
}

11、单元测试(略)

12、指标监控

12.1、SpringBoot Actuator

12.1.1、简介

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

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

image.png

12.1.2、1.x与2.x的不同

image.png

12.1.3、如何使用

  • 引入场景
  • 访问 http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露
  • 测试

http://localhost:8080/actuator/beans

http://localhost:8080/actuator/configprops

http://localhost:8080/actuator/metrics

http://localhost:8080/actuator/metrics/jvm.gc.pause

http://localhost:8080/actuator/endpointName/detailPath
。。。。。。

12.1.4、可视化

https://github.com/codecentric/spring-boot-admin

<!--    actuator可视化界面    -->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.5.1</version>
        </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

主项目导入

<!-- actuator可视化界面客户端 -->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.5.1</version>
</dependency>

yaml

spring:
    boot:
        admin:
          client:
            url: http://localhost:8081
            instance:
              prefer-ip: true #使用IP注册进来

12.2、Actuator Endpoint

12.2.1、最常使用的端点

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录
ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID描述
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

12.2.2、Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制
# management 是所有actuator的配置
management:
  endpoint:
    #显示health的详细信息
    health:
      show-details: always
  endpoints:
    enabled-by-default: true #默认开启所有的监控端点
    web:
      exposure:
        #以web方式暴露所有端点
        include: '*'

image.png

12.2.3、Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

image.png

12.2.4、管理Endpoints

1、开启与禁用Endpoints
  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint..enabled = true
management:
  endpoint:
    beans:
      enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true
2、暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露healthinfo Endpoint
  • JMX:默认暴露所有Endpoint

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpWh5wbY-1649560184794)(https://gitee.com/wxl2ld/cloudimages/raw/master/img/image-20220326132817798.png)]开启jconsole监控

  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
IDJMXWeb
auditeventsYesNo
beansYesNo
cachesYesNo
conditionsYesNo
configpropsYesNo
envYesNo
flywayYesNo
healthYesYes
heapdumpN/ANo
httptraceYesNo
infoYesYes
integrationgraphYesNo
jolokiaN/ANo
logfileN/ANo
loggersYesNo
liquibaseYesNo
metricsYesNo
mappingsYesNo
prometheusN/ANo
scheduledtasksYesNo
sessionsYesNo
shutdownYesNo
startupYesNo
threaddumpYesNo

12.3、定制 Endpoint

12.3.1、定制 Health 信息

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

}

构建Health
Health build = Health.down()
                .withDetail("msg", "error service")
                .withDetail("code", "500")
                .withException(new RuntimeException())
                .build();

或者继承AbstractHealthIndicator

@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
    /**
     * 功能描述:真实的检查方法
     * @Description:
     * @Date: 2022/3/26 16:30
     * @Param:
     * @return:
     **/
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        //mongodb 获取连接进行测试
        Map<String, Object> map = new HashMap<>();
        //检查完成
        if(1== 2){
//            builder.up();
            builder.status(Status.UP);
            map.put("count", 1);
            map.put("ms", 100);
        }else{
//            builder.down();
            builder.status(Status.OUT_OF_SERVICE);
            map.put("error", "连接超时");
            map.put("errorms", "3000");

        }

        builder.withDetail("code", 100)
                .withDetails(map);

    }
}

management:
    health:
      enabled: true
      show-details: always #总是显示详细信息。可显示每个模块的状态信息

12.3.2、定制info信息

常用两种方式

1、编写配置文件(视频2.4版本,2.6以上不能用)
info:
  appName: boot-admin
  version: 2.0.1
  mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值
  mavenProjectVersion: @project.version@
2、编写InfoContributor
import java.util.Collections;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;

@Component
public class ExampleInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("example",
                Collections.singletonMap("key", "value"));
    }

}

http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息

12.3.3、定制Metrics信息

1、SpringBoot支持自动适配的Metrics
  • JVM metrics, report utilization of:

    • Various memory and buffer pools
    • Statistics related to garbage collection
    • Threads utilization
    • Number of classes loaded/unloaded
  • CPU metrics

  • File descriptor metrics

  • Kafka consumer and producer metrics

  • Log4j2 metrics: record the number of events logged to Log4j2 at each level

  • Logback metrics: record the number of events logged to Logback at each level

  • Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time

  • Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)

  • Spring Integration metrics

2、增加定制Metrics
class MyService{
    Counter counter;
    public MyService(MeterRegistry meterRegistry){
         counter = meterRegistry.counter("myservice.method.running.counter");
    }

    public void hello() {
        counter.increment();
    }
}


//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

12.3.4、定制Endpoint

@Component
@Endpoint(id = "container")
public class DockerEndpoint {


    @ReadOperation
    public Map getDockerInfo(){
        return Collections.singletonMap("info","docker started...");
    }

    @WriteOperation
    private void restartDocker(){
        System.out.println("docker restarted....");
    }

}

场景:开发ReadinessEndpoint来管理程序是否就绪,或者Liveness****Endpoint来管理程序是否存活;

当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

13、原理解析

13.1、Profile功能

同5.2、多环境切换

为了方便多环境适配,springboot简化了profile功能。

13.1.1、application-profile功能

  • 默认配置文件 application.yaml;任何时候都会加载

  • 指定环境配置文件 application-{env}.yaml

  • 激活指定环境

    • 配置文件激活
    • 命令行激活:java -jar xxx.jar –spring.profiles.active=prod --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效

  • 同名配置项,profile配置优先

13.1.2、@Profile条件装配功能

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}

13.1.3、profile分组

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

使用:--spring.profiles.active=production  激活

13.2、外部化配置

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

底下的覆盖上面的配置

指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as **application.properties** files)
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.
  6. Java System properties (System.getProperties()).
  7. JNDI attributes from java:comp/env.
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.

13.2.1、外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

13.2.2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

13.2.3、配置文件加载顺序:

  1. 当前jar包内部的application.properties和application.yml
  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3. 引用的外部jar包的application.properties和application.yml
  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

13.2.4、指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

13.3、自定义starter

7、自定义starter

13.3.1、starter启动原理

  • starter-pom引入 autoconfigurer 包

img

  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类

  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties

    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean

引入starter — xxxAutoConfiguration — 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

13.3.2、自定义starter

atguigu-hello-spring-boot-starter(启动器)

atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

13.4、SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

13.4.1、SpringBoot启动过程

  • 创建 SpringApplication

    • 保存一些信息。

    • 判定当前应用的类型。ClassUtils。Servlet

    • bootstrappers**:初始启动引导器(List):去spring.factories文件中找** org.springframework.boot.Bootstrapper

    • public interface Bootstrapper {
      
      	/**
      	 * Initialize the given {@link BootstrapRegistry} with any required registrations.
      	 * @param registry the registry to initialize
      	 */
      	void intitialize(BootstrapRegistry registry);
      
      }
      
    • ApplicationContextInitializer;去spring.factories****找 ApplicationContextInitializer 初始器

    • image.png

      • List<ApplicationContextInitializer<?>> initializers
    • ApplicationListener ;应用监听器。spring.factories****找 ApplicationListener

      image.png

      • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication

    • startTime = System.nanoTime();
    • 记录应用的启动时间
    • **创建引导上下文(Context环境)**createBootstrapContext()
      • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式。java.awt.headless
    • 获取所有 RunListener**(运行监听器)【为了方便所有Listener进行事件感知】**
      • getSpringFactoriesInstances 去spring.factories****找 SpringApplicationRunListener.

      image.png

    • 遍历 SpringApplicationRunListener 调用 starting 方法;
      • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
    • 保存命令行参数;ApplicationArguments
    • 准备环境 prepareEnvironment();
      • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。
        • 读取所有的配置源的配置属性值。
      • 绑定环境信息
      • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成

    • 创建IOC容器(createApplicationContext())
      • 根据项目类型(Servlet)创建容器,
      • 当前会创建 AnnotationConfigServletWebServerApplicationContext
    • 准备ApplicationContext IOC容器的基本信息 prepareContext()
      • 保存环境信息
      • IOC容器的后置处理流程。
      • 应用初始化器;applyInitializers;
        • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能

        • 遍历所有的 listener 调用 contextPrepared。SpringApplicationRunListenr;通知所有的监听器****contextPrepared

      • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;

    • **刷新IOC容器。**refreshContext
      • 创建容器中的所有组件(Spring注解)
    • 容器刷新完成后工作?afterRefresh
    • 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started

    • **调用所有runners;**callRunners()
      • 获取容器中的 ApplicationRunner

      • 获取容器中的 CommandLineRunner

      • 合并所有runner并且按照@Order进行排序

      • 遍历所有的runner。调用 run 方法

      • @FunctionalInterface
        public interface CommandLineRunner {
        
        	/**
        	 * Callback used to run the bean.
        	 * @param args incoming main method arguments
        	 * @throws Exception on error
        	 */
        	void run(String... args) throws Exception;
        
        }
        
      • @FunctionalInterface
        public interface ApplicationRunner {
        
        	/**
        	 * Callback used to run the bean.
        	 * @param args incoming application arguments
        	 * @throws Exception on error
        	 */
        	void run(ApplicationArguments args) throws Exception;
        
        }
        
    • 如果以上有异常,
      • 调用Listener 的 failed

    • 调用所有监听器的 ready 方法 listeners.ready(context, timeTakenToReady);通知所有的监听器 running

    • **running如果有问题。继续通知 failed 。**调用所有 Listener 的 **failed;**通知所有的监听器 failed

    • this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
      
    • private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) {
              try {
                  try {
                      this.handleExitCode(context, exception);
                      if (listeners != null) {
                          listeners.failed(context, exception);
                      }
      ...............
      

13.4.2、核心组件:

Application Events and Listeners

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners

ApplicationContextInitializer

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        System.out.println("MyApplicationContextInitializer....initialize");
    }
}

ApplicationListener

public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener.......onApplicationEvent");
    }
}

SpringApplicationRunListener

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private SpringApplication application;

    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("MySpringApplicationRunListener.....starting");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("MySpringApplicationRunListener.....environmentPrepared");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.....contextPrepared");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.....contextLoaded");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("MySpringApplicationRunListener.....started");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("MySpringApplicationRunListener.....ready");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("MySpringApplicationRunListener.....failed");
    }
}

ApplicationRunner

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("MyApplicationRunner .... run");
    }
}

CommandLineRunner

@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner....run");
    }
}

创建resources/META-INF/spring.factories

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
  com.beast.boot.listener.MyApplicationContextInitializer


# Application Listeners
org.springframework.context.ApplicationListener=\
  com.beast.boot.listener.MyApplicationListener


# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
  com.beast.boot.listener.MySpringApplicationRunListener

14、Spring Boot与缓存

14.1、JSR107

JSR是Java Specification Requests的缩写,意思是Java 规范提案。2012年10月26日JSR规范委员会发布了JSR 107(JCache API的首个早期草案)。JCache 规范定义了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等,可被用于缓存JSP内最经常读取的数据。

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

接口介绍
**CachingProvider**缓存提供者。 定义了创建、配置、获取、管理和控制多个 CacheManager。一个应用可以在运行期访问多个 CachingProvider。
**CacheManager**缓存管理器。 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache,这些 Cache 存在于 CacheManager 的上下文中。一个 CacheManager 仅被一个 CachingProvider 所拥有。
**Cache**缓存组件。 是一个类似 Map 的数据结构并临时存储以 key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有。
**Entry**键值对。 是一个存储在 Cache 中的 key-value 对。
**Expiry**有效期。 每一个存储在 Cache 中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。

image-20220327211039408

14.2、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache

和org.springframework.cache.CacheManager接口来统一不同的缓存技术;

并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;

  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;

  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

  • 使用Spring缓存抽象时我们需要关注以下两点;

    1、确定方法需要被缓存以及他们的缓存策略

    2、从缓存中读取之前缓存存储的数据

image-20220327213101457

14.3、几个重要概念&缓存注解

Cache缓存接口,定义缓存操作。实现有:RedisCacheEhCacheCacheConcurrentMapCache
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@CachePut保证方法被调用,又希望结果被缓存。
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

打印sql日志方法

mybatis:
  configuration:
    map-underscore-to-camel-case: true


#    打印sql日志方法
    #方法1:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

#方法1:
logging:
  level:
    com:
      beast:
        boot:
          mapper: debug

14.3.1、@Cacheable/@CachePut/@CacheEvict

@Cacheable/@CachePut/@CacheEvict参数

**** 主要的参数
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如: @Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries (@CacheEvict )是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation (@CacheEvict)是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存例如: @CachEvict(value=”testcache”,beforeInvocation=true)
unless (@CachePut) (@Cacheable)用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存例如: @Cacheable(value=”testcache”,unless=”#result == null”)
1、cacheNames/value使用
cacheNames/value:指定缓存的名字;将方法的返回结果放在那个缓存中,是数组的方式,可以指定多个缓存
2、key/KeyGenerator使用
key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值  1-方法的返回值
*            编写SpEl; #id;参数的值 #a0 #p0 #root.args[0]
*            key = "#root.methodName + '[' + #id + ']'"    --->  getEmployeeById[key]
*
*      keyGenerator:key的生成器;可以自己指定key的生成器组件id
*            key/keyGenerator

实现key = getEmployeeById[1]

image-20220328193042260

方法一:
@Cacheable(cacheNames = "employ", key = "#root.methodName + '[' + #id + ']'")
方法二:
@Configuration
public class MyCacheConfig{

    @Bean("myKerGenerator")
    public KeyGenerator keyGenerator(){
        return (target, method, params) -> method.getName() + Arrays.asList(params);
    }
    
}
@Cacheable(cacheNames = "employ", keyGenerator = "myKerGenerator")
3、cacheManager使用
4、condition使用
*      condition:符合条件的情况下才缓存
*          condition = "#a0>1"  :  第一给参数的值大于1才缓存(注意"#a0>1"不能有空格)
5、unless使用
*      unless:否定缓存:当unless指定的条件喂true,方法的返回子就不会被缓存;可以获取到结果进行判断
*          unless = "#result == null"
*          unless = "#a0 == 2"    : 如果第一个参数==2就不缓存

14.3.2、@Cachable 参数的 SpEl表达式

Cache SpEL available metadata

名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;#iban 、 #a0 、 #p0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false)#result

14.3.3、原理解析

1、CacheAutoConfiguration类
--------------------CacheAutoConfiguration-------------------------

@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureAfter({CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
    public CacheAutoConfiguration() {
    }
    。。。。。。。。
1)、导入 CacheAutoConfiguration.CacheConfigurationImportSelector.class
2、CacheAutoConfiguration类 的静态内部类
static class CacheConfigurationImportSelector implements ImportSelector {
    CacheConfigurationImportSelector() {
    }

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        CacheType[] types = CacheType.values();
        String[] imports = new String[types.length];

        for(int i = 0; i < types.length; ++i) {
            imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
        }

        return imports;
    }
}
1)、selectImports(AnnotationMetadata importingClassMetadata)选择缓存的配置类

image-20220328172928171

3、CacheAspectSupport类
private ValueWrapper findCachedItem(Collection<CacheAspectSupport.CacheOperationContext> contexts) {
    Object result = CacheOperationExpressionEvaluator.NO_RESULT;
    Iterator var3 = contexts.iterator();

    while(var3.hasNext()) {
        CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)var3.next();
        if (this.isConditionPassing(context, result)) {
            Object key = this.generateKey(context, result);
            ValueWrapper cached = this.findInCaches(context, key);
            if (cached != null) {
                return cached;
            }
            
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
1)调用方法
private Object generateKey(CacheAspectSupport.CacheOperationContext context, @Nullable Object result) {
    Object key = context.generateKey(result);
    if (key == null) {
        throw new IllegalArgumentException("Null key returned for cache operation (maybe you are using named params on classes without debug info?) " + context.metadata.operation);
    } else {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
        }

        return key;
    }
}
2)执行方法
protected Object generateKey(@Nullable Object result) {
    if (StringUtils.hasText(this.metadata.operation.getKey())) {
        EvaluationContext evaluationContext = this.createEvaluationContext(result);
        return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
    } else {
        return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
    }
}
4、ConcurrentMapCacheManager
1) 调用方法查缓存
public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }

        return cache;
    }
5、ConcurrentMapCache
1)执行方法
protected Object lookup(Object key) {
    return this.store.get(key);
}
public void put(Object key, @Nullable Object value) {
        this.store.put(key, this.toStoreValue(value));
    }

14.3.4、@Cacheable:

运行时机

  1. @Cacheable:先去缓存查数据?返回数据:调用方法存缓存
/**
 * 功能描述:
 * 将方法运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法;
 *
 * CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;

 *
 *  原理:
 *      1、自动配置类;CacheAutoConfiguration
 *      2、缓存配置类
 *             0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
 *             1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
 *             2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
 *             3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
 *             4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
 *             5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
 *             6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
 *             7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
 *             8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"【默认】
 *             9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
 *
 *       3、那个配置类默认生效: SimpleCacheConfiguration matched:
 *
 *       4、给容器中注册了一个cacheManager:ConcurrentMapCacheManager
 *       5、可以获取喝创建ConcurrentMapCacheManager类型的缓存组件;他的作用将数据保存在ConcurrentMap<Object, Object> store;
 *
 *       运行流程:
 *       @Cacheable;
 *       1、方法运行之前,先去查询Cache(缓存组件) ,按照cacheNames指定的名字获取;
 *       (CacheManager先获取相应的缓存), 第一次获取缓存回自动创建
 *       2、去cache中查找缓存的内容,使用一个key,默认就是方法的参数
 *          key是按照某种策略生成的;默认是使用KeyGenerator生成的;
 *          默认是使用SimpleKeyGenerator生成的;
 *                 如果没有参数:key = new SimpleKey();
 *                 如果有一个参数:key = 参数的值
 *                 如果有多个参数:key = new simpleKey(params)
 *       3、没有查到缓存就调用put();
 *       4、将目标的方法返回的结果,放进缓存中
 *
 *       @Cacheable标注的方法执行之前先类检查缓存中没有有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存中
 *                                                                                    如果在缓存找到数据直接返回数据
 *
 *
 *      核心:
 *          1)、使用CacheManager[ConcurrentMapCacheManager]按照名字得到Cache[ConcurrentMapCache]组件
 *          2)、key使用KeyGenerator生成的,默认是使用SimpleKeyGenerator
 *
 *
 *
 *  几个属性:
 *      cacheNames/value:指定缓存的名字;将方法的返回结果放在那个缓存中,是数组的方式,可以指定多个缓存
 *
 *      key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值  1-方法的返回值
 *            编写SpEl; #id;参数的值 #a0 #p0 #root.args[0]
 *            key = "#root.methodName + '[' + #id + ']'"    --->  getEmployeeById[key]
 *
 *      keyGenerator:key的生成器;可以自己指定key的生成器组件id
 *            key/keyGenerator
 *
 *      cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
 *
 *      condition:符合条件的情况下才缓存
 *          condition = "#a0>1"  :  第一给参数的值大于1才缓存(注意"#a0>1"不能有空格)
 *
 *      unless:否定缓存:当unless指定的条件喂true,方法的返回子就不会被缓存;可以获取到结果进行判断
 *          unless = "#result == null"
 *          unless = "#a0 == 2"    : 如果第一个参数==2就不缓存
 *
 *      sync:是否使用异步模式
 *
 * @Param: id
 * @return:
 **/

14.3.5、@CachePut:

运行时机

  1. @CachePut:先执行方法 and 方法入缓存中

1、第一次查询

image-20220328202546855

执行方法

image-20220328202658555

2、第二次查询直接调用缓存

3、更新

image-20220328202808663

4、再次查询

image-20220328202850021

问题:查询结果和更新的数据不一致

原因:出现问题因为运行时机和key未指定的原因

解决指定@CachePut的key

/**
     * @CachePut:即调用方法,又更新数据;同步更新缓存
     * 修改了数据得到某个数据,同时更新缓存;
     * 运行时机:
     *  1、先调用目标方法
     *  2、将目标方法的结果缓存起来
     *
     *
     *  测试步骤:
     *          1、查询1号员工;查到的结果会放到缓存中
     *              key:1 value:lastName:张三
     *          2、以后查询还是之前的结果
     *          3、更新1号员工;【id=1&lastName=zhangsan&gender=0】
     *              将方法的返回子也放进缓存了;
     *              key:传入的employee对象的    value: 返回的employee对象;
     *          4、查询1号员工?
     *              应该是更新后的员工
     *                  key = "#employee.id" 使用传入的参数的员工id  == key = "#result.id" 使用返回后对象的id
     *                      因为@Cacheable运行前要查缓存必须先有key,所以它的key不能用result
     *              为什么是没更新前的?
     *
     **/

14.3.6、@CacheEvict

特殊属性:beforeInvocation、allEntries

/**
     * @CacheEvict:缓存清除
     * key:指定要清除的数据
     * allEntries = true: 指定清除这个缓存中所有数据
     * beforeInvocation = false:缓存的清除是否在方法之前执行
     *          默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就会清除
     *
     * beforeInvocation = true:
     *          代表清除缓存清除操作是在方法执行之前执行;无论方法是否出现异常,缓存都清除
     **/
@CacheEvict(cacheNames = "employ", allEntries = true, beforeInvocation = true)

14.3.7、@Caching组合注解

//@Caching定义复杂的缓存规则
@Caching(
        cacheable = {
                @Cacheable(value = "employ", key = "#lastName")
        },
        put = {
                @CachePut(value = "employ", key = "#result.id"),
                @CachePut(value = "employ", key = "#result.email")
        }
)
@Override
public Employee getEmployeeByLastName(String lastName) {
    Employee employee = employeeMapper.getEmployeeByLastName(lastName);
    System.out.println("@CachingEmployee" + employee);
    return employee;
}
public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}

出现问题:多次调用按名查找但是还是一直sql查询

原因:虽然标注@Cacheable,但是有@CachePut在,所以getEmployeeByLastName(String lastName)一定还会被执行

14.3.8、@CacheConfig组合注解

@CacheConfig(cacheNames = "employ")//公共的配置
public @interface CacheConfig {
    String[] cacheNames() default {};

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";
}

注意!!!!

注意!!!!

注意!!!!

@Caching的配置必须指定

@Caching(
        cacheable = {
                @Cacheable(value = "employ", key = "#lastName")
        },
        put = {
                @CachePut(value = "employ", key = "#result.id"),
                @CachePut(value = "employ", key = "#result.email")
        }
)
@Override
public Employee getEmployeeByLastName(String lastName) {
    Employee employee = employeeMapper.getEmployeeByLastName(lastName);
    System.out.println("@CachingEmployee" + employee);
    return employee;
}

14.4、整合redis实现缓存

/**
 * Redis常见的5个数据类型
 *      String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
 *
 *      stringRedisTemplate.opsForValue();[操作String(字符串)]
 *      stringRedisTemplate.opsForList();[操作List(列表)]
 *      stringRedisTemplate.opsForSet();[操作Set(集合)]
 *      stringRedisTemplate.opsForHash();[操作Hash(散列)]
 *      stringRedisTemplate.opsForZSet();[操作ZSet(有序集合)]
 **/

14.4.1、使用redis

1、保存类型为对象时出现错误
Employee employeeById = employeeMapper.getEmployeeById(2);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
redisTemplate.opsForValue().set("emp-01", employeeById);
    //1)自己将对象转为json
    //2)redisTemplate默认的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGvas7jS-1649560184799)(https://gitee.com/wxl2ld/cloudimages/raw/master/img/image-20220329191010956.png)]

解决

2、使用jdk序列化机制,序列化后的数据保存到redis

在pojo层实现接口Serializable

public class Employee implements Serializable {
3、自己将对象转为json

自己写Uitl转换

4、redisTemplate默认的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6yZrFkEq-1649560184800)(https://gitee.com/wxl2ld/cloudimages/raw/master/img/image-20220329191657892.png)]

编写配置类

@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Employee> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
//        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        template.setDefaultSerializer(genericJackson2JsonRedisSerializer);
        return template;
    }
}
@SpringBootTest
class BootCache01ApplicationTests {
  @Autowired
    RedisTemplate<Object, Employee> employeeRedisTemplate;
    
    @Test
    void testRedis() {
        Employee employeeById = employeeMapper.getEmployeeById(2);
        //默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
            //1)自己将对象转为json
            //2)redisTemplate默认的

        employeeRedisTemplate.opsForValue().set("emp-02", employeeById);
    }
}

14.4.2、Redis整合缓存

* 三、整合redis作为缓存
*  redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
*      1、安装redis : 使用docker
*      2、引入redis的stater
*      3、配置redis
*      4、测试缓存
*              原理:CacheManager===Cache 缓存组件来实际个给缓存中存取数据

引入redis的stater默认使用redis的RedisCacheConfiguration

image-20220329192532533

1、重写RedisTemplate解决调用RedisTemplate序列化问题
    @Bean
    public RedisTemplate<Object, Employee> employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Employee> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
//        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        template.setDefaultSerializer(serializer);
        return template;
    }
2、通过自定义cacheManager解决CRUD的序列化问题
出现问题

缓存的数据能存入redis

  • 第二次从缓存中查询就不能反序列化回来了;
  • 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作
/**
 * 缓存的数据能存入redis
 * 第二次从缓存中查询就不能反序列化回来了;
 * 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作
 **/
@Override
@Cacheable(cacheNames = "dept")
//还可以通过缓存管理器操作缓存
public Department getDeptById(Integer id){
    System.out.println("查询部门" + id);
    Department department = departmentMapper.getDeptById(id);
    return department;
}
解决方法
@Bean
    public CacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory){
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                        //可选设置序列化key
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                        //序列化value
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Employee.class))))
                .build();
    }
    @Bean
    @Primary
    public CacheManager departmentCacheManager(RedisConnectionFactory redisConnectionFactory){
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                        //可选设置序列化key
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                        //序列化value
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Department.class))))
                .build();
    }

并配置serviceImpl中的cacheManager

image-20220329222335887

具体看(76条消息) SpringBoot2.x版本自定义cacheManager_黄铜小马的博客-CSDN博客

15、Spring Boot与消息

15.1、引入场景

15.1.1、异步处理

image-20220330001802908

image-20220330001822154

image-20220330001829341

15.1.2、应用解耦

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lcQNFyDw-1649560184802)(https://gitee.com/wxl2ld/cloudimages/raw/master/img/image-20220330001957949.png)]

image-20220330002003290

15.1.3、流量削峰

image-20220330002022457

15.2、概述

1.大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力

2.消息服务中两个重要概念:

消息代理(message broker)和 目的地(destination)

​ 消息服务中间件的服务器 发送的地点

当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。

3.消息队列主要有两种形式的目的地

​ **1.队列(queue):**点对点消息通信(point-to-point)

​ **2.主题(topic):**发布(publish)/订阅(subscribe)消息通信

15.3、消息通信机制

15.3.1、点对点式:

–消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列

–消息只有唯一的发送者和接受者,但并不是说只能有一个接收者**(一旦被消费了就不能在接收了)**

15.3.2、发布订阅(监听)式:

–发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息

15.4、消息服务的规范

15.4.1、JMS(Java Message Service)JAVA消息服务:

–基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现

15.4.2、AMQP(Advanced Message Queuing Protocol)

–高级消息队列协议,也是一个消息代理的规范,兼容JMS

–RabbitMQ是AMQP的实现

15.4.3、JMS和AMQP的区别

JMSAMQP
定义Java api网络线级协议
跨语言
跨平台
Model提供两种消息模型: (1)、Peer-2-Peer (2)、Pub/sub提供了五种消息模型: (1)、direct exchange (2)、fanout exchange (3)、topic change (4)、headers exchange (5)、system exchange 本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分;
支持消息类型多种消息类型: TextMessage MapMessage BytesMessage StreamMessage ObjectMessage Message (只有消息头和属性)byte[] 当实际应用时,有复杂的消息,可以将消息序列化后发送。
综合评价JMS 定义了JAVA API层面的标准;在java体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差;AMQP定义了wire-level层的协议标准;天然具有跨平台、跨语言特性。

15.5、spring支持

1、Spring支持

–spring-jms提供了对JMS的支持

–spring-rabbit提供了对AMQP的支持

–需要ConnectionFactory的实现来连接消息代理

–提供JmsTemplate、RabbitTemplate来发送消息

–@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息

–@EnableJms、@EnableRabbit开启功能支持

2、Spring Boot自动配置

JmsAutoConfiguration

RabbitAutoConfiguration

15.6、RabbitMQ简介

15.6.1、RabbitMQ简介:

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。

15.6.2、核心概念

1、Message

消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头:则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

2、Publisher

消息的生产者,也是一个向交换器发布消息的客户端应用程序。

3、Exchange

交换器,用来接收生产者发送的消息将这些消息路由给服务器中的队列。

Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别

4、Queue

消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

5、Binding

绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

Exchange 和Queue的绑定可以是多对多的关系。

6、Connection

网络连接,比如一个TCP连接。

7、Channel

信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

8、Consumer

消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

9、Virtual Host

虚拟主机:表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / (vhost之间是相互隔离的)。

10、Broker

表示消息队列服务器实体

11、总览

image-20220330004931528

15.7、RabbitMQ运行机制

AMQP 中的消息路由

  • AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 ExchangeBinding 的角色。生产者把消息发布到 Exchange 上,消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列。

image-20220330005102383

15.7.1、Exchange的类型

  • Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键, headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
1、Direct Exchange(点对点)

image-20220330130925932

​ 消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。

2、Fanout Exchange(广播)

image-20220330131201512

​ 每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。

3、Topic Exchange(模糊匹配)

image-20220330131234619

​ topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号*#匹配0个或多个单词*匹配一个单词

15.8、RabbitMQ整合

image-20220330131517126

/**
 *  自动配置
 *      1、RabbitAutoConfiguration
 *      2、有自动配置了连接工厂
 *      3、RabbitProperties封装了RabbitMq的配置
 *      4、RabbitTemplate:给RabbitMQ发送和接受消息
 *      5、AmqpAdmin: RabbitMQ系统管理功能组件
 *          AmqpAdmin : 创建和删除Queue、Exchange、Binding
 *      6、@RabbitListener + @EnableRabbit:监听消息队列的内容
 **/

15.8.1、安装RabbitMq

docker pull rabbitmq:3-management

15.8.2、端口映射

5672:客户端和RabbitMq通信的端口

15672:管理接口访问web界面的端口

1.引入 spring-boot-starter-amqp
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.application.yml****配置
spring:
  rabbitmq:
    host: 175.178.183.32
    stream:
      username: guest
      password: guest
#      虚拟主机名
#    virt
3.初始的MessageConverter的配置
private MessageConverter messageConverter = new SimpleMessageConverter();
方法一:

通过导入容器中的 RabbitTemplate然后设置MessageConverter属性

注意此方法在监听的时候会报错

@Configuration
public class MyAMQPConfig {

@Autowired
RabbitTemplate rabbitTemplate;

@Bean
public void messageConverter() {
    rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
}
方法二:

通过new MessageConverter 代替初始的

@Configuration
public class MyAMQPConfig {
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}
4.RabbitTemplate:消息发送处理组件
@Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 1、单播(点对点)
     **/
    @Test
    void contextLoads() {
//        Message需要自己构造一个;定义消息体内容和消息头
//        rabbitTemplate.send(exchange, routeKey, message);

//        object默认当初消息体只需要传入要发送的对象自动序列化发送给rabbitmq;
//        rabbitTemplate.convertAndSend(exchange, routeKey, object);
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "第一条消息");
        Book book = new Book("金瓶梅", 5);
        map.put("data", Arrays.asList("hello", 1213, true));
        //对象被默认序列化以后发送出去
        rabbitTemplate.convertAndSend("exchange.direct", "zhansan.emps", book);
    }

    //接收数据,如何将数据自动转为json发送出去
    @Test
    public void receive() {
        Object zhansan = rabbitTemplate.receiveAndConvert("zhansan.news");
        System.out.println(zhansan.getClass());
        System.out.println(zhansan);

    }

    /**
     * 2、广播
     **/
    @Test
    public void fanout(){
        Book book = new Book("西游记", 5);
        rabbitTemplate.convertAndSend("exchange.fanout", "", book);
    }
5.AmqpAdmin:管理组件
@Autowired
    AmqpAdmin amqpAdmin;

    @Test
    void createExchange(){
//        amqpAdmin.declareExchange(new DirectExchange("amqpExchange.direct"));
//        amqpAdmin.declareQueue(new Queue("wangwu"));
        //创建绑定规则
        amqpAdmin.declareBinding(new Binding("wangwu", Binding.DestinationType.QUEUE, "amqpExchange.direct", "wangwu", null));
    }

16、Spring Boot与检索

16.1、检索

我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持;

Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,github等大型的站点也是采用了ElasticSearch作为其搜索服务,

16.2、概念

  • 员工文档 的形式存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做 索引 ,但在索引一个文档之前,需要确定将文档存储在哪里。

  • 一个 ElasticSearch 集群可以 包含多个 索引 ,相应的每个索引可以包含多个 类型 。 这些不同的类型存储着多个 文档 ,每个文档又有 多个 属性

  • 类似关系:

    –索引-数据库

    –类型-表

    –文档-表中的记录

    –属性-列

image-20220330131730330

16.3、整合ElasticSearch

16.3.1、ElasticSearch安装

1、端口映射

9200:web通信

9300:分布式的情况下节点通信

2、安装
docker run --name elasticsearch -d -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "discovery.type=single-node" -p 9200:9200 -p 9300:9300 elasticsearch:7.7.0
3、导入starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

版本匹配

image-20220331204646791

4、创建索引
@Data
@Document(indexName = "blog")
public class Article {

    @Id
    private String id;
    private String title;
    @Field(type = FieldType.Nested, includeInParent = true)
    private List<Author> authors;

    public Article(String title) {
        this.title = title;
    }
}
5、CRUD支持
@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, String> {

    //根据作者的名称 搜索
    Page<Article> findByAuthorsName(String name, Pageable pageable);

    //搜索title字段
    Page<Article> findByTitleIsContaining(String word, Pageable pageable);


    Page<Article> findByTitle(String title, Pageable pageable);

}
6、测试
@SpringBootTest
class Boot03ElasticsearchApplicationTests {

    @Autowired
    ArticleRepository articleRepository;

    @Test
    void testSave() {
        Article article = new Article("Spring Data Elasticsearch");
        article.setAuthors(Arrays.asList(new Author("god"), new Author("John")));
        articleRepository.save(article);

        article = new Article("Spring Data Elasticsearch2");
        article.setAuthors(Arrays.asList(new Author("god"), new Author("King")));
        articleRepository.save(article);

        article = new Article("Spring Data Elasticsearch3");
        article.setAuthors(Arrays.asList(new Author("god"), new Author("Bill")));
        articleRepository.save(article);
    }

    @Test
    void queryAuthorName(){
        Page<Article> articles = articleRepository.findByAuthorsName("laoli", PageRequest.of(0, 10));
        if (articles == null){
            System.out.println("null");
        }
        for (Article article : articles) {
            System.out.println(article);
            for (Author author : article.getAuthors()) {
                System.out.println(author);
            }
        }
    }

    @Test
    void update(){
        Page<Article> articles = articleRepository.findByTitle("Spring Data Elasticsearch", PageRequest.of(0, 10));

        Article article = articles.getContent().get(0);
        article.setAuthors(Collections.singletonList(new Author("laoli")));
        articleRepository.save(article);
        System.out.println(article);

    }

    @Test
    void delete(){
        Page<Article> articles = articleRepository.findByTitle("Spring Data Elasticsearch", PageRequest.of(0, 10));
        Article article = articles.getContent().get(0);
        articleRepository.delete(article);
    }
    //老版本
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 使用ElasticsearchRestTemplate进行关键字查询
     * 关于正则表达式可以参考https://www.runoob.com/java/java-regular-expressions.html
     * .*data.* 可以匹配ddata 、 dataa等
     **/
//    @Test
//    void queryTitleContainByTemplate(){
//        Query query = new NativeSearchQueryBuilder().withFilter()
//    }

}

17、Spring Boot与任务

17.1、异步任务

在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况之前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完美解决这个问题。

两个注解:

@EnableAysnc、@Aysnc

17.2、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口。

两个注解:@EnableScheduling、@Scheduled

**cron**表达式:

字段允许值允许的特殊字符
0-59, - * /
0-59, - * /
小时0-23, - * /
日期1-31, - * ? / L W C
月份1-12, - * /
星期0-7或SUN-SAT 0,7是SUN, - * ? / L C #
特殊字符代表含义
,枚举
-区间
*任意
/步长
?日/星期冲突匹配
L最后
W工作日
C和calendar联系后计算过的值
#星期,4#2,第2个星期四

17.3、邮件任务

•邮件发送需要引入spring-boot-starter-mail

•Spring Boot 自动配置MailSenderAutoConfiguration

•定义MailProperties内容,配置在application.yml中

•自动装配JavaMailSender

•测试邮件发送

image-20220331225135246

18、Spring Boot与安全

18.1、安全

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

几个类:

WebSecurityConfigurerAdapter:自定义Security策略

AuthenticationManagerBuilder:自定义认证策略

@EnableWebSecurity:开启WebSecurity模式

  • 应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。

  • “认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。

  • “授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。

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

18.1.1、整合springboot-security

1)导入依赖
<!-- springboot-security       -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
2)编写配置类
package com.beast.boot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/1 14:37
 */
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //定制请求的授权规则
        http.authorizeHttpRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");

        //开启自动配置登陆功能
        //效果:
        //如果没有登录权限,就会跳转到springSecurity生成的/login界面
        http.formLogin();
        //1、login来到登录页
        //2、重定向到/login?error表示登录失败

        //开启自动配置的注销功能
        http.logout().logoutSuccessUrl("/");
        //1、访问/logout表示用户注销,清空session
        //2、注销成功会返回/login?logout页面
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //passwordEncoder()需要放一个密码解析类
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("zhansan").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2")
                .and()
                .withUser("lisi").password(new BCryptPasswordEncoder().encode("123")).roles("VIP2", "VIP3")
                .and()
                .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP3");



    }
    
}

18.1.2、整合security和thymeleaf

1)导入依赖
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
2)导入html标签
<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
3)实践
package com.beast.boot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/1 14:37
 */
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //定制请求的授权规则
        http.authorizeHttpRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");

        //开启自动配置登陆功能
        //效果:
        //如果没有登录权限,就会跳转到springSecurity生成的/login界面
        http.formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage("/userlogin");
        //1、login来到登录页
        //2、重定向到/login?error表示登录失败
        //3、默认post形式的/login代表处理登录
        //4、一但但定制loginPage;那么login的post请求就是登录

        //开启自动配置的注销功能
        http.logout().logoutSuccessUrl("/");
        //1、访问/logout表示用户注销,清空session
        //2、注销成功会返回/login?logout页面



        //开启记住我的功能
        http.rememberMe().rememberMeParameter("remember");
        //登录成功以后,将cookie给浏览器保存,以后访问页面带上这个cookie,只要通过检查就可以免登录
        //点击注销会删除cookie
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //passwordEncoder()需要放一个密码解析类
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("zhansan").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2")
                .and()
                .withUser("lisi").password(new BCryptPasswordEncoder().encode("123")).roles("VIP2", "VIP3")
                .and()
                .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP3");



    }

}

18.2、Web&安全

1.登陆/注销

–HttpSecurity配置登陆、注销功能

2.Thymeleaf提供的SpringSecurity标签支持

–需要引入thymeleaf-extras-springsecurity4

–sec:authentication=“name”获得当前用户的用户名

–sec:authorize=“hasRole(‘ADMIN’)”当前用户必须拥有ADMIN权限时才会显示标签内容

3.remember me

–表单添加remember-me的checkbox

–配置启用remember-me功能

4.CSRF(Cross-site request forgery)跨站请求伪造

HttpSecurity启用csrf功能,会为表单添加_csrf的值,提交携带来预防CSRF;

19、Spring Boot与分布式

分布式、Dubbo/Zookeeper、Spring Boot/Cloud

19.1、分布式应用

在分布式系统中,国内常用zookeeper+dubbo组合,而Spring Boot推荐使用全栈的Spring,Spring Boot+Spring Cloud。

分布式系统:

image-20220401163033175

19.1.1、单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

19.1.2、垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

19.1.3、分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

1)RPC架构
a.作用:
  1. 通过IP和端口来实现服务通信
  2. 服务可以复用
  3. 维护方便
  4. 减少网络开销
b.特点:

应用直接调用服务,服务之间是隔离的

c.缺点:

服务过多,管理成本高昂。服务治理、服务注册、发现、服务容错、服务跟踪、服务网关、IP暴露等都是此架构无法避免的问题

2)RPC与HTTP、TCP、UDP、Socket的区别
  • **TCP/UDP:**都是传输协议,主要区别是tcp协议连接需要3次握手,断开需要四次挥手,是通过流来传输的,就是确定连接后,一直发送信息,传完后断开。udp不需要进行连接,直接把信息封装成多个报文,直接发送。所以udp的速度更快写,但是不保证数据的完整性。

  • **Http:**超文本传输协议是一种应用层协议,建立在TCP协议之上

  • **Socket:**是在应用程序层面上对TCP/IP协议的封装和应用。其实是一个调用接口,方便程序员使用TCP/IP协议栈而已。程序员通过socket来使用tcp/ip协议。但是socket并不是一定要使用tcp/ip协议,Socket编程接口在设计的时候,就希望也能适应其他的网络协议。

  • **RPC:**是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。所以RPC的实现可以通过不同的协议去实现比如可以使http、RMl等。

3)RPC(基于RMI)的运行流程

首先:要解决通讯的问题(TCP),主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。

第二:要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI(一种目录服务,通过该目录服务进行服务注册与搜索)服务上查找。如果是RMI调用的话,还需要一个RMl Registry来注册服务的地址

第三:当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是**序列化(Serialize)**或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。

第四:B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
第五:返回值还要发送回服务器A上的应用,也要经过
序列化
的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用

19.1.4、流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

1)SOA架构
  • SOA(Service oriented Architecture):面向服务架构
  • ESB(Enterprise Service Bus):服务中介。主要是提供了一个服务于服务之间的交互。
  • ESB包含功能:负载均衡、流量控制、加密处理、服务的监控、异常处理、监控告急等等。
    1. 总线基础服务框架:提供系统一致性、安全性、可靠性,以及性能和扩展能力保障的基础技术手段。
    2. 集成服务:提供基础的集成服务与用户定制的应用服务;支持多种集成服务模式;支持服务的封装、重用、服务组合、服务调度。
    3. 公用服务:提供内置的各种公用服务。例如,渠道认证服务,日志服务等公用服务。
    4. 服务管理和服务标准:提供服务配置管理的前台工具集合,并提供行业的服务规约标准。
    5. 系统监控:提供多角度的系统实时监控与交易报表,提供用户定制的告警。
    6. 安全体系:提供多种安全机制并支持和第三方安全系统的有效集成,提供有效的安全监控机制。

19.1.5、微服务

1)概述

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)

简而言之,微服务体系结构风格是一种将单个应用程序开发为一套小型服务的方法,每个服务都在自己的流程中运行,并与轻量级机制(通常是HTTP资源API)通信。这些服务围绕业务能力构建,并可通过全自动部署机制独立部署。对这些服务的集中管理是最低限度的,这些服务可以用不同的编程语言编写,并使用不同的数据存储技术--詹姆斯·刘易斯和马丁·福勒(2014)
2)优点:
  • 测试容易:服务高内聚低耦合,每个服务可以独立测试。
  • 可伸缩性强:服务相对独立,可随时增删服务实现系统服务变化,可针对某服务独立水平扩展
  • 可靠性强:服务出现问题,受影响的位置是当前服务,不会影响其他服务。应用健壮性有更好的保证。
  • 跨语言程度会更加灵可针对服务特性使用不同的语言开发,尽可能发挥出每种语言的特性。
  • 团队协作容易:团队专注自主研发的服务,对其他服务的了解可局限在服务的调用上。
  • 系统迭代容易:当服务发生变更时,只需针对单一服务进行系统升级迭代
3)缺点:
  • 运维成本过高,部署数量较多∶服务过多导致运维成本成倍提升
  • 接口兼容多版本:因服务可独立升级迭代,所以会导致接口版本过多。
  • 分布式系统的复杂性:系统分部,导致通讯成本提升,系统复杂度提升。
  • 分布式事务:分布式系统会引出分布式事务的出现,现在有很多的分布式事务解决方案,分布式事务不是不可解决的,这不会影响微服务架构的应用。

19.2、RMI实现

19.2.1、项目实现

整体框架搭建步骤
  1. 创建Maven父项目

  2. 创建提供接口子项目

    • 即service层
  3. 创建服务端兼(实现)子项目

    • 即 service下的 Impl层
  4. 创建客户端即消费者和访问端子项目

    • 提供接口子项目的接口远程代理对象给给服务的发现

注意 子项目中需要导入父依赖中导入父坐标

<parent>
  <groupId>com.beast</groupId>
  <artifactId>rpc-rmi-demo1</artifactId>
  <version>1.0-SNAPSHOT</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>
1、创建Maven父项目

image-20220402161504358

a)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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.beast</groupId>
    <artifactId>rpc-rmi-demo1</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>rmi-api</module>
        <module>rmi-server</module>
        <module>rmi-client</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>


</project>
2、创建提供接口子项目

image-20220402162459843

a)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.beast</groupId>
  <artifactId>rmi-api</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>rmi-api</name>


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

  <dependencies>

  </dependencies>

  <build>

  </build>
</project>

b)编写接口和实体类

包层

image-20220402163256523

注意 :因为是远程调用,所以实体类要实现序列化接口

public class User implements Serializable 

编写接口

/**
 * @author wxl
 * @version 1.0
 * @description: 用户查询API, 接口定义
 * @date 2022/4/2 14:57
 */
public interface UserServiceI {

    /**
     * 功能描述:通过用户Id查询用户
     **/
    public User queryUserByUserId(Integer id) throws RemoteException;
}
3、创建服务端兼(实现)子项目
创建项目

​ 同api创建

a)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>


  <artifactId>rmi-server</artifactId>
  <version>1.0-SNAPSHOT</version>
  <parent>
    <groupId>com.beast</groupId>
    <artifactId>rpc-rmi-demo1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <name>rmi-server</name>


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

  <dependencies>
    <dependency>
      <groupId>com.beast</groupId>
      <artifactId>rmi-api</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

  <build>

  </build>
</project>
b)编写服务实现
package com.beast.server.service;

import com.beast.api.pojo.User;
import com.beast.api.service.UserServiceI;
import org.springframework.stereotype.Service;

import java.rmi.RemoteException;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:01
 */
@Service
public class UserServiceImpl implements UserServiceI {
    @Override
    public User queryUserByUserId(Integer id) throws RemoteException {
        System.out.println("服务端收到请求,请求参数 ---》" + id);
        User user = new User();
        user.setId(id);
        user.setUsername("zhangsan");
        return user;
    }
}
c)编写Rmi远程调用的配置
package com.beast.server.config;

import com.beast.api.service.UserServiceI;
import com.beast.server.service.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;

import javax.annotation.Resource;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:40
 */
@Configuration
public class RmiServerConfig {

    @Resource
    private UserServiceI userServiceI;

    @Bean
    public RmiServiceExporter rmiServiceExporter(){
        RmiServiceExporter exporter =  new RmiServiceExporter();
        //配置服务的端口
        exporter.setRegistryPort(2002);
        //设置服务的名称
        exporter.setServiceName("userService");
        //设置服务的实现
        exporter.setService(userServiceI);
        //设置服务的实现的接口:客户端根据 接口创建代理对象
        exporter.setServiceInterface(UserServiceI.class);
        return exporter;
    }
}
d)编写启动类
@SpringBootApplication
public class RmiServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(RmiServiceApplication.class, args);
    }
}
4、创建客户端和访问端子项目
创建项目

​ 同api创建

a)编写controller访问层
package com.beast.client.controller;

import com.beast.api.pojo.User;
import com.beast.api.service.UserServiceI;

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

import javax.annotation.Resource;
import java.rmi.RemoteException;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:25
 */
@RestController
public class UserController {

    @Resource
    private UserServiceI userServiceI;

    @GetMapping("/user/{id}")
    public User queryUserByUserId(@PathVariable("id") Integer id){
        try {
            return userServiceI.queryUserByUserId(id);
        } catch (RemoteException e) {
            e.printStackTrace();
            return null;
        }
    }
}
b)编写远程代理对象配置

注意:自 spring5.3 起已弃用(逐步淘汰基于序列化的远程处理)

package com.beast.client.config;

import com.beast.api.service.UserServiceI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

/**
 * @author wxl
 * @version 1.0
 * @description:
全系统的有效集成,提供有效的安全监控机制。

### 19.1.5、微服务

#### 1)概述

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)

简而言之,微服务体系结构风格是一种将单个应用程序开发为一套小型服务的方法,每个服务都在自己的流程中运行,并与轻量级机制(通常是HTTP资源API)通信。这些服务围绕业务能力构建,并可通过全自动部署机制独立部署。对这些服务的集中管理是最低限度的,这些服务可以用不同的编程语言编写,并使用不同的数据存储技术–詹姆斯·刘易斯和马丁·福勒(2014)




#### 2)优点:

- 测试容易:服务高内聚低耦合,每个服务可以独立测试。
- 可伸缩性强:服务相对独立,可随时增删服务实现系统服务变化,可针对某服务独立水平扩展
- 可靠性强:服务出现问题,受影响的位置是当前服务,不会影响其他服务。应用健壮性有更好的保证。
- 跨语言程度会更加灵可针对服务特性使用不同的语言开发,尽可能发挥出每种语言的特性。
- 团队协作容易:团队专注自主研发的服务,对其他服务的了解可局限在服务的调用上。
- 系统迭代容易:当服务发生变更时,只需针对单一服务进行系统升级迭代

#### 3)缺点:

- 运维成本过高,部署数量较多∶服务过多导致运维成本成倍提升
- 接口兼容多版本:因服务可独立升级迭代,所以会导致接口版本过多。
- 分布式系统的复杂性:系统分部,导致通讯成本提升,系统复杂度提升。
- 分布式事务:分布式系统会引出分布式事务的出现,现在有很多的分布式事务解决方案,分布式事务不是不可解决的,这不会影响微服务架构的应用。

## 19.2、RMI实现

### 19.2.1、项目实现 

#### 整体框架搭建步骤

1. 创建Maven父项目

2. 创建提供接口子项目

   - 即service层

3. 创建服务端兼(实现)子项目 

   - 即 service下的 Impl层 

4. 创建客户端即消费者和访问端子项目

   - 提供接口子项目的接口远程代理对象给给服务的发现

   

> 注意 子项目中需要导入父依赖中导入父坐标 

```xml
<parent>
  <groupId>com.beast</groupId>
  <artifactId>rpc-rmi-demo1</artifactId>
  <version>1.0-SNAPSHOT</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>
1、创建Maven父项目

[外链图片转存中…(img-nEYpB0PS-1649560184807)]

a)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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.beast</groupId>
    <artifactId>rpc-rmi-demo1</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>rmi-api</module>
        <module>rmi-server</module>
        <module>rmi-client</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>


</project>
2、创建提供接口子项目

[外链图片转存中…(img-mGhjbrin-1649560184807)]

a)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.beast</groupId>
  <artifactId>rmi-api</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>rmi-api</name>


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

  <dependencies>

  </dependencies>

  <build>

  </build>
</project>

b)编写接口和实体类

包层

[外链图片转存中…(img-Ir0DshEs-1649560184807)]

注意 :因为是远程调用,所以实体类要实现序列化接口

public class User implements Serializable 

编写接口

/**
 * @author wxl
 * @version 1.0
 * @description: 用户查询API, 接口定义
 * @date 2022/4/2 14:57
 */
public interface UserServiceI {

    /**
     * 功能描述:通过用户Id查询用户
     **/
    public User queryUserByUserId(Integer id) throws RemoteException;
}
3、创建服务端兼(实现)子项目
创建项目

​ 同api创建

a)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>


  <artifactId>rmi-server</artifactId>
  <version>1.0-SNAPSHOT</version>
  <parent>
    <groupId>com.beast</groupId>
    <artifactId>rpc-rmi-demo1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <name>rmi-server</name>


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

  <dependencies>
    <dependency>
      <groupId>com.beast</groupId>
      <artifactId>rmi-api</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

  <build>

  </build>
</project>
b)编写服务实现
package com.beast.server.service;

import com.beast.api.pojo.User;
import com.beast.api.service.UserServiceI;
import org.springframework.stereotype.Service;

import java.rmi.RemoteException;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:01
 */
@Service
public class UserServiceImpl implements UserServiceI {
    @Override
    public User queryUserByUserId(Integer id) throws RemoteException {
        System.out.println("服务端收到请求,请求参数 ---》" + id);
        User user = new User();
        user.setId(id);
        user.setUsername("zhangsan");
        return user;
    }
}
c)编写Rmi远程调用的配置
package com.beast.server.config;

import com.beast.api.service.UserServiceI;
import com.beast.server.service.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;

import javax.annotation.Resource;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:40
 */
@Configuration
public class RmiServerConfig {

    @Resource
    private UserServiceI userServiceI;

    @Bean
    public RmiServiceExporter rmiServiceExporter(){
        RmiServiceExporter exporter =  new RmiServiceExporter();
        //配置服务的端口
        exporter.setRegistryPort(2002);
        //设置服务的名称
        exporter.setServiceName("userService");
        //设置服务的实现
        exporter.setService(userServiceI);
        //设置服务的实现的接口:客户端根据 接口创建代理对象
        exporter.setServiceInterface(UserServiceI.class);
        return exporter;
    }
}
d)编写启动类
@SpringBootApplication
public class RmiServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(RmiServiceApplication.class, args);
    }
}
4、创建客户端和访问端子项目
创建项目

​ 同api创建

a)编写controller访问层
package com.beast.client.controller;

import com.beast.api.pojo.User;
import com.beast.api.service.UserServiceI;

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

import javax.annotation.Resource;
import java.rmi.RemoteException;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 15:25
 */
@RestController
public class UserController {

    @Resource
    private UserServiceI userServiceI;

    @GetMapping("/user/{id}")
    public User queryUserByUserId(@PathVariable("id") Integer id){
        try {
            return userServiceI.queryUserByUserId(id);
        } catch (RemoteException e) {
            e.printStackTrace();
            return null;
        }
    }
}
b)编写远程代理对象配置

注意:自 spring5.3 起已弃用(逐步淘汰基于序列化的远程处理)

package com.beast.client.config;

import com.beast.api.service.UserServiceI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

/**
 * @author wxl
 * @version 1.0
 * @description:
 * @date 2022/4/2 16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值