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;
        }
    }
    
    

    根据上面代码,可以知道拦截器需要利用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("/**");//对所有的路径都进行拦截
        }
    }
    
    

    剩下的的对应的代码为:

    @EnableScheduling //开启定时任务
    @Import({IPProperties.class,SpringMvcConfig.class})
    public class IPConfig {
    
        @Bean
        public IPCounter ipCounter(){
            return new IPCounter();
        }
    }
    

    打印任务的所在的类:

    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清空
            }
        }
    }
    
    

    对应的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;
        }
    }
    
    

通过上面的代码,那么这时候我们就可以在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(){
}

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"
        }
      ]

    }
  ]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值