4.springboot原理篇

原理篇

spring与springboot区别

  • spring是承载容器

  • springboot做的主要工作:

    ①简化配置(省去了spring中配置xml,引入application.yml文件)

    ②为我们提供了 spring-boot-starter-web 依赖,这个依赖包含了Tomcat和springmvc等一系列的web依赖

    ③自动配置(基于自动配置包spring-boot-starter-autoconfig

1.自动配置的工作流程

1.1 bean的加载方式

方式一:配置文件+<bean/>标签

image-20230403161415267

  • 缺点:配置bean太繁琐
方式二:配置文件扫描+注解定义bean⭐️

@configuration注解和@component注解功能相似,但是@configuration注解实现的是单例配置bean

  • 获取bean方式

  • ①通过配置文件,扫描指定包,加载bean

    ②通过注解声明bean,本地的加上@component注解;第三方声明一个配置类,提供一个返回第三方对象的方法(方法名为bean的id名),然后配置为bean即可

image-20230403163520478

image-20230403163626044

  • 缺点:只需要一个配置文件,可否干掉配置文件?
方式三:注解方式声明配置类
  • 声明一个总配置类,然后加上@componentScan注解,扫描要加载bean的包即可

    image-20230403165337560

1.使用FactroyBean接口⭐️
  • 介绍:

    @Bean注解所得到的bean对象不一定是该类本身,如果一个类实现了FactoryBean接口,那么配置到@bean中,返回的对象是其实现FactoryBean接口中的getObject方法返回的对象

  • 工厂模式特点:

    1. 方法返回时工厂bean对象,但是交给Spring容器管理的是bean对象的getObject()方法后的对象
    2. 工厂接口交给Spring容器管理的bean可设置单例或者多例
    3. 只有在自己调用工厂方法时,获取的是工厂对象
  • 实现FactoryBean接口好处:

    可以在给spring配置一个bean的时候,**检查bean对象是否满足一些条件,**或者是给bean初始化一些属性,然后再交给bean管理

  • 使用FactoryBean方法:

    ①实现接口,要注意加上泛型

    ②实现三个方法(分别为真正配置的bean,bean的class类型,交给spring管理的模式)

    ③配置bean注解,交给spring管理

    交给spring一般是单例模式的类

  • 使用FactoryBean举例:

    getBean(“book”)得到的是book对象

    但是如果通过这个配置类调book方法,返回的是BookFactoryBean对象

    也就是说BookFactoryBean会造自己的对象,但是不会送到Spring容器中,只有当外部方法调用的时候会返回BookFactoryBean对象(方法特性)

    image-20230403170454186

2.注解格式导入XML格式配置的bean

@ImportResource注解导入xml注解配置的bean,兼容老项目

  • image-20230403171618380
3.proxyBeanMethods属性(探讨@configuration注解)⭐️

推荐看这篇文章

Spring中Bean的单例和多例简单总结

  • Configuration总结:一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

  • @component注解和@configuration注解相同和区别:✏️

    相同:@component注解默认交给Spring容器管理的bean和@configuration注解一样,都是单例模式的

    不同:

    ①@configuration交给IOC容器管理的最外层bean(比如Animal)是一个代理类对象,不是Animal类对象本身,通过代理类对象调用方法获取的是IOC容器中的对象

    ②@component交给IOC管理的就是本身对象(Animal和Dog),但是获取Animal后调用dog获取的是一个新对象,这里没有做到单例

  • Spring中单例和多例的理解:✏️

    重在向Spring容器中请求获取bean

    单例的话每次获取的bean的对象都是同一个

    多例的话相当于只是将类的类型交给Spring管理,每次请求的bean都是不一样的对象

    Spring中默认是单例,节省内存

  • @component注解和@configuration注解区别

    ① @component注解注释的类交给spring管理是普通的bean(animal,dog),如果再从IOC容器中获取animal对象,这时获取的就是普通的animal对象。调用dog方法获取的就是普通的dog对象,不同与IOC容器的dog对象

    image-20230403174549163

    ② 而@configuration注解注释的类,交给spring管理是cglib生成的代理类对象(这里指Animal对象),因此获取这个代理类对象(Animal)再调用相关方法(这里指dog方法),获取的都是同一个bean,实现了单例

    proxyBeanMethods属性可控制代理类的开关

    image-20230403174823020

image-20230403174838336

  • 一般开发中推荐使用@configuration注解,交给spring容器管理的都是单例配置对象(针对animal这种配置类bean),节省内存

    推荐看代码理解

  • 总结:

    与工厂bean中的单例方法区别:

    工厂bean中如果单例方法返回true,那么这个bean交给IOC容器单例管理,不用区分@component注解;

    而如果要获取这个工厂类,就要根据两个注解区别,来区别交给IOC容器的模式,并且工厂类不会交给spring容器管理

image-20230403172821607

方式四:使用@Import注解注入bean
  • 用处:@import注解常用来导入配置类和第三方class文件,降低了源代码与Spring技术的耦合度

  • 用该注解的时候与其他注解结合情况:

    @Import放入容器的话是单例的,但是如果放入容器的配置类不加@configuration注解,那么通过方法获取到的不是和容器中同一个对象,加了@configuration注解得到和容器中一样。

    比如@import是Animal.class,如果Animal有@configuration注解,那么调用其中的dog方法获取的是同一个bean

  • image-20230403193802119
方式五:编程形式注册bean
  • 必须为annotationConfigApplicationContext类声明,不能用ApplicationContext,ApplicationContext没有相应的注册方法

image-20230404153245215

方式六:导入实现了ImportSelector接口的类⭐️
  • 使用条件:

    @Import(ImportSelector.class),元数据标识位置是使用@Import且调用选择器的类

    根据元数据的判定,来把实现类中返回的Class名称都定义为bean。

  1. 源码常用
  • 功能:各种条件的判定,判定完之后才决定加载不加载bean
  • 属性注释:metadata是元数据,对原位置的描述信息
  • image-20230404154428371
方式七:导入实现了ImportBeanDefinitionRegistrar接口的类

也是实现接口才能注册,然后也是使用@Import注解导入注册bean

  • 接口内实现方法的流程:

    ①先根据元数据判断

    ②生成BeanDefinition对象,也就是要注册的bean

    ③使用注册器注册bean对象

  • 这一个方式相对于方式六,开放了bean的注册方式,权限更大了

    image-20230404155914976

方式八:导入实现了BeanDefinitionRegistryPostProcessor接口的类
  • 还是用@Import注解导入

  • 作用:

    • BeanDefinitionRegistryPostProcessor(注册完之后最后修改),解决优先级问题,高于6.7,低于5最终注册

    • 实现对导入的bean的覆盖的最终裁定,就是说如果有多个重复的bean加载,实现了PostProcessor的实现类中的bean中配置,会做到优先覆盖加载

image-20230404161149209

总结:

image-20230404161530953

1.2 bean的加载控制

对bean进行选择配置,有选择的配置bean

  1. 基于编程式控制

​ 前面import之后的加载方式都能进行选择配置

  • image-20230404163259550

    Class.forName方法来进行控制类的加载

  • image-20230404163558981

  1. 基于注解管理bean⭐️

介绍:@conditional等子注解用来决定是否配置bean

使用:@conditional注解Spring里面提供,但是需要自己写实现类实现接口。

Springboot扩展了@condition注解,有子注解使用,用的时候直接导入Springboot包即可

配置位置:有配置为bean对象的注解的地方都可以加@Conditional条件判断

  • IOC容器中有指定bean才加载image-20230404165256334

    image-20230404165359250

    image-20230404165348009

    类路径下有该Class才加载

    image-20230404165440306

  1. 控制作用:省去无用的配置依赖,spring管理资源更加高效准确

1.3 bean的依赖属性配置管理⭐️(自动配置类的开发)

  1. 本节重点要掌握一种思想,约定大于配置(实现解耦功能)

    • 约定大于配置思想:(业务类+配置类

      没有配置就用默认,有配置就根据属性类导入配置属性进行加载

      举例:比如猫和老鼠动画片类的属性中,如果配置类有值,那么用配置类的,否则用约定好的(默认的)

  2. 业务类和主体bean分开,使用@Import注解实现,如果需要业务类的时候加载为bean

  • 本节实现步骤

    image-20230404174712415

    1. 用业务类中的时候,导入@import类

    2. 将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息,实现解耦

    3. 定义业务功能bean,通常使用**@Import导入,解耦强制加载bean**,增强spring管控bean的能力

    4. 示例代码:

      启动类:

      image-20230627163551378

      业务类

      image-20230627163436615

      配置类

      image-20230627163458238

    5. 总结:

      image-20230405100644483

1.4 自动配置原理⭐️

  • 概述:

    技术集A类似前面的业务类设置类B类似前面的配置类pom.xml坐标类似@Import注解

    第7步开放覆盖接口对应application.yml

    image-20230405100852642

  • 具体注解作用:

    源码重点:

    • 只需要关注自动配置部分
    • 分为两部分讲解,技术集A的加载和设置集B的加载

    image-20230627170258105

    1. @SpringBootApplication -->@EnableAutoConfiguration -->@AutoConfigurationPackage --> @Import(AutoConfigurationPackages.Registrar.class)

      设置当前配置包作为配置包(“com.itiheima”),后序会对当前包进行扫描

    2. @SpringBootApplication -->@EnableAutoConfiguration -->@Import(AutoConfigurationImportSelector.class)✏️

      ①凡是以aware(发现)结尾的接口,作用就是让当前类拥有一个aware前缀的对象,如applicationContextAware接口,一个bean实现了此接口,就可以在该bean中发现applicationContext对象并使用。(简单来说实现aware接口,该类(必须是bean)中就能发现该资源并使用)

      举例:这样原本只能在启动类中获取applicationContext的操作,现在变成在bean类中也能发现该对象并使用

      ②Inordered接口:实现了此接口,就赋予该加载的bean的优先级,这样如果第51个bean依赖第37个bean,那么根据优先级回先去加载第37个bean。

      每个具有功能的bean实现了这个接口,如这个AutoConfigurationImportSelector.class中优先级为最大优先级(枚举) - 1

      DeferredImportSelector接口:延迟选择器接口,里面有一个Group接口,Group接口里面有一个process方法,process方法实现了加载技术集A的配置,调试的话process方法打断点

      image-20230405110717276

      process方法里面的getAutoConfigurationEntry()才是加载核心方法,加载spring.fatories文件(这里会默认加载技术集A(spring-boot-autoconfigure-2.6.11.jar/META-INF/spring.fatories))中的技术,都是以autoConfig结尾)

      到这一步实现了技术集A的加载,下面讲讲设置集B的加载

      image-20230405110813897

      image-20230405110923066

      spring.fatories文件在autoconfigure配置里面

      image-20230405111048388

对应第六、七步,将设置集B默认加载

这里以Redis技术集的加载为例

xxxConfiguration.class --> xxxProperties.class --> application.yml

spring.fatories文件里的配置信息为xxxConfiguration,现在以RedisAutoConfiguration(默认技术集A中有的东西)为例拆解自动配置属性

  1. @ConditionalOnClass(RedisOperations.class)讲解

    衔接上一步的设置技术集A

    先做条件检测,如果环境中包含某某类,才会加载为bean(环境检测,降低spring管理bean的负载)

    **举例1:**假如原来项目中是空的,但是boot默认加载技术集里面有RedisAutoConfiguration,这时候我在pom.xml文件中导入Redis相关坐标,这时候RedisAutoConfiguration中的@Conditional注解就在类路径下识别到了Redis的类,进而这个技术集合就生效了

    **举例2:**RedisAutoConfiguration相当于之前的动画片业务类,而@EnableConfigurationProperties注解所用得类相当于之前猫和老鼠的配置类

  2. @EnableConfigurationProperties(RedisProperties.class)讲解:

    环境满足之后,去加载配置类为bean,配置类又依赖application.yml文件,这样的话一个xxxConfiguration配置类含有配置信息bean

  3. 最后两种方法的讲解:

    将对Redis的对外操作对象配置为bean,当技术中有需要配置条件的时候,直接去IOC容器中获取即可

image-20230405114119895

image-20230405114229147

image-20230627173900755

  • 总结

    1. springboot启动时先加载spring.factories文件中的①org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项,将其中配置所有以xxxAutoConfiguration.class类加载为配置类(这就是加载默认的技术集A)。

      ②要注意这些只是默认技术的配置类,而不是这些技术本身。根据这些配置类的@Conditional注解和pom.xml坐标(jar包)来决定加载具体的技术

    2. 在加载bean的时候,bean对应的类定义上都设置有加载条件,因此有可能加载成功,也可能条件检测失败不加载bean

    3. 对于可以正常加载成bean的类,通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置

    4. 配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置,当然这些配置通常都有默认值。如果没有默认值,就强制你必须配置后使用了

  • image-20230405115046742

一句话总结:

就是springboot加载的时候会全部加载配置类,如果检测到环境中存在环境,那么把配置类交给bean管理,实现自动配置,减轻spring的bean管控负载

1.5 变更自动配置✏️

这个问题是自定义starter的基础

  1. 问题:

    • boot中自定义了一些技术集,但是如果是后来的技术如何装进boot让其自动装配呢?
  2. 解决:

    • 仿照mybatisplus-starter,可以自己在META-INF目录下写一个自己的spring.factories文件,将自己的业务配置类信息加入到这个文件中,最后加入自身jar包坐标,这样boot在启动的时候会扫描全部的spring.factories文件

      然后扫描到mp的文件时会自动加上这个技术集

  3. 自己自定义:

    • 自己在META-INF目录下写一个自己的spring.factories,这样boot在启动的时候会自动加载自己在配置文件spring.factories配置的业务类
  4. 自定义自动配置 + 控制boot自动配置类的加载(根上解决依赖

    排除坐标(只是没有实现,但是配置类还自动加载(默认技术集中的坐标))

  5. 总结:

    从上述配置中可以看出,自动配置也是加载bean的方式

  • 唯一目的:帮开发者实现自定义自动配置类,管控bean的注入

image-20230405121344081

image-20230405121432478

2.自定义starter开发

自动配置学习完后,我们就可以基于自动配置的特性,开发springboot技术中最引以为傲的功能了,starter。其实通过前期学习,我们发现用什么技术直接导入对应的starter,然后就实现了springboot整合对应技术,再加上一些简单的配置,就可以直接使用了。这种设计方式对开发者非常友好,本章就通过一个案例的制作,开发自定义starter来实现自定义功能的快捷添加。

自定义starter就是设置一组bean,然后通过boot的自动配置实现导入该坐标就能自启相关功能

大体开发版块:

  • 自动配置相关
    • 自动配置类(xxxConfig.java)
    • 自动配置需要加载文件(spring.factories)
  • 业务相关
    • 业务类

YL-2-1.案例:记录系统访客独立IP访问次数

​ 本案例的功能是统计网站独立IP访问次数的功能,并将访问信息在后台持续输出。整体功能是在后台每10秒输出一次监控信息(格式:IP+访问次数) ,当用户访问网站时,对用户的访问行为进行统计。

​ 例如:张三访问网站功能15次,IP地址:192.168.0.135,李四访问网站功能20次,IP地址:61.129.65.248。那么在网站后台就输出如下监控信息,此信息每10秒刷新一次。

         IP访问监控
+-----ip-address-----+--num--+
|     192.168.0.135  |   15  |
|     61.129.65.248  |   20  |
+--------------------+-------+

​ 在进行具体制作之前,先对功能做具体的分析

  1. 数据记录在什么位置

    最终记录的数据是一个字符串(IP地址)对应一个数字(访问次数),此处可以选择的数据存储模型可以使用java提供的map模型,也就是key-value的键值对模型,或者具有key-value键值对模型的存储技术,例如redis技术。本案例使用map作为实现方案,有兴趣的小伙伴可以使用redis作为解决方案。

  2. 统计功能运行位置,因为每次web请求都需要进行统计,因此使用拦截器会是比较好的方案,本案例使用拦截器来实现。不过在制作初期,先使用调用的形式进行测试,等功能完成了,再改成拦截器的实现方案。

  3. 为了提升统计数据展示的灵活度,为统计功能添加配置项。输出频度,输出的数据格式,统计数据的显示模式均可以通过配置实现调整。

    • 输出频度,默认10秒
    • 数据特征:累计数据 / 阶段数据,默认累计数据
    • 输出格式:详细模式 / 极简模式

​ 在下面的制作中,分成若干个步骤实现。先完成最基本的统计功能的制作,然后开发出统计报表,接下来把所有的配置都设置好,最后将拦截器功能实现,整体功能就做完了。

image-20230405201938677

YL-2-2.IP计数业务功能开发(自定义starter)

  • 可以看p162理解一下开发规范

原理:就是将设置一个spring.factories文件和自动配置类,让bean随着boot工程加载而加载,顺便导入所有的bean

名称规范:spring官方starterspring-xxx-starter,自定义技术名-spring-boot-starter

安装步骤:

  • 将制作好的starter先安装到maven仓库中
  • 导入相关坐标,使用starter

​ 本功能最终要实现的效果是在现有的项目中导入一个starter,对应的功能就添加上了,删除掉对应的starter,功能就消失了,要求功能要与原始项目完全解耦。因此需要开发一个独立的模块,制作对应功能。

步骤一:创建全新的模块,定义业务功能类

​ 功能类的制作并不复杂,定义一个业务类,声明一个Map对象,用于记录ip访问次数,key是ip地址,value是访问次数

public class IpCountService {
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
}

​ 有些小伙伴可能会有疑问,不设置成静态的,如何在每次请求时进行数据共享呢?记得,当前类加载成bean以后是一个单例对象,对象都是单例的,哪里存在多个对象共享变量的问题。

步骤二:制作统计功能

​ 制作统计操作对应的方法,每次访问后对应ip的记录次数+1。需要分情况处理,如果当前没有对应ip的数据,新增一条数据,否则就修改对应key的值+1即可

public class IpCountService {
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
    public void count(){
        //每次调用当前操作,就记录当前访问的IP,然后累加访问次数
        //1.获取当前操作的IP地址
        String ip = null;
        //2.根据IP地址从Map取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null){
            //如果为null就将ip放进去
            ipCountMap.put(ip,1);
        }else{
            ipCountMap.put(ip,count + 1);
        }
    }
}

​ 通过HttpServletRequest对象,获取ip地址

public class IpCountService {
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
    @Autowired
    //当前的request对象的注入工作由使用当前starter的工程提供自动装配
    private HttpServletRequest httpServletRequest;
    public void count(){
        //每次调用当前操作,就记录当前访问的IP,然后累加访问次数
        
        //1.获取当前操作的IP地址
        String ip = httpServletRequest.getRemoteAddr();
        //2.根据IP地址从Map取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null){
            ipCountMap.put(ip,1);
        }else{
            ipCountMap.put(ip,count + 1);
        }
    }
}

