SpringBoot 自定义 Starter

SpringBoot 自定义 Starter

捡破烂的诗人 540 2022-07-10
  • 联系方式:1761430646@qq.com
  • 菜狗摸索,有误勿喷,烦请联系

1. 自定义starter

1.1 前言

1.2 要求

  • 做一个系统访客独立 IP 访问次数的自定义starter

  • 并在后台每10 秒输出一次监控信息

  • 业务分析

    • 数据记录中介:Map / Redis
    • 功能触发位置:每次请求
    • 配置项:
      • 输出频度,默认 10s 一次
      • 数据特征:累计 / 阶段,默认为累计
      • 输出格式:详细 / 极简
    • 其他:
      • yml 的配置提示功能

1.3 功能实现

1.3.1 项目搭建

  • 由于这个功能比较简单,只牵扯到 web 场景,所以正常搭建 SpringBoot 项目后导入一个web依赖即可

    1-1657432383431

  • 切记把 SpringBoot 自带的 maven 构建依赖给删掉,要不然会出大错

1.3.2 访问 IP 计数功能实现

  1. 二话不说,先创建对应的业务接口以及抽象方法,并添加实现类

    2-1657432383426

  2. 由于 IP 的获取我们需要通过HtttpServerletRequest对象获取,现在没有,就得通过注解@Autowried注入一个,并且 IP 统计的存储是计划使用Map(可以使用Redis等其他实现,这里重点是stater的自定义过程,就使用Map来简化了),得手动创建一个

    2-1657432383426

    • 这里的 Map 可能有线程安全问题,最好是通过注入的形式来获取,这里为了简化才直接new出来的
  3. 访问 Ip 统计业务实现,这个比较无脑,直接拿并存就好

    3-1657432383425

  4. 到这里其实 IP 计数业务接口已经写好,后面是自定义starter的关键了

  5. 创建一个IpCountAutoConfiguration配置类,并加以配置

    4-1657432385759

  6. resources目录下创建META-INF目录,然后创建spring.factories文件,并把我们刚刚的自动配置类的全路径类名作为keyorg.springframework.boot.autoconfigure.EnableAutoConfigurationvalue

    5-1657432385772

  • 这里是自定义starter的关键,切记切记

  • 现在其实一个最基本的实现 IP 统计的自定义starter已经做出来,通过Maven工具的先cleaninstall操作,我们即可在本地的 Web 项目开发中导入此starter,直接在Controller层注入IpCountServicebean,调用方法便可自动增加了访问 IP 统计的功能

1.3.3 展示 IP 访问数据功能实现

  1. 照仿上一小节的功能实现,我们先创建对应的业务接口以及其实现类,但是这里为了简化(–偷个懒)并且由于存储 IP 的容器由于不是注入的,如果是新建业务接口类的话是拿不到,所以这里直接在上一节的已有接口中增加展示 IP 访问数据功能的实现

    6-1657432385774

  2. 接着就是 IP 数据展示的功能实现了,其实也挺无脑的,直接拿出来并打印在控制台即可,最重要的是格式的控制

    7-1657432388376

  • 好了,IP 数据展示功能接口已经做完了

1.3.4 配置拦截路径

  1. 其实这个配置拦截路径可以不用实现,但是为了方便当别人使用到我们自定义的starter时,无需手动注入IpCountService bean,再通过调用count()方法再来实现 IP 统计功能的实现,而是为了导入依赖后啥都不用干就有此功能的快捷,在这里我们还是给它下配置拦截路径(使用者快乐就好)

  2. 下面分析下怎么做

  3. 对于以前我们使用Serverlet开发的项目,如果进行 IP 统计的话,通常都是在filter中利用AOP的思想做

  4. 在这里的自定义starter项目中,我们也可以通过实现filter来做,当然也可以通过interceptor来做,本次我们就通过interceptor来搞

  5. 新建一个专门存放interceptor的包,并在其中新建关于 IP 的interceptor类,实现HandlerInterceptor接口,重写preHandle方法

    8-1657432388359

  6. 注入IpCountServicebean,调用其count()方法实现 IP 统计功能

    9-1657432388354

  7. 创建一个实现WebMvcConfigurer接口的SpringMvc配置类,并在其中配置刚刚的拦截器加载到容器中,且设置拦截路径–这一小节最重要的一点,属于SpringBoot如何配置Interceptor的内容)

    10-1657432390757

    • 注意使用@Configuration标注此类
  8. 切记最后要在自动配置类中通过@Import导入刚刚的SpringMvc配置类,要不然这个功能以及刚刚做的一大堆功夫就相当于YY了

    11-1657432390736

1.3.5 定时数据日志功能实现

  1. 对 IP 进行统计后的数据得进行定时展示,有利于使用者监控,在这里我们就是用简单的控制台输出即可

  2. 实现很简单

  3. 首先在自动配置类中开启SpringBoot的定时展示功能

    12-1657432390720

  4. 在数据展示的功能接口上通过cron表达式配置数据显示周期,在这里即为在show()方法上配置(先默认设置每 5 秒进行一次数据展示)

    13-1657432393079

  5. 完事

