• SpringBoot学习_day7


    自定义starter

    通过观察,如果需要自定义starter的时候,那么它的名字应该是:

    • 如果是第三方技术的话,那么是 技术名-spring-boot-starter
    • 如果是springboot内部支持的技术时,那么应该是 spring-boot-starter-技术名

    那么我们需要自定义starter的时候,那么我们可以新建maven1,这时候,我们需要将这个新建的maven1导入到另一个maven2项目,那么这时候在maven2中就可以使用到了maven1中的类了。但是在spring boot中由于我们导入了坐标之后,就可以直接从spring容器中获得对应的bean,那么这时候我们需要在maven1中使用自动配置,即在resources包下面创建META-INF/spring-factories文件,然后设计org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,这样就可以实现自动配置。通过这样操作,我们就可以在maven2中直接从spring容器中取出对应的bean了,而不需要我们新建。此时我们就已经实现了自定义的starter了。

    所以我们在利用自定义的ipCounter-spring-boot-starter来统计ip的访问次数,基于上面day3中的ssmp进行简单开发,那么我们可以设计统计ip的统计次数。

    那么要统计ip的访问次数,我们可以利用map、redis来存放ip以及它的访问次数,并在控制台中打印出来。这时候我们还增加了要间隔多久才打印一次,并且打印的模式等。所以再次涉及到了下面内容:

    • 因为要涉及间隔多久打印一次,那么需要我们利用定时任务来实现,而要实现定时任务,首先我们需要在自定义的starter中利用注解@EnableScheduling来开启定时任务,然后在print这个方法上面使用注解@Scheduled来控制定时任务要间隔多久执行这个方法

    • 需要设计打印模式,也即我们需要设计打印的一些属性,例如要间隔多久打印一次,打印的模式,以及是否采用累计模式的打印,所以我们需要定义一个IPProperties类,用于设计打印的属性,然后可能会利用注解@EnableConfigurationProperties@ConfigurationProperties来设置bean的依赖配置。

    • 因为要访问某一项功能的时候,也是需要统计这个ip的,那么这时候就需要添加拦截器,在执行某一项功能之前进行拦截,来统计这个ip的访问次数,此时我们应该将拦截器添加在maven2还是添加在maven1呢?首先我们先看一下拦截器的代码:

      public class IPCounterInterceptor implements HandlerInterceptor {
          /*
          因为这个interceptor不是一个bean,所以需要通过构造方法
          来初始化这个属性值
           */
          private IPCounter ipCounter;
      
          public IPCounterInterceptor(IPCounter ipCounter){
              this.ipCounter = ipCounter;
          }
      
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              System.out.println("执行拦截操作...............");
              ipCounter.count();
              return true;
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      根据上面代码,可以知道拦截器需要利用maven1中的类IPCounter,那么这时候如果maven2中写拦截器代码,并且在maven2中如果不再需要统计ip的访问次数的时候(也即maven2并没有导入maven1的坐标),那么这时候就会因为找不到IPCounter类而发生报错,或许可以通过删除这个拦截器来修正,但是这时候就需要修改maven2中的代码,显然不是最优解。所以是在maven1中添加拦截功能,当我们添加了maven依赖之后,自然也添加了拦截器的功能,同理,当我们不需要导入maven1的时候,也删除了这个拦截功能。在上面写了拦截器的代码之后,我们需要将拦截器添加到spring中,这时候对应的代码为:

      public class SpringMvcConfig implements WebMvcConfigurer {
      
          @Autowired //要保证SpringMvcConfig是一个bean,这时候需要在IPConfig中利用注解@Import来导入这个bean
          private IPCounter ipCounter;
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              //添加自己定义的拦截器
              registry.addInterceptor(new IPCounterInterceptor(ipCounter))
                      .addPathPatterns("/**");//对所有的路径都进行拦截
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      剩下的的对应的代码为:

      @EnableScheduling //开启定时任务
      @Import({IPProperties.class,SpringMvcConfig.class})
      public class IPConfig {
      
          @Bean
          public IPCounter ipCounter(){
              return new IPCounter();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      打印任务的所在的类:

      public class IPCounter {
      
          private HashMap<String,Integer> map = new HashMap<>();
      
          @Autowired
          private HttpServletRequest request;
      
          public void count(){
              /*
              读取对应的ip地址,并且统计它的访问次数
              这时候可以通过map或者redis来实现,而需要获取
              参数ip地址,那么可以通过HttpServletRequest来获取
              这时候从spring容器中取出即可,但是要从spring容器中
              取出,需要保证当前这个类也是一个bean,所以需要自动配置
               */
              System.out.println("ip is counting.........");
              String ip = request.getRemoteAddr();
              map.put(ip, map.getOrDefault(ip, 0) + 1);
      
          }
      
          @Autowired
          private IPProperties ipProperties;
      
         @Scheduled(cron = "0/5 * * * * ?") //设置定时任务是每个5秒就打印
          public void print(){
              System.out.println("----------ip统计---------");
              for(Map.Entry<String,Integer> entry : map.entrySet()){
                  if("detail".equals(ipProperties.getMode())){
                      System.out.println(entry.getKey() + ",      count = " + entry.getValue());
                  }else if("simple".equals(ipProperties.getMode())){
                      System.out.println(entry.getKey());
                  }
              }
              System.out.println("-------------------------");
      
              if(ipProperties.getCycle_reset()){
                  map.clear();//如果不是累计统计ip,那么需要将map清空
              }
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      对应的IPProperties代码为:

      /*
      值得注意的是,要想使用注解@ConfigurationProperties,需要保证这个类是一个bean,否则就会发生错误
      而这里之所以没有使用@Component等注解来将这个类添加到bean中,是因为在IPConfig这个类中已经利用了
      注解@EnableConfigurationProperties,当加载了IPConfig这个类的时候,就会自动将这个注解中的类添加
      到spring容器中了,而同样的,如果我们已经在IPConfig类中使用了@EnableConfigurationProperties(IPProperties.class)注解,那么这时候必须要在IPProperties类中
      使用注解@ConfigurationProperties来进行属性绑定,否则就会发生报错。
      */
      @ConfigurationProperties(prefix = "tools.ip")
      public class IPProperties {
          /**
           * 间隔时间,每间隔多久就打印
           */
          private Long cycle = 5L;
      
          /**
           * 是否是累计,还是重新刷新,然后打印
           * false表示的是累计
           */
          private Boolean cycle_reset = false;
      
          /**
           * 设置打印的模式
           * detail以及simple
           */
          private String mode = PrintModel.DETAIL.value;
      
          private enum PrintModel{
              DETAIL("detail"),
              SIMPLE("simple");
              private String value;
              PrintModel(String value){
                  this.value = value;
              }
              public String getValue(){
                 return this.value;
              }
          }
      
          public Long getCycle() {
              return cycle;
          }
      
          public void setCycle(Long cycle) {
              this.cycle = cycle;
          }
      
          public Boolean getCycle_reset() {
              return cycle_reset;
          }
      
          public void setCycle_reset(Boolean cycle_reset) {
              this.cycle_reset = cycle_reset;
          }
      
          public String getMode() {
              return mode;
          }
      
          public void setMode(String mode) {
              this.mode = mode;
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63

    通过上面的代码,那么这时候我们就可以在maven2项目中的配置文件中来着tools.ip的属性了,但是这时候还没有解决间隔多久就打印问题,@Scheduled(cron = "0/5 * * * * ?")仅仅表示每间隔5秒就执行一次,那么我们将如何实现间隔多久就打印呢?

    首先我们可以尝试着通过@Value的方式来读取配置文件的内容,所以应该是@Scheduled(cron = "0/@{tools.ip.cycle} * * * * ?")来实现,但是这样会有一个弊端,那就是如果我们没有在配置文件中配置tools.ip.cycle的值,那么这时候就会发生报错,因为没有办法找到这个属性。所以我们需要利用条件表达式,如果配置文件中存在tools.ip.cycle的值,那么就是用它的值,否则就采用默认值,所以改成了@Scheduled(cron = "0/@{tools.ip.cycle:5} * * * * ?")。但是这样的话,那么我们就没有必要在IPProperties这个类中设置属性cycle了。所以这种方式并不可取。

    那么这时候我们需要通过@Scheduled(cron = "0/#{beanId.属性} * * * * ?")来实现,如果是通过@EnableConfigurationProperties(IPProperties.class)来将IPProperties这个类添加到spring容器中,所以它的beanId就是tools.ip-cn.itcast.properties.IPProperties,也即绑定的配置文件的属性-IPProperties类的全路径名.

    但是如果我们这样写的话,当运行的时候,就会发生了报错,提示EL1008E: Property or field 'tools' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?,因为他将tools当作是beanId了,从而发生了报错。

    所以解决办法是我们直接在IPProperties类的上方利用注解@Component来将其添加到spring容器中,并设置它的beanId,这时候就不需要在IPConfig类上面使用注解@EnableConfigurationProperties了。

    但是这样依旧是有问题的,因为尽管使用了注解@Component,但是没有进行组件扫描,所以我们还需要在IPConfig类中使用注解@Import(IPProperties.class)添加到spring容器中,或者利用注解@ComponentScann进行组件扫描(仅仅利用@Import,而没有在IPProperties中使用注解@Component,虽然也可以将其添加到spring容器中,但是这时候它的beanId是它的全路径名cn.itcast.properties.IPProperties.如果是这样写的话@Scheduled(cron = "0/#{cn.itcast.properties.IPProperties.cycle} * * * * ?"),同样会发生报错,提示EL1008E: Property or field 'cn' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?,原因是将cn看作是beanId了)。所以这就是为什么我们还需要在IPProperties类上方使用注解@Component来设置它的beanId。

    所以对应的代码为:

    @EnableScheduling //开启定时任务
    @Import(IPProperties.class)
    public class IPConfig {
       .....   
    }
    
    
    @Component("ipProperties")
    @ConfigurationProperties(prefix = "tools.ip")
    public class IPProperties {
        ......
    }
    
    @Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?") //设置定时任务是每个5秒就打印
    public void print(){
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    springboot项目开启yml提示功能

    如果我们需要实现在yml文件中开启提示功能,那么我们需要可以先导入坐标spring-boot-configuration-processor坐标,然后点击maven-> 找到对应的maven项目 -> Lifecycle -> 双击compile,运行完之后就可以在target/classes/META-INF包下面看到一个spring-configuration-metadata.json文件,将这个文件复制到resources/META-INF包下面即可,然后删除spring-boot-configuration-processor依赖即可。对应的spring-configuration-metadata.json文件的代码为:

    {
      "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": "是否是累计,还是重新刷新,然后打印 false表示的是累计",
          "sourceType": "cn.itcast.properties.IPProperties",
          "defaultValue": false
        },
        {
          "name": "tools.ip.mode",
          "type": "java.lang.String",
          "description": "设置打印的模式 detail以及simple",
          "sourceType": "cn.itcast.properties.IPProperties"
        }
      ],
        /*
        如果直接赋值的时候,那么hints的内容是空的,即"hints": []
        所以我们需要将它的值添加,这样如果我们在配置文件上写tools.ip.mode的时候,就可以给出它的值
        对应得形式如下所示
        */
      "hints": [
        {
          "name": "tools.ip.mode",
          "values": [
            {
              "value": "detail"
            },
            {
              "value": "simple"
            }
          ]
    
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    剑指 Offer 04. 二维数组中的查找
    JavaScript之运算符相关知识
    C多维数组指针(学习笔记)
    python读取word详解【from docx import Document】
    2015年经过官方去噪的NPP/VIIRS数据
    JAVA中的属性、方法、构造器,你真的弄懂了吗?
    Chapter8 : Predicting Residence Time of GPCR Ligands with Machine Learning
    Java实现俄罗斯方块
    第四个专栏,Kubernetes云原生实战,它来了~
    pandas使用read_csv函数读取csv数据、设置parse_dates参数将csv数据中的指定字段数据列解析为时间日期对象
  • 原文地址:https://blog.csdn.net/weixin_46544385/article/details/126696229