步骤三:定义自动配置类⭐️

​ 我们需要做到的效果是导入当前模块即开启此功能,因此使用自动配置实现功能的自动装载,需要开发自动配置类在启动项目时加载当前功能。

public class IpAutoConfiguration {
    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

​ 自动配置类需要在spring.factories文件中做配置方可自动运行。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.itcast.autoconfig.IpAutoConfiguration

步骤四:在原始项目中模拟调用,测试功能

​ 原始调用项目中导入当前开发的starter

<dependency>
    <groupId>cn.itcast</groupId>
    <artifactId>ip_spring_boot_starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

温馨提示

由于当前制作的功能需要在对应的调用位置进行坐标导入,因此必须保障仓库中具有当前开发的功能,所以每次原始代码修改后,需要重新编译并安装到仓库中。为防止问题出现,建议每次安装之前先clean然后install,保障资源进行了更新。切记切记!!

当前效果

​ 在分页controller方法中使用这个IpCountService.count方法,每次调用分页操作后,可以在控制台输出当前访问的IP地址,此功能可以在count操作中添加日志或者输出语句进行测试。

YL-2-3.定时任务报表开发

定时功能实现:

  • 可以选取第三方技术Quartz实现,也可以选择Spring内置的task来完成此功能,此处选用Spring的task作为实现方案。
  • 定时功能是一种监控功能,时刻监控该工程

步骤一:开启定时任务功能

image-20230406160928265

步骤二:制作显示统计数据功能

​ 定义显示统计功能的操作print(),并设置定时任务,当前设置每10秒运行一次统计数据。

String.format()就是占位符的功能

image-20230406160949231

当前效果

​ 每次调用分页操作后,可以在控制台看到统计数据,到此基础功能已经开发完毕。

YL-2-4.使用属性配置设置功能参数

配置类的设置与使用

​ 由于当前报表显示的信息格式固定,为提高报表信息显示的灵活性,需要通过yml文件设置参数,控制报表的显示格式。

步骤一:定义参数格式

​ 设置3个属性,分别用来控制显示周期(cycle),阶段数据是否清空(cycleReset),数据显示格式(model)

image-20230406162533599

步骤二:定义封装参数的属性类,读取配置参数

​ 为防止项目组定义的参数种类过多,产生冲突,通常设置属性前缀会至少使用两级属性作为前缀进行区分。

​ 日志输出模式是在若干个类别选项中选择某一项,对于此种分类性数据建议制作枚举定义分类数据,当然使用字符串也可以。

image-20230406162242062

步骤三:加载属性类

image-20230406162306613

步骤四:应用配置属性

​ 在应用配置属性的功能类中,使用自动装配加载对应的配置bean,然后使用配置信息做分支处理。

​ 注意:清除数据的功能一定要在输出后运行,否则每次查阅的数据均为空白数据。

image-20230406162441913

image-20230406162502537

当前效果

​ 在web程序端可以通过控制yml文件中的配置参数对统计信息进行格式控制。但是数据显示周期还未进行控制。

YL-2-5.使用属性配置设置定时器参数

无论是什么地方,读取配置文件都应该想法从配置类对象中读取,否则是一种不规范行为