1.3.6 配置值动态获取实现

  1. 对于上述功能的实现,我们发现我们都是通过默认值来控制其数据显示周期,数据展示格式,永久不周期内重置数据,拦截路径固定死等

  2. 这样就太死板了,所以我们得做一个属性配置类,为使用者提供一个接口来动态调整

  3. 抽取所有可调属性,重新构造成一个新类

    14-1657432393062

    具体内容如下

    @Component("ipCountProperties")
    @ConfigurationProperties(prefix = "tools.ip")
    public class IpCountProperties {
        /**
         * 日志显示周期
         */
        private long cycle = 10L;
        /**
         * 是否周期内重置日志
         */
        private boolean cycleReset = false;
        /**
         * 日志输出模式  detail:详细模式  simple:极简模式
         */
        private String model = LogModel.DETAIL.value;
    
        /**
         * 拦截路径
         */
        private String interceptorPath = "/**";
        public enum LogModel {
            /**
             * 详细日志模式
             */
            DETAIL("detail"),
            /**
             * 简单日志模式
             */
            SIMPLE("simple");
            private String value;
    
            private LogModel(String value) {
                this.value = value;
            }
    
            public String getValue() {
                return value;
            }
    
            public void setValue(String value) {
                this.value = value;
            }
        }
    
        public long getCycle() {
            return cycle;
        }
    
        public void setCycle(long cycle) {
            this.cycle = cycle;
        }
    
        public boolean isCycleReset() {
            return cycleReset;
        }
    
        public void setCycleReset(boolean cycleReset) {
            this.cycleReset = cycleReset;
        }
    
        public String getModel() {
            return model;
        }
    
        public void setModel(String model) {
            this.model = model;
        }
    }
    
    • 注意:在这里通过@ConfigurationProperties配置其属性值前缀,通过@Component来加载此对象到 Spring 容器中,至于为什么不是通过@EnableConfigurationProperties来加载,这里面会有个坑,后面再讲
  4. 接下来就是在所有可以用到这些属性值的地方注入此bean,然后动态设置值

    15-1657432393045

    16-1657432395455

  1. 切记,还得在自动配置类手动导入这个IpCountProperties类,因为我们默认没有开启扫描当前包功能,不主动导入就相当于做了个寂寞

    17-1657432395435

  2. 完事

1.4 配置 yml 时提示功能实现

  1. 现在通过配置文件进行动态调值时,会发现配置时不会像别人那样自动弹出提示,虽然说我们也知道有哪些值可以配置,它们代表什么含义,但是使用者可不知道

    18-1657432395420

  2. 当然也可以建立相对应的starter配置文档,但是在配置属性时有一个提示功能无疑会让使用者感觉舒服很多

  3. 下面就进行提示功能的实现

  4. 它是通过一个配置文件来实现的,在抽取属性成为一个类时,Idea 也在冒出一个提示

    19-1657432397855

  5. 虽然不知道为什么会有这个提示出现,但是点击Open Ducementation,会发现进入到SpringBoot的官方文档里,那提示我们要加载一组依赖

  6. 而本次的提示功能实现也是需要这组依赖的,所以先手动导入其对应依赖

    21

  7. 然后Maven工具的先cleaninstall命令,会发现编译后的META-INF目录会多生成一个名为spring-configuration-metadata.json的文件(如果不加入上述依赖,通过Maven工具运行同样的命令,是不会有这个文件生成的,你们可以试试)

    22

  8. 然后我们拷贝一份spring-configuration-metadata.json文件到我们的META-INF目录下,并把源文件给删掉,以及上面导入的这组依赖给取消掉

    23

  9. 打开这个spring-configuration-metadata.json文件,我们会发现这是由我们之前配置的IpCountProperites类的属性注释信息构成的

    24

  10. 现在在yml文件里配置属性,就会有提示功能了

    25

  11. 对于model属性,也即输出模式的选定,其值是通过枚举类是枚举的,在别人开发的starter中,对于枚举的属性值的配置,会有一个专门的提示功能,但是现在我们没有

    26

  12. 下面来做这个枚举的提示功能,其实也很简单,配置文件的提示功能归根到底是spring-configuration-metadata.json文件中的内容生成的,我们只需进到这个文件,找到hints区域进行值的配置即可

    27

  13. 配置完后就可以了,效果马上出来了

    28

  14. 完事

  • 通过Maven工具的先cleaninstall操作,将此模块导入我们的Maven仓库

1.5 实践

  1. 找到一个 Web 项目

  2. 加入我们刚刚自定义starter的依赖

    29

  3. 启动测试,会发现我们做的 IP 统计功能就直接有了

    30

  4. 在配置文件中也可以看到对应的配置提示信息

    31

1.6 老鼠坑

  1. 在上面的工作中,我们把xxxProperties的实例对象加载到bean容器是通过@Component(名称)注解以及在自动配置类中通过@Import导入此xxxProperties类生成的

  2. 那时就留了个疑问,为什么不是使用@EnableConfigurationProperties搭配呢?官方也是这样开发的呀

  3. 这主要是考虑到我们的业务特点,因为对于定时周期的取值,我们是通过#{bean名称.属性}取出来的

    32

  4. 但是对于使用@EnableConfigurationProperties注册 bean 的名称,是由一个专门的 bean名称生成器来生成的,也就是不受我们的管控,通过这个注解注册的 bean 的名称格式可以通过下面一个例子得出

    33

  5. 总结一下,其生成的名称格式为属性匹配前缀-全路径类名,这也是最重要的一点,因为#{bean名称.属性}是由.作为分割的,那么对于上述例子,SpringBoot 就会认为person才是这个bean的名称,后面的是它的属性值(可能是个对象套娃,所以会有多个.)

  6. 所以所以,如果通过@EnableConfigurationProperties注册此xxxPropertiesbean的话,我们是拿不到值的,所以只能通过@Component去固定这个 bean 的名称方便我们取值

  7. 这也告诉我们业务需求第一,不一定要遵守开发习惯


# SpringBoot # 自定义 Starter