【SpringBoot】自定义Starter
🚩本文已收录至专栏: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包以及自动适配版本。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-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工程,实现本案例相关功能只需要导入如下坐标即可
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-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 Map<String, 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.EnableAutoConfiguration=cn.guanzhi.autoconfig.IpAutoConfiguration
项目加载流程:加载模块 -> 加载spring.factories
文件 -> 加载IpAutoConfiguration
类 -> 加载IpCountService
类
我们已经自定义好了一个starter!震惊不?已经可以导入其他项目中使用了,只能功能没开始描述的那么齐全。
(2.4) 在新项目测试功能(终)
步骤一:安装到本地
先在自定义Starter项目中用Maven:install一下,使得其能重新编译并安装到本地仓库,以便我们在其他项目中导入坐标能够获取到该坐标。
步骤二:创建测试项目
为了测试功能需要(也可以在已有的web项目中进行测试),我们再创建一个springboot的web工程。
导入坐标:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
创建测试接口:
@RestController
public class DemoController {@GetMapping("/guanzhi")public void ipDemo() {System.out.println("方法触发成功")}}
步骤三:导入项目
在调用项目中导入我们自己开发的starter进行使用
<dependency><groupId>cn.guanzhi</groupId><artifactId>ip-spring-boot-starter</artifactId><version>0.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 Map<String,Integer> ipCountMap = new HashMap<String,Integer>();// 定时任务执行频率@Scheduled(cron = "0/5 * * * * ?")public void print(){System.out.println(" IP访问监控");System.out.println("+-----ip-address-----+--num--+");for (Map.Entry<String, 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 Map<String,Integer> ipCountMap = new HashMap<String,Integer>();// 使用自动装配加载对应的配置bean@Resourceprivate 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.Entry<String, 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提供有专用的工具实现此功能,仅需要导入下列坐标。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</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 pressctrl-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 calledthirdpartyproject
would typically be namedthirdpartyproject-spring-boot-starter
.
概述来说就是:
为了查找方便,官方提供的starter
命名格式基本都是spring-boot-starter-xxx
,因此不建议我们也使用这种命名格式。它推荐我们使用形如xxx-spring-boot-starter的格式进行命名。
例如我们在上述案例中自定义的Starter
<groupId>cn.guanzhi</groupId><artifactId>ip-spring-boot-starter</artifactId><version>0.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 exampleacme
).
概述来说就是:
在为我们暴露给外界读取设置的参数前缀进行命名时,必须确保其唯一性,否则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;}
输入/**+再按回车即可快捷生成相应注释格式。
此外官方给出的一些命名或描述相关建议可以根据需要进行遵守。
相关文章:
【SpringBoot】自定义Starter
🚩本文已收录至专栏:Spring家族学习之旅 👍希望您能有所收获 一.概述 在使用SpringBoot进行开发的时候,我们发现使用很多技术都是直接导入对应的starter,然后就实现了springboot整合对应技术,再加上一些简…...
【C陷阱与缺陷】----语法陷阱
💯💯💯 要理解一个C程序,必须理解这些程序是如何组成声明,表达式,语句的。虽然现在对C的语法定义很完善,几乎无懈可击,大门有时这些定义与人们的直觉相悖,或容易引起混淆…...
虹科分享| 关于TrueNAS十问十答
上一篇文章我们向您介绍了虹科新品HK-TrueNAS企业存储,很多小伙伴会疑问到底什么是NAS存储,之前常用的磁盘、磁带属于什么存储架构,NAS存储好在哪里,什么时候使用NAS?今天我们整理了关于TrueNAS的十问十答,…...
Https 笔记
HTTP TLS TLS 的前身是 SSL 非对称加密的核心: 两个密钥(公私) https 需要第三方CA(证书授权中心)申请SSL证书以确定其真实性 证书种包含了特定的公钥和私钥 密钥交换 自己将私钥上锁后发给对方对方也上锁 在还回来…...
【Python+requests+unittest+excel】实现接口自动化测试框架
一、框架结构: 工程目录 二、Case文件设计 三、基础包 base 3.1 封装get/post请求(runmethon.py) 1 import requests2 import json3 class RunMethod:4 def post_main(self,url,data,headerNone):5 res None6 if heade…...
MySQL终端的使用及其数据类型的使用
什么是数据库?数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据。我们也可以将数据存储在文件中,…...
长视频终局:一场考验资金储备的消耗战
赢者通吃,似乎已成为各行各业的常识,但事实真的是这样吗?20世纪70年代,石油价格高涨,在墨西哥湾油田拍卖中高价拍得油田的企业,要么亏损,要么收入低于预期,但仍然有无数企业在高价竞…...
javaEE初阶 — CSS 常用的属性
文章目录CSS 常用的属性1 字体属性1.1 设置字体家族 font-family1.2 设置字体大小 font-size1.3 设置字体粗细 font-weight1.4 文字倾斜 font-style2 文本属性2.1 文本颜色2.2 文本对齐2.3 文本装饰2.4 文本缩进2.5 行高3 背景属性3.1 背景颜色3.2 背景图片3.3 背景位置3.4 背景…...
【面试题】如何取消 script 标签发出的请求
大厂面试题分享 面试题库前后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库问题之前在业务上有这样一个场景,通过 script 标签动态引入了一个外部资源,具体方式是这样的const script document.…...
蓝桥杯嵌入式(G4系列):RTC时钟
前言: 关于RTC时钟的HAL库配置我也是第一次,之前都是用库函数的写法,这里写下这篇博客来记录一下自己的学习过程。 STM32Cubemx配置: 首先点击左侧的Timers的RTC,勾选以下选项 进入时钟树配置 进入时间设置࿰…...
Linux——进程间通信1
目录 进程间通信目的 进程间通信标准 管道 匿名管道 管道实现进程间通信 管道的特点 进程池 ProcessPool.cc Task.hpp 习题 进程间通信目的 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 通知事件…...
循环语句——“Python”
各位CSDN的uu们你们好呀,今天小雅兰的内容是Python中的循环语句呀,分为while循环和for循环,下面,让我们进入循环语句的世界吧 循环语句 while循环 for循环 continue和break 循环语句小结 人生重开模拟器 设置初始属性 设置性别…...
Python synonyms查找中文任意词汇的同义词近义词
Python synonyms查找中文任意词汇的同义词近义词 作者:虚坏叔叔 博客:https://xuhss.com 早餐店不会开到晚上,想吃的人早就来了!😄 一、安装 对于非专业的开发人员来说可以简单的使用Python一行代码来找到同义词。这…...
三分钟了解http和https
对应测试人员都会听过http请求和响应.在这里给大家介绍http相关的知识 一.http和https基本概念 HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本…...
docker应用:搭建私有云盘
简介:NextCloud是一个开源的云存储解决方案,可以在自己的服务器上搭建个人云存储系统。它提供了与市面上主流云存储服务(如Dropbox、Google Drive)相似的功能,包括文件存储、共享、同步、协作等。NextCloud的主要优势在…...
【C++进阶】面向对象
程序 编写程序是为了让计算机解决现实生活中的实际问题。pascal之父、结构化程序设计先驱Niklaus Wirth提出程序 算法 数据结构。程序是完成一定功能的一些列有序指令的集合。指令 操作码 指令。将指令按一定的顺序进行整合,就形成了程序。 机器语言与汇编语言…...
从ChatGPT与New Bing看程序员为什么要学习算法?
文章目录为什么要学习数据结构和算法?ChatGPT与NEW Bing 的回答想要通关大厂面试,就不能让数据结构和算法拖了后腿业务开发工程师,你真的愿意做一辈子CRUD boy吗?对编程还有追求?不想被行业淘汰?那就不要只…...
SpringBoot-实用开发篇
SpringBoot开发实用篇开发实用篇中因为牵扯到SpringBoot整合各种各样的技术,所以在整合每一个技术之前,都会做一个快速的普及,这样的话内容整个开发实用篇所包含的内容就会比较多。在学习的时候,如果对某一个技术不是很清楚&#…...
Python进阶-----高阶函数->filter() 函数
目录 前言: filter() 函数介绍 filter() 函数使用示例 1.与循环对比 2.与lambda函数综合使用 3.使用None过滤False 4.过滤字典相关数据 前言: 家人们,当你们获取了一个序列的时候,想要把一些内容去掉,保留一部分…...
C/C++面试可能会问三:指针和数组一样吗?
答案:不一样。 哪里不同? 数组名:数组名的值是一个指针常量,也就是数组第一个元素的地址。 它的类型取决于数组元素的类型:如果他们是int类型,那么数组名的类型就是“指向int的常量指针”;如果…...
数字经济新生态,中小企业如何发展营销数字化
五年弹指一挥间,中国数字经济正从尝试探索迈向快速发展,这一趋势,从今年两会的国务院机构改革、总理政府工作报告、部长通道答疑解惑、科技领域大佬提案中都能看出来。 在政府工作报告中,我们可以看到数字经济在不断壮大ÿ…...
【网络】https协议
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...
【11】SCI易中期刊推荐——计算机方向(中科院4区)
🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…...
STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2)
STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2) 目录STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2)前言1 环境搭建2 功能描述3 程序编写3.1 BootLoader部分3.2 APP的制作4 修改工程中的内存配置4.1 Bootloader…...
【Spring6】| Bean的生命周期(重要)
目录 一:Bean的生命周期 1. 什么是Bean的生命周期 2. Bean的生命周期之5步 3. Bean生命周期之7步 4. Bean生命周期之10步 5. Bean的scop(作用域)不同,管理方式不同 6. 自己new的对象如何让Spring管理 一:Bean的…...
【C#】单据打印方案(定义打印模板、条形码、二维码、图片、标签)
系列文章 C#项目–业务单据号生成器(定义规则、自动编号、流水号) 本文链接:https://blog.csdn.net/youcheng_ge/article/details/129129787 C#项目–开始日期结束日期范围计算(上周、本周、明年、前年等) 本文链接&…...
前后端身份验证
1、web 开发模式 【】基于服务端渲染的传统 Web 开发模式 【】基于前后端分离的新型 Web 开发模式:依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式 2、身份认证 【】服务端渲染推荐使用 Session 认证机制 【】…...
【蓝桥杯嵌入式】ADC模数转换的原理图解析与代码实现(以第十一届省赛为例)——STM32G4
🎊【蓝桥杯嵌入式】专题正在持续更新中,原理图解析✨,各模块分析✨以及历年真题讲解✨都在这儿哦,欢迎大家前往订阅本专题,获取更多详细信息哦🎏🎏🎏 🪔本系列专栏 - 蓝…...
Matlab表示 CDF 时间值
从 CDF 纪元对象中提取日期信息。CDF 表示时间的方式与 MATLAB 不同。CDF 将日期和时间表示为自 1-Jan-0000 以来的毫秒数。这在 CDF 术语中称为纪元。为了表示 CDF 日期,MATLAB 使用一个称为 CDF 纪元对象的对象。MATLAB 还可以将日期和时间表示为日期时间值或日期序列号,即…...
基于Halcon的条码定位与识别【包含 一维码 和 二维码 】
1.针对一维码问题,先列代码: dev_update_off () dev_close_window () dev_open_window (0, 0, 600, 819, black, WindowHandle) dev_set_draw (margin) *读图 read_image (Image, 20221213-174036.png)*获取一维码区域对原图进行抠图 gen_rectangle1 (ROI_0, 2169.33, 1835.…...
一个网站可以做多少地区词/自媒体营销的策略和方法
文章目录[点击展开](?)[] KSFramework KEngine SLua Framework KSFramework KSFramework是一个整合KEngine、SLua的Unity 5开发框架,并为程序、美术、策划、运营提供辅助工具集。 热重载是KSFramework的开发重点——在不重启游戏的前提下,重载代码、配…...
郑州做设计公司网站/腾讯企点客服
本文参考数据挖掘与R第二章节 读入数据 方法1,下载Data mining with r的配套包 install.packages(DMwR) 方法2,下载txt数据,并且读入数据。方法见上文。 Summary()#的到数据的摘要,概括。(包括最大,小值&…...
做网站都需要买什么/淘宝如何提升关键词排名
原创地址:http://www.cnblogs.com/jfzhu/archive/2012/12/10/2812040.html 转载请注明出处 我在之前的博客中介绍过如何为Microsoft Dynamics CRM 2011 安装语言包,安装了不同的语言包后,用户可以选择使用不同的界面语言。我在本文中介绍一下…...
手机网站什么意思/ip或域名查询网
getYear():对于1900-1999这段时间而言,返回的年份值是一个两位数字的整数,代表保存年份与1900 年之间的差。 IE对getYear()进行了修正,其他浏览器未修正。 测试代码如下: 1 //IE下输出为2012,非IE下输出为1122 3 var o…...
网站的优化分析/想做百度推广找谁
南财大学计算机基础试题A含参PAGE大学计算机基础试卷 A卷 第 PAGE 15 页 共 NUMPAGES 15 页大学计算机基础 课程试卷(A卷)一、单项选择题(共50小题,每题1分,共计50分)二、填空题(共36空,每空1分,共计36分)三、简答题(共3小题&…...