  1. 问题:

    读取属性值应该是从bean中读取(规范),但是这个用@EnableConfigurationProperties(xxx.class)生成的配置bean不规范,从而导致读取属性难以读取

    image-20230627201702018

  2. 解决问题:

    ①@EnableConfigurationProperties不能指定名称,导致@Scheduled注解上无法识别Java对象属性

    ②去掉@EnableConfigurationProperties注解,在配置类上设置@Component注解,在启动类上用@Import注解或@ComponentScan导入注册为bean

​ 在使用属性配置中的显示周期数据时,遇到了一些问题。由于无法在@Scheduled注解上直接使用配置数据,改用曲线救国的方针,放弃使用@EnableConfigurationProperties注解对应的功能,改成最原始的bean定义格式。

image-20230406162753837

步骤一:@Scheduled注解使用#{}读取bean属性值

​ 此处读取bean名称为ipProperties的bean的cycle属性值

@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
}

步骤二:属性类定义bean并指定bean的访问名称

​ 如果此处不设置bean的访问名称,spring会使用自己的命名生成器生成bean的长名称,无法实现属性的读取

@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
}

步骤三:弃用@EnableConfigurationProperties注解对应的功能,改为导入bean的形式加载配置属性类

@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {
    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

当前效果

​ 在web程序端可以通过控制yml文件中的配置参数对统计信息的显示周期进行控制

YL-2-6.拦截器开发

功能:

  • 就是在starter中配置了一个web拦截器功能实现类,记得要加入总配置类中,这样就实现了每次导入starter,就自动加上了web拦截器

具体实现:

  • 继承Servlet的拦截器接口,实现相应的拦截功能
  • 实现WebConfigure的接口,将拦截器注册进web容器
  • 在主项目中导入这个starter的MVCConfig配置类

步骤一:开发拦截器

​ 使用自动装配加载统计功能的业务类,并在拦截器中调用对应功能

image-20230406171805840

步骤二:配置拦截器

​ 配置mvc拦截器,设置拦截对应的请求路径。此处拦截所有请求,用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性,通过配置设置拦截器拦截的请求。

image-20230406171938034

当前效果

​ 在web程序端导入对应的starter后功能开启,去掉坐标后功能消失,实现自定义starter的效果。

​ 到此当前案例全部完成,自定义stater的开发其实在第一轮开发中就已经完成了,就是创建独立模块导出独立功能,需要使用的位置导入对应的starter即可。如果是在企业中开发,记得不仅需要将开发完成的starter模块install到自己的本地仓库中,开发完毕后还要deploy到私服上,否则别人就无法使用了。

YL-2-7.功能性完善——开启yml提示功能

开启yml配置提示

步骤:

  • 在配置类写好文档注释

  • 导入相应jar包,编译之后得到配置提示的json文件放入META-INF目录下就可以了

​ 我们在使用springboot的配置属性时,都可以看到提示,尤其是导入了对应的starter后,也会有对应的提示信息出现。但是现在我们的starter没有对应的提示功能,这种设定就非常的不友好,本节解决自定义starter功能如何开启配置提示的问题。

​ springboot提供有专用的工具实现此功能,仅需要导入下列坐标。

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

​ 程序编译后,在META-INF目录中会生成对应的提示文件,然后拷贝生成出的文件到自己开发的META-INF目录中,并对其进行编辑。打开生成的文件,可以看到如下信息。其中groups属性定义了当前配置的提示信息总体描述,当前配置属于哪一个属性封装类,properties属性描述了当前配置中每一个属性的具体设置,包含名称、类型、描述、默认值等信息。hints属性默认是空白的,没有进行设置。hints属性可以参考springboot源码中的制作,设置当前属性封装类专用的提示信息,下例中为日志输出模式属性model设置了两种可选提示信息。

{
  "groups": [
    {
      "name": "tools.ip",
      "type": "cn.itcast.properties.IpProperties",
      "sourceType": "cn.itcast.properties.IpProperties"
    }
  ],
  "properties": [
    {
      "name": "tools.ip.cycle",
      "type": "java.lang.Long",
      "description": "日志显示周期",
      "sourceType": "cn.itcast.properties.IpProperties",
      "defaultValue": 5
    },
    {
      "name": "tools.ip.cycle-reset",
      "type": "java.lang.Boolean",
      "description": "是否周期内重置数据",
      "sourceType": "cn.itcast.properties.IpProperties",
      "defaultValue": false
    },
    {
      "name": "tools.ip.model",
      "type": "java.lang.String",
      "description": "日志输出模式  detail:详细模式  simple:极简模式",
      "sourceType": "cn.itcast.properties.IpProperties"
    }
  ],
  // 提示
  "hints": [
    {
      "name": "tools.ip.model",
      "values": [
        {
          "value": "detail",
          "description": "详细模式."
        },
        {
          "value": "simple",
          "description": "极简模式."
        }
      ]
    }
  ]
}

总结

  1. 自定义starter其实就是做一个独立的功能模块,核心技术是利用自动配置的效果在加载模块后加载对应的功能
  2. 通常会为自定义starter的自动配置功能添加足够的条件控制,而不会做成100%加载对功能的效果
  3. 本例中使用map保存数据,如果换用redis方案,在starter开发模块中就要导入redis对应的starter
  4. 对于配置属性务必开启提示功能,否则使用者无法感知配置应该如何书写

3.springboot启动流程

掌握springboot如何加载容器,如何配置容器流程,只是加速Spring开发

核心:还是加载spring容器ApplicationContext

image-20230701143100969

  • springboot启动流程 = 初始化数据 + 加载容器

boot启动流程就两步

  • new Application(primarySource):加载配置信息,初始化各种配置对象,还没初始化容器
  • new Application(primarySource).run(args):初始化IOC容器

image-20230408144029417

image-20230408144133123

image-20230408144150747

​ 其实不管是springboot程序还是spring程序,**启动过程本质上都是在做容器的初始化,并将对应的bean初始化出来放入容器。**在spring环境中,每个bean的初始化都要开发者自己添加设置,但是切换成springboot程序后,自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程,加上各种各样的参数设置,使得整体初始化过程显得略微复杂,但是核心本质还是在做一件事,初始化容器。作为开发者只要搞清楚springboot提供了哪些参数设置的环节,同时初始化容器的过程中都做了哪些事情就行了。

​ springboot初始化的参数根据参数的提供方,划分成如下3个大类,每个大类的参数又被封装了各种各样的对象,具体如下:

  • 环境属性(Environment)
  • 系统配置(spring.factories)
  • 参数(Arguments、application.properties)

​ 上述过程描述了springboot程序启动过程中做的所有的事情,这个时候好奇宝宝们就会提出一个问题。如果想干预springboot的启动过程,比如自定义一个数据库环境检测的程序,该如何将这个过程加入springboot的启动流程呢?

​ 遇到这样的问题,大部分技术是这样设计的,设计若干个标准接口,对应程序中的所有标准过程。当你想干预某个过程时,实现接口就行了。例如spring技术中bean的生命周期管理就是采用标准接口进行的。

public class Abc implements InitializingBean, DisposableBean {
    public void destroy() throws Exception {
        //销毁操作
    }
    public void afterPropertiesSet() throws Exception {
        //初始化操作
    }
}

​ springboot启动过程由于存在着大量的过程阶段,如果设计接口就要设计十余个标准接口,这样对开发者不友好,同时整体过程管理分散,十余个过程各自为政,管理难度大,过程过于松散。那springboot如何解决这个问题呢?它采用了一种最原始的设计模式来解决这个问题,这就是监听器模式,使用监听器来解决这个问题。

​ springboot将自身的启动过程比喻成一个大的事件,该事件是由若干个小的事件组成的。例如:

  • org.springframework.boot.context.event.ApplicationStartingEvent
    • 应用启动事件,在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent
  • org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
    • 环境准备事件,当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
  • org.springframework.boot.context.event.ApplicationContextInitializedEvent
    • 上下文初始化事件
  • org.springframework.boot.context.event.ApplicationPreparedEvent
    • 应用准备事件,在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent
  • org.springframework.context.event.ContextRefreshedEvent
    • 上下文刷新事件
  • org.springframework.boot.context.event.ApplicationStartedEvent
    • 应用启动完成事件,在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent
  • org.springframework.boot.context.event.ApplicationReadyEvent
    • 应用准备就绪事件,在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求
  • org.springframework.context.event.ContextClosedEvent(上下文关闭事件,对应容器关闭)

​ 上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。

//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
    public void onApplicationEvent(ApplicationStartingEvent event) {
		//自定义事件处理逻辑
    }
}

​ 按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。

总结

  1. springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器
  2. 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码

监听器

工作机制:

  • 监听器工作模式,可以实现干预boot启动过程,想在boot启动时期哪干预就干预

  • springboot给开发者留了一个Listener接口,如果开发者实现了这个接口(不加泛型的话),那么这个监听器就会在boot启动过程中多个事件都运行

  • 如果实现了带泛型的接口,那么只会在boot执行到特定阶段的时候运行

image-20230701153051185

image-20230701153108796

2023-04-08T10_41_54

2023-04-08T10_39_52

(上下文关闭事件,对应容器关闭)

​ 上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。

//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
    public void onApplicationEvent(ApplicationStartingEvent event) {
		//自定义事件处理逻辑
    }
}

​ 按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。

总结

  1. springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器
  2. 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码

监听器

工作机制:

  • 监听器工作模式,可以实现干预boot启动过程,想在boot启动时期哪干预就干预

  • springboot给开发者留了一个Listener接口,如果开发者实现了这个接口(不加泛型的话),那么这个监听器就会在boot启动过程中多个事件都运行

  • 如果实现了带泛型的接口,那么只会在boot执行到特定阶段的时候运行

[外链图片转存中…(img-XQWL1Ehw-1688197404544)]

[外链图片转存中…(img-SMaCelwL-1688197404545)]

[外链图片转存中…(img-msgJPKMO-1688197404546)]

[外链图片转存中…(img-ojyajChT-1688197404547)]

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值