英文网站建设官网,网页制作属于前端吗,搜狗网站提交,东莞微信网站开发#x1f6a9;本文已收录至专栏#xff1a;Spring家族学习之旅 #x1f44d;希望您能有所收获 一.概述
在使用SpringBoot进行开发的时候#xff0c;我们发现使用很多技术都是直接导入对应的starter#xff0c;然后就实现了springboot整合对应技术#xff0c;再加上一些简… 本文已收录至专栏Spring家族学习之旅 希望您能有所收获 一.概述
在使用SpringBoot进行开发的时候我们发现使用很多技术都是直接导入对应的starter然后就实现了springboot整合对应技术再加上一些简单的配置就可以直接使用了。那什么是Starter呢使用Starter对我们开发有什么好处自定义Starter能对我们有什么帮助呢
(1) 什么是Starter
官方文档给出了如下描述: Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the spring-boot-starter-data-jpa dependency in your project. 概述来说就是当我们想使用某项技术与Spring结合进行使用时很多时候可直接导入该技术的starter而不必再去找该技术所依赖的n个坐标一起cv进去。
例如我们想使用Spring开发web项目,不使用Starter可能需要导入这些坐标:
有没有一种想跑路的感觉此外如果你导的不同jar包之间存在版本不兼容还会产生一系列版本冲突问题。而使用springboot提供的starter只需要导入一个坐标即可包含上面所有的jar包以及自动适配版本。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId
/dependency(2) 为什么要自定义Starter
问题产生
在我们的日常开发工作中经常会有一些独立于业务之外的通用模块在许多场景下都能够用到我们经常将其备份到一个地方下然后如果在某一个工程中需要用这块功能的时候需要将代码硬拷贝到其中重新集成一遍麻烦至极。
问题解决
我们在开发过程为了方便常常会封装各种工具类使我们在项目中能很方便的进行调用。同样的我们也可以将这些可独立于业务代码之外的功通用模块封装成一个个starter复用的时候只需要将其在pom中引用依赖即可SpringBoot为我们完成自动装配简直不要太爽。通过我们自定义的Starter相当于一个大的工具模块导入其他项目能够快速的实现功能的引入与剔除。
常见场景
例如短信发送模块自定义一些sdk使得调用者更加方便使用等等功能。
二.使用示例
(1) 引入
在我们的web项目中例如博客等可能会添加一个记录系统访客IP及访问次数的功能而这个功能模块可以应用到很多的地方。接下来我们一起通过实现这个模块来学习如何自定义Starter,来看看如何做到只需要一个Starter坐标以及简单的yml配置即可在项目中无感引入或摘除这个功能模块。
功能介绍
本案例的功能是统计网站独立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 |
---------------------------实现分析 如何记录访问数据 如上所述我们记录的数据是一个字符串IP地址对应一个数字访问次数的形式此处存储数据我们可以使用java提供的map模型也就是key-value的键值对模型或者具有key-value键值对模型的存储技术例如redis技术。本案例使用map作为实现方案当然你也可以根据需要使用redis作为解决方案。 统计功能运行位置因为每次web请求都需要进行统计我们有若干个接口不可能在每个请求中都手动调用一遍吧因此使用拦截器会是比较好的选择。不过在实现初期先使用调用的形式进行测试等功能完成了再改成拦截器的实现方案。 为了提升统计数据展示的灵活度为统计功能添加配置项。输出频度输出的数据格式统计数据的显示模式均可以通过配置实现调整。 输出频度默认10秒数据特征累计数据 / 阶段数据默认累计数据输出格式详细模式 / 极简模式 A typical Spring Boot starter contains code to auto-configure and customize the infrastructure of a given technology, let’s call that “acme”. To make it easily extensible, a number of configuration keys in a dedicated namespace can be exposed to the environment. Finally, a single “starter” dependency is provided to help users get started as easily as possible. 概述来说就是我们在引入一个starter后可轻松开始使用并且能够在配置文件中设置参数对其实现灵活调整。
如此我们便按照官方文档所推荐用法简单的设计了一个简单starter~
项目整体结构一览 (2) 功能开发
(2.1) 环境搭建
创建一个SpringBoot工程实现本案例相关功能只需要导入如下坐标即可
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId
/dependency(2.2) 统计访问ip次数
功能类的制作并不复杂创建一个业务类声明一个Map对象用于记录ip访问次数key是ip地址value是访问次数。制作统计操作对应的方法每次访问后对应ip的记录次数1。需要分情况处理如果当前没有对应ip的数据新增一条数据否则就修改对应key的值1即可。
因为当前功能模块最终需要导入到其他项目中进行而导入当前功能的项目是一个web项目可以从容器中直接获取请求对象因此获取IP地址的操作可以通过自动装配得到请求对象然后获取对应的访问IP地址。
public class IpCountService {// 1.当前类加载成bean以后是一个单例对象不存在多个对象共享数据的问题// 因此不用设置为static静态变量private MapString, Integer ipCountMap new HashMap();// 2. 从容器中直接获取请求对象Resourceprivate HttpServletRequest httpServletRequest;// 3. 统计ip次数public void count() {System.out.println(----触发统计ip次数方法------);//每次调用当前操作就记录当前访问的IP然后累加访问次数//1.获取当前操作的IP地址String ip httpServletRequest.getRemoteAddr();//2.根据IP地址从Map取值并递增次数ipCountMap.put(ip, ipCountMap.getOrDefault(ip, 0) 1);}
}(2.3) 定义自动配置类
步骤一定义自动配置类
我们需要做到的效果是导入当前模块即可启动模块提供功能因此可以使用自动配置实现功能的自动装载需要我们创建自动配置类在启动项目时加载当前功能。
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤二加载自动配置类
在创建的spring.factories文件对其进行配置使得其变成自动配置类加载。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfigurationcn.guanzhi.autoconfig.IpAutoConfiguration项目加载流程加载模块 - 加载spring.factories文件 - 加载IpAutoConfiguration类 - 加载IpCountService类
我们已经自定义好了一个starter震惊不已经可以导入其他项目中使用了只能功能没开始描述的那么齐全。
(2.4) 在新项目测试功能(终)
步骤一安装到本地
先在自定义Starter项目中用Maven:install一下使得其能重新编译并安装到本地仓库以便我们在其他项目中导入坐标能够获取到该坐标。
步骤二创建测试项目
为了测试功能需要也可以在已有的web项目中进行测试我们再创建一个springboot的web工程。
导入坐标 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency创建测试接口
RestController
public class DemoController {GetMapping(/guanzhi)public void ipDemo() {System.out.println(方法触发成功)}}步骤三导入项目
在调用项目中导入我们自己开发的starter进行使用
dependencygroupIdcn.guanzhi/groupIdartifactIdip-spring-boot-starter/artifactIdversion0.0.1-SNAPSHOT/version
/dependency步骤四进行调用
由于我们开发的功能还不完善暂时需要自己注入ipCountService对象并调用count方法进行使用。
RestController
public class DemoController {Resourceprivate IpCountService ipCountService;GetMapping(/guanzhi)public void ipDemo() {ipCountService.count();System.out.println(方法触发成功);}
}
步骤五效果检验
当我们发送请求调用该方法后能够触发我们在starter中定义count方法因此可以在控制台输出当前访问的IP地址此功能可以在count操作中添加日志或者输出语句进行测试。
可以看到我们已经成功使用了我们自定义Starter中的方法就是功能有点简陋接下来让我们一起逐步完善功能细节吧。
(2.5) 定时打印日志
当前已经实现了在业务功能类中记录访问数据但是具体还没有输出监控的信息到控制台。我们可以控制监控信息每5秒打印1次因此需要使用定时器功能。我们可以选择Spring内置的task来完成此功能。
步骤一开启配置
使用定时任务功能需要在当前项目的总配置中进行开启例如在本项目中我们可以在自动配置类上加上如下注解开启。加载自动配置类时即启用定时任务功能。
EnableScheduling
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤二设置频率
定义一个打印统计访问Ip访问次数的print()方法并设置定时任务使得其每5秒运行一次统计数据也可根据需要修改cron数值。
public class IpCountService {private MapString,Integer ipCountMap new HashMapString,Integer();// 定时任务执行频率Scheduled(cron 0/5 * * * * ?)public void print(){System.out.println( IP访问监控);System.out.println(-----ip-address-------num--);for (Map.EntryString, Integer entry : ipCountMap.entrySet()) {String key entry.getKey();Integer value entry.getValue();System.out.println(String.format(|%18s |%5d |,key,value));}System.out.println(---------------------------);}
}其中关于统计报表的显示信息拼接可以使用各种形式进行此处使用String类中的格式化字符串操作进行。
步骤三检验效果
重新clean然后install一下自定义starter项目然后我们重新启动我们的测试项目再次访问接口。
通过循环打印的日志可以看到我们已经成功的完成了定时打印日志功能。
(2.6) 通过yml设置功能参数 To make it easily extensible, a number of configuration keys in a dedicated namespace can be exposed to the environment 由于我们当前打印日志显示的信息格式是固定为提高报表信息显示的灵活性可以通过yml文件提供一些参数给外界使用者进行灵活的更改以达到想要实现的效果。
步骤一预设参数
假设我们预设置3个属性分别用来控制日志显示周期cycle周期数据是否清空cycleReset数据显示格式model
tools:ip:cycle: 10cycleReset: falsemodel: detail步骤二定义封装参数的属性类读取配置参数
为防止项目组定义的参数种类过多产生冲突通常设置属性前缀会至少使用两级属性作为前缀进行区分。日志输出模式是在若干个类别选项中选择某一项对于此种分类性数据建议制作枚举定义分类数据为了方便使用字符串也可以。注意写文档注释后面有作用
// 指定加载的属性
ConfigurationProperties(prefix tools.ip)
public class IpProperties {/*** 日志显示周期*/private Long cycle 5L;/*** 是否周期内重置数据*/private Boolean cycleReset false;/*** 日志输出模式 detail详细模式 simple极简模式*/private String model LogModel.DETAIL.value;/*** 枚举模式*/public enum LogModel{DETAIL(detail),SIMPLE(simple);private String value;LogModel(String value) {this.value value;}public String getValue() {return value;}}
}步骤三加载属性类
在配置类指定加载上述Bean也可以直接在属性类中加Component注解
EnableScheduling
EnableConfigurationProperties(IpProperties.class)
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤四业务功能调整
接下来我们就可以根据配置的不同属性参数在功能类中进行不同的逻辑处理以实现不同的功能效果。注意清除数据的功能一定要在输出后运行否则每次查阅的数据均为空白数据。
public class IpCountService {private MapString,Integer ipCountMap new HashMapString,Integer();// 使用自动装配加载对应的配置beanResourceprivate IpProperties ipProperties;Scheduled(cron 0/5 * * * * ?)public void print(){// 详细模式日志展示格式if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){System.out.println( IP访问监控);System.out.println(-----ip-address-------num--);for (Map.EntryString, Integer entry : ipCountMap.entrySet()) {String key entry.getKey();Integer value entry.getValue();System.out.println(String.format(|%18s |%5d |,key,value));}System.out.println(---------------------------);// 简洁模式日志展示格式}else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){System.out.println( IP访问监控);System.out.println(-----ip-address-----);for (String key: ipCountMap.keySet()) {System.out.println(String.format(|%18s |,key));}System.out.println(--------------------);}// 阶段内统计数据是否清除if(ipProperties.getCycleReset()){ipCountMap.clear();}}
}步骤五效果展示
我们已经完成了两个属性的动态控制日志打印周期配置稍稍有些不同我们先来看看上述配置好的两个属性是否生效。同样是先clean再install一下然后在我们web测试程序端通过控制yml文件中的配置参数对统计信息进行格式控制。
(2.7) 设置定时器注解参数
我们在使用yml配置属性配置中的显示周期数据时由于无法在Scheduled注解上直接使用属性配置类数据因此我们需要放弃使用**EnableConfigurationProperties**注解对应的功能改成最原始的bean定义格式。
步骤一读取数值
我们还是在Scheduled注解中使用#{}读取bean属性值此处读取名称为ipProperties的bean的cycle属性值
Scheduled(cron 0/#{ipProperties.cycle} * * * * ?)
public void print(){
}步骤二属性类定义bean并指定bean的访问名称
注意如果此处不设置bean的访问名称spring会使用自己的命名生成器生成bean的长名称无法实现属性的读取
// 设置为Bean,并自定义名称便于使用
Component(ipProperties)
ConfigurationProperties(prefix tools.ip)
public class IpProperties {
}步骤三重新读取Bean
为了使用我们自己定义的Bean名称,还需要弃用原来写的EnableConfigurationProperties注解对应的功能改为Import导入bean的形式加载配置属性类.
EnableScheduling
// EnableConfigurationProperties(IpProperties.class)
Import(IpProperties.class)
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤四测试
再次clean然后install我们重新在web程序测试端通过控制yml文件中的配置参数对统计信息的显示周期进行控制查看展示效果
(2.8) 拦截器开发
在之前的使用中我们导入模块后如果想使用这个功能还得自己创建并注入IpProperties对象然后再中调用其count方法如果有很多个方法需要使用那么我们就要cv很多次假如有一天要移除这个功能那么又要一处处寻找删除遗漏了将产生报错显然十分不方便。我们可以考虑开发一个拦截器统一进行处理这样我们就能实现只需导入坐标进行简单的配置即可轻松引入或摘去功能模块。
步骤一开发拦截器
使用自动装配加载统计功能的业务类并在拦截器中调用对应功能
public class IpCountInterceptor implements HandlerInterceptor {Autowiredprivate IpCountService ipCountService;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {ipCountService.count();return true;}
}步骤二配置拦截器
配置mvc拦截器设置拦截对应的请求路径。此处拦截所有请求用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性通过配置设置拦截器拦截的请求。
Configuration
public class SpringMvcConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(ipCountInterceptor()).addPathPatterns(/**);}// 加载拦截器Beanpublic IpCountInterceptor ipCountInterceptor(){return new IpCountInterceptor();}
}步骤三导入拦截器
在配置类中导入我们配置的拦截器
EnableScheduling
//EnableConfigurationProperties(IpProperties.class)
Import({IpProperties.class,SpringMvcConfig.class})
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService() {return new IpCountService();}
}步骤四测试
我们再次启动进行测试可以看到我们注释掉了手动注入调用的代码功能依旧正常执行~ (2.9) 开启yml提示功能
我们在使用springboot的配置属性时都可以看到提示尤其是导入了对应的starter后也会有对应的提示信息出现。但是现在我们自己开发的starter并没有对应的提示功能这就非常的不友好接下来我们一起尝试解决自定义starter功能开启配置提示的问题。
步骤一导入坐标
springboot提供有专用的工具实现此功能仅需要导入下列坐标。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdoptionaltrue/optional
/dependency步骤二重启install
程序重新编译后在META-INF目录中会生成对应的提示文件我们可以拷贝生成出的文件到自己开发的META-INF目录中并对其进行编辑。为了避免产生重复的提示效果我们可以注释掉上述坐标了。
步骤三进行些许配置
打开生成的文件可以看到如下信息。其中groups属性定义了当前配置的提示信息总体描述当前配置属于哪一个属性封装类。properties属性描述了当前配置中每一个属性的具体设置包 含名称、类型、描述、默认值等信息。hints属性默认是空白的没有进行设置。
注意文档中的description都是根据我们之前在配置类中的doc文档注释所自动生成的。
为了更友好的提供效果hints属性可以参考springboot源码中的制作设置当前属性封装类专用的提示信息下例中为日志输出模式属性model设置了两种可选提示信息。
{......hints: [{name: tools.ip.model,values: [{value: detail,description: 详细模式.},{value: simple,description: 极简模式.}]}]
}步骤四测试
同样的我们在测试项目的yml文件中查看效果可以看到与官方基本一致啦。
(3) 整体流程总结
别看我们在上述进行了很多步的开发其实自定义stater的开发在2.4就已经完成了就是创建独立模块然后install到自己的本地仓库中如果需要给别人使用的话还要deploy到私服上。最后在需要使用的项目中导入对应的starter坐标即可。
总体流程概括来说就是 创建一个功能模块按照需求导入坐标并实现功能。√必须 创建一个自动配置类加载功能类(Service)然后再spring.factories中配置自动配置类。√必须 完成上述两步我们的自定义Starter工作就算完成了install到本地仓库后就能通过导入坐标在其他项目使用了只是功能十分简陋后续我们便是在不断的完善它。 为了能让我们灵活的控制功能模块我们可以通过读取yml配置属性对外暴露一些参数设置以供外界进行调整。√非必须 我们都不可能一直记住配置的每个属性作用更何况别人况且没有提示极易写错因此我们通过设置开启了yml配置提示功能。√非必须
如此我们便算是简单的完成了一个Starter的开发是不是没有想象中的那么困难
三.相关说明
(1) starter命名规范 All official starters follow a similar naming pattern; spring-boot-starter-*, where * is a particular type of application. This naming structure is intended to help when you need to find a starter. The Maven integration in many IDEs lets you search dependencies by name. For example, with the appropriate Eclipse or Spring Tools plugin installed, you can press ctrl-space in the POM editor and type “spring-boot-starter” for a complete list. third party starters should not start with spring-boot, as it is reserved for official Spring Boot artifacts. Rather, a third-party starter typically starts with the name of the project. For example, a third-party starter project called thirdpartyproject would typically be named thirdpartyproject-spring-boot-starter. 概述来说就是
为了查找方便官方提供的starter命名格式基本都是spring-boot-starter-xxx因此不建议我们也使用这种命名格式。它推荐我们使用形如xxx-spring-boot-starter的格式进行命名。
例如我们在上述案例中自定义的Starter groupIdcn.guanzhi/groupIdartifactIdip-spring-boot-starter/artifactIdversion0.0.1-SNAPSHOT/version(2) 参数前缀命名 If your starter provides configuration keys, use a unique namespace for them. In particular, do not include your keys in the namespaces that Spring Boot uses (such as server, management, spring, and so on). If you use the same namespace, we may modify these namespaces in the future in ways that break your modules. As a rule of thumb, prefix all your keys with a namespace that you own (for example acme). 概述来说就是
在为我们暴露给外界读取设置的参数前缀进行命名时必须确保其唯一性否则SpringBoot在启动时可能会修改这些名称导致一些不可预知的错误。
例如在上述案例中为了防止意外我们使用了两级前缀和自己项目名以示区别
// 参数设置
tools:ip:cycle: 10cycleReset: falsemodel: detail// 指定加载的属性前置
ConfigurationProperties(prefix tools.ip)(3) yml提示相关 Make sure that configuration keys are documented by adding field javadoc for each property。 概述来说就是
我们在上述开启yml提示功能之后可以看到输入部分数值后不但会联想配置参数参数后面还有一些相关说明这些其实都是因为我们在配置参数类中使用了doc文档注释所生成的描述。例如
// 指定加载的属性
ConfigurationProperties(prefix tools.ip)
public class IpProperties {/*** 日志显示周期*/private Long cycle 5L;/*** 是否周期内重置数据*/private Boolean cycleReset false;}输入/**再按回车即可快捷生成相应注释格式。
此外官方给出的一些命名或描述相关建议可以根据需要进行遵守。