自定义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"
}
]
}
]
}