SpringBoot自定义starter开发分布式任务调度实践
概述
需求
在前面的博客《Java定时器演进过程和生产级分布式任务调度ElasticJob代码实战》中,我们已经熟悉ElasticJob分布式任务的应用,其核心实现为elasticjob-lite-spring-boot-starter,少量配置开箱即用;还有前面也有博客文档谈谈走进Spring Boot源码学习之路和浅谈入门,了解Spring Boot的原理,没看过伙伴可以先翻看下前面的文章。SpringBoot官网已经提供非常多的starter使用,然而今天我们就来模拟封装一个简易的分布式任务调度实现定时任务选主执行和故障自动转移的starter,本篇主要重心在于基于SpringBoot官网标准start封装的模板和步骤。
相关概念
应用程序上下文
在Spring应用程序中,应用程序上下文是组成应用程序的对象(或“bean”)的网络。它包含我们的Web控制器,服务,存储库以及我们的应用程序可能需要的任何(通常是无状态的)对象。配置
使用注释@Configuration标注的类,扮演添加到应用程序上下文的bean工厂。它可能包含带注释的工厂方法,@Bean其返回值由Spring自动添加到应用程序上下文中。
简而言之,Spring配置为应用程序上下文提供bean。自动配置
自动配置是Spring自动发现的@Configuration类。只要该类位于在类路径classpath上,即可自动配置,并将配置的结果添加到应用程序上下文中。自动配置可以是有条件的,使得其激活取决于外部因素,例如具有特定值的特定配置参数。自动配置模块
自动配置模块是包含自动配置类的Maven或Gradle模块。这样,我们就可以构建自动为应用程序上下文做贡献的模块,添加某个功能或提供对某个外部库的访问。我们在Spring Boot应用程序中使用它所要做的就是在我们的pom.xml或者包含它的依赖项build.gradle。
Spring Boot团队大量使用此方法将Spring Boot与外部库集成。Spring Boot Starter
Spring Boot Starter是一个Maven或Gradle模块,其唯一目的是提供“使用某个功能”“开始”所需的所有依赖项。这通常意味着它是一个单独的pom.xml或build.gradle文件,包含一个或多个自动配置模块的依赖项以及可能需要的任何其他依赖项。在Spring Boot应用程序中,我们只需要包含此启动器Starter即可使用该功能。
制作starter基本步骤
- 提供了一个配置类,该配置类定义了我们需要的对象的实例化过程;
- 提供了一个spring.factories文件,包含了配置类的全限定名;
- 将配置类和spring.factories文件打包为一个启动器starter;
- 程序启动时通过加载starter.jar包的spring.factories文件信息,然后通过反射实例化文件里面的类。
SpringBoot启动简述
Spring Boot 在启动的时候会做这几件事情
- Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。
- 根据 spring.factories 配置加载 AutoConfigure 类。
- 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context。
其实也就是 Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可。
实践
创建项目
- 首先建立light-job-spring-boot-starter-autoconfigure的空项目,然后在项目中添加light-job-spring-boot-starter-autoconfigure的Maven模块,这里的light-job-spring-boot-starter-autoconfigure模块则是实现简易的分布式任务调度。
- 然后再新建一个专门作为依赖light-job-spring-boot-starter-autoconfigure模块空实现的maven模块,名称为light-job-spring-boot-starter,这个也是参考SpringBoot官网封装标准,具体可以看前面的文章如何说明spring-boot-starter-data-redis的官网实现。
autoconfigure实现
参考GitHub基于分布式任务实现的一些代码,这里核心主要是构建一个light-job自动装配配置文件读取类和一个light-job自动装配配置类。
light-job-spring-boot-starter-autoconfigure模块添加Pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itxs</groupId>
<artifactId>light-job-spring-boot-starter-autoconfigure</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>light-job-spring-boot-starter-autoconfigure</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.8</jdk.version>
<spring-boot.version>2.6.4</spring-boot.version>
<zookeeper.version>3.4.6</zookeeper.version>
<commons-lang3.version>3.4</commons-lang3.version>
<quartz.version>2.3.2</quartz.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>${quartz.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
创建LightJobProperties读取配置文件
package com.itxs.lightjob.config;
import com.itxs.lightjob.zk.ZKManager.KEYS;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ConfigurationProperties(prefix = "light.job",ignoreInvalidFields = true)
public class LightJobProperties {
private String enabled;
private String zkConnect;
private String rootPath = "/light/job";
private int zkSessionTimeout = 60000;
private String zkUsername;
private String zkPassword;
private List<String> ipBlackList;
private List<String> targetBean;
private List<String> targetMethod;
private List<String> cronExpression;
private List<String> startTime;
private List<String> period;
private List<String> delay;
private List<String> params;
private List<String> type;
private List<String> extKeySuffix;
private List<String> beforeMethod;
private List<String> afterMethod;
private List<String> threadNum;
public Map<String, String> getConfig(){
Map<String, String> properties = new HashMap<String, String>();
properties.put(KEYS.zkConnectString.key, zkConnect);
if(StringUtils.isNotBlank(rootPath)){
properties.put(KEYS.rootPath.key, rootPath);
}
if(zkSessionTimeout > 0){
properties.put(KEYS.zkSessionTimeout.key, zkSessionTimeout+"");
}
if(StringUtils.isNotBlank(zkUsername)){
properties.put(KEYS.userName.key, zkUsername);
}
if(StringUtils.isNotBlank(zkPassword)){
properties.put(KEYS.password.key, zkPassword);
}
StringBuilder sb = new StringBuilder();
if(ipBlackList != null && ipBlackList.size() > 0){
for(String ip:ipBlackList){
sb.append(ip).append(",");
}
sb.substring(0,sb.lastIndexOf(","));
}
properties.put(KEYS.ipBlacklist.key, sb.toString());
return properties;
}
public String getEnabled() {
return enabled;
}
public void setEnabled(String enabled) {
this.enabled = enabled;
}
public String getZkConnect() {
return zkConnect;
}
public void setZkConnect(String zkConnect) {
this.zkConnect = zkConnect;
}
public String getRootPath() {
return rootPath;
}
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}
public int getZkSessionTimeout() {
return zkSessionTimeout;
}
public void setZkSessionTimeout(int zkSessionTimeout) {
this.zkSessionTimeout = zkSessionTimeout;
}
public String getZkUsername() {
return zkUsername;
}
public void setZkUsername(String zkUsername) {
this.zkUsername = zkUsername;
}
public String getZkPassword() {
return zkPassword;
}
public void setZkPassword(String zkPassword) {
this.zkPassword = zkPassword;
}
public List<String> getIpBlackList() {
return ipBlackList;
}
public void setIpBlackList(List<String> ipBlackList) {
this.ipBlackList = ipBlackList;
}
public List<String> getTargetBean() {
return targetBean;
}
public void setTargetBean(List<String> targetBean) {
this.targetBean = targetBean;
}
public List<String> getTargetMethod() {
return targetMethod;
}
public void setTargetMethod(List<String> targetMethod) {
this.targetMethod = targetMethod;
}
public List<String> getCronExpression() {
return cronExpression;
}
public void setCronExpression(List<String> cronExpression) {
this.cronExpression = cronExpression;
}
public List<String> getStartTime() {
return startTime;
}
public void setStartTime(List<String> startTime) {
this.startTime = startTime;
}
public List<String> getPeriod() {
return period;
}
public void setPeriod(List<String> period) {
this.period = period;
}
public List<String> getDelay() {
return delay;
}
public void setDelay(List<String> delay) {
this.delay = delay;
}
public List<String> getParams() {
return params;
}
public void setParams(List<String> params) {
this.params = params;
}
public List<String> getType() {
return type;
}
public void setType(List<String> type) {
this.type = type;
}
public List<String> getExtKeySuffix() {
return extKeySuffix;
}
public void setExtKeySuffix(List<String> extKeySuffix) {
this.extKeySuffix = extKeySuffix;
}
public List<String> getBeforeMethod() {
return beforeMethod;
}
public void setBeforeMethod(List<String> beforeMethod) {
this.beforeMethod = beforeMethod;
}
public List<String> getAfterMethod() {
return afterMethod;
}
public void setAfterMethod(List<String> afterMethod) {
this.afterMethod = afterMethod;
}
public List<String> getThreadNum() {
return threadNum;
}
public void setThreadNum(List<String> threadNum) {
this.threadNum = threadNum;
}
}
创建自动装配类LightJobAutoConfiguration.java
package com.itxs.lightjob.config;
import com.itxs.lightjob.ZKScheduleManager;
import com.itxs.lightjob.core.TaskDefine;
import com.itxs.lightjob.util.ScheduleUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Configuration
@EnableConfigurationProperties({LightJobProperties.class})
@ConditionalOnProperty(value = "light.job.enabled", havingValue = "true")
@ComponentScan()
public class LightJobAutoConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(LightJobAutoConfiguration.class);
@Autowired
private LightJobProperties uncodeScheduleConfig;
@Bean(name = "zkScheduleManager", initMethod="init")
public ZKScheduleManager commonMapper(){
ZKScheduleManager zkScheduleManager = new ZKScheduleManager();
zkScheduleManager.setZkConfig(uncodeScheduleConfig.getConfig());
List<TaskDefine> list = initAllTask();
zkScheduleManager.setInitTaskDefines(list);
LOGGER.info("=====>ZKScheduleManager inited..");
return zkScheduleManager;
}
private List<TaskDefine> initAllTask(){
List<TaskDefine> list = new ArrayList<TaskDefine>();
int total = 0;
if(uncodeScheduleConfig.getTargetBean() != null){
total = uncodeScheduleConfig.getTargetBean().size();
}
for(int i = 0; i < total; i++){
TaskDefine taskDefine = new TaskDefine();
if(uncodeScheduleConfig.getTargetBean() != null){
String value = uncodeScheduleConfig.getTargetBean().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setTargetBean(value);
}
}
if(uncodeScheduleConfig.getTargetMethod() != null){
String value = uncodeScheduleConfig.getTargetMethod().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setTargetMethod(value);
}
}
if(uncodeScheduleConfig.getCronExpression() != null){
String value = uncodeScheduleConfig.getCronExpression().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setCronExpression(value);
}
}
if(uncodeScheduleConfig.getStartTime() != null){
String value = uncodeScheduleConfig.getStartTime().get(i);
if(StringUtils.isNotBlank(value)){
Date time = null;
try {
time = ScheduleUtil.transferStringToDate(value);
} catch (ParseException e) {
e.printStackTrace();
}
if(time != null){
taskDefine.setStartTime(time);
}
}
}
if(uncodeScheduleConfig.getPeriod() != null){
String value = uncodeScheduleConfig.getPeriod().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setPeriod(Long.valueOf(value));
}
}
if(uncodeScheduleConfig.getDelay() != null){
String value = uncodeScheduleConfig.getDelay().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setDelay(Long.valueOf(value));
}
}
if(uncodeScheduleConfig.getParams() != null){
String value = uncodeScheduleConfig.getParams().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setParams(value);
}
}
if(uncodeScheduleConfig.getType() != null){
String value = uncodeScheduleConfig.getType().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setType(value);
}
}
if(uncodeScheduleConfig.getExtKeySuffix() != null){
String value = uncodeScheduleConfig.getExtKeySuffix().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setExtKeySuffix(value);
}
}
if(uncodeScheduleConfig.getBeforeMethod() != null){
String value = uncodeScheduleConfig.getBeforeMethod().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setBeforeMethod(value);
}
}
if(uncodeScheduleConfig.getAfterMethod() != null){
String value = uncodeScheduleConfig.getAfterMethod().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setAfterMethod(value);
}
}
if(uncodeScheduleConfig.getThreadNum() != null){
String value = uncodeScheduleConfig.getThreadNum().get(i);
if(StringUtils.isNotBlank(value)){
taskDefine.setThreadNum(Integer.valueOf(value));
}
}
list.add(taskDefine);
}
return list;
}
}
然后在resources目录下的META-INF目录下创建spring.factories文件,跟SpringBoot其他starter一样,输出自动装配类的全类名;springboot项目默认只会扫描本项目下的带@Configuration注解的类,如果自定义starter,不在本工程中,是无法加载的,所以要配置META-INF/spring.factories配置文件。配置了META-INF/spring.factories配置文件是springboot实现starter的关键点,springboot的这种配置加载方式是一种类SPI(Service Provider Interface)的方式,SPI可以在META-INF/services配置接口扩展的实现类,springboot中原理类似,只是名称换成了spring.factories而已。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itxs.lightjob.config.LightJobAutoConfiguration
其他还有自动装配类的具体实现代码文件,如下面目录,主要利用zookeeper做分布式协调如分布式选主,执行maven install打包和安装到本地maven仓库。
light-job-spring-boot-starter
light-job-spring-boot-starter是不做实现,主要管理依赖,Pom文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itxs</groupId>
<artifactId>light-job-spring-boot-starter</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.itxs</groupId>
<artifactId>light-job-spring-boot-starter-autoconfigure</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
最后我们执行maven install打包和安装到本地maven仓库。
调用示例
示例工程中加入light-job-spring-boot-starter依赖,这里选择前面文章示例的库存微服务模块中添加
<dependency>
<groupId>com.itxs</groupId>
<artifactId>light-job-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
创建演示任务并放到Spring容器里管理
package cn.itxs.ecom.storage.job;
import org.springframework.stereotype.Component;
@Component
public class DemoTask {
public void execute() {
System.out.println("===========execute start!=========");
System.out.println("===========do job!=========");
System.out.println("===========execute end !=========");
}
}
配置文件增加
light:
job:
enabled: true
zk-connect: 192.168.4.27:2181,192.168.4.28:2181,192.168.4.29:2181
root-path: /ecom/storage
zk-session-timeout: 60000
target-bean:
- demoTask
target-method:
- execute
period:
- 1000
cron-expression:
- 0/10 * * * * ?
启动三个库存微服务模块,在第1个库存微服务模块看到demoTask任务已经根据配置每十秒在运行
关闭第1个库存微服务模块程序后,通过zookeeper重新选举一个节点定时执行,从下面看选择第3个库存微服务模块每十秒实行
Redis读取配置赋值lightjob
zookeeper地址配置可以放到配置中心如Nacos,如果目前我们配置数据是放在Redis中,可以通过System.setProperty设置系统变量的方式来实现,先注释zk-connect的配置,这是启动程序就会报错
RedisConfig配置类中增加实现BeanPostProcessor接口实现其postProcessAfterInitialization方法,在bean初始化后读取redis值设置环境变量值。
package cn.itxs.ecom.storage.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@Slf4j
public class RedisConfig implements BeanPostProcessor{
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
//template.setValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
//template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
//在redisTemplate Bean初始化之后设置light.job.zk-connect为公共集群的zk地址
if (beanName.equals("redisTemplate")){
log.info("postProcessAfterInitialization match beanName {}",beanName);
try {
RedisTemplate redisObj = (RedisTemplate) bean;
String zkConnect = (String)redisObj.opsForHash().get("clusterinfo", "zookeeper-server");
if (StringUtils.isNotBlank(zkConnect)) {
log.info("postProcessAfterInitialization get zkConnect ={}", zkConnect);
System.setProperty("light.job.zk-connect", zkConnect);
log.info("System.setProperty light.job.zk-connect={}", zkConnect);
}
} catch (Exception e) {
log.error("postProcessAfterInitialization operate redisTemplate {} failed", e);
}
}
return null;
}
}
启动后可以看到正常每十秒执行定时任务
**本人博客网站 **IT小神 www.itxiaoshen.com
SpringBoot自定义starter开发分布式任务调度实践的更多相关文章
- SpringBoot --- 自定义 Starter
SpringBoot --- 自定义 Starter 创建 1.需要创建一个新的空工程 2.新的工程需要引入两个模块 一个Maven 模块 作为启动器 一个SpringBoot 模块 作为自动配置模块 ...
- SpringBoot自定义starter及自动配置
SpringBoot的核心就是自动配置,而支持自动配置的是一个个starter项目.除了官方已有的starter,用户自己也可以根据规则自定义自己的starter项目. 自定义starter条件 自动 ...
- SpringBoot自定义Starter实现
自定义Starter: Starter会把所有用到的依赖都给包含进来,避免了开发者自己去引入依赖所带来的麻烦.Starter 提供了一种开箱即用的理念,其中核心就是springboot的自动配置原理相 ...
- springboot 自定义starter之AutoConfiguration【原】
八.自定义starter AutoConfiguration: 1.这个场景需要使用到的依赖是什么? 没有特别依赖的配置 2.如何编写自动配置 @Configuration //指定这个类是一个配置类 ...
- Spring-Boot自定义Starter实践
此文已由作者王慎为授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. disconf-spring-boot-starter 使用方法: 引入maven依赖: <depen ...
- SpringBoot系列三:SpringBoot自定义Starter
在前面两章 SpringBoot入门 .SpringBoot自动配置原理 的学习后,我们对如何创建一个 SpringBoot 项目.SpringBoot 的运行原理以及自动配置等都有了一定的了解.如果 ...
- springboot自定义starter
1,创建一个空工程 2,new一个Modules ---------------- maven (启动器) : springboottest-spring-boot-starter 3,new一个M ...
- Springboot自定义starter打印sql及其执行时间
前面写到了通过实现mybatis提供的org.apache.ibatis.plugin.Interceptor接口实现了打印SQL执行时间,并格式化SQL及其参数,如果我们使用的是ssm还得再配置文件 ...
- SpringBoot系列之自定义starter实践教程
SpringBoot系列之自定义starter实践教程 Springboot是有提供了很多starter的,starter翻译过来可以理解为场景启动器,所谓场景启动器配置了自动配置等等对应业务模块的一 ...
随机推荐
- Bugku CTF练习题---加密---ok
Bugku CTF练习题---加密---ok flag:flag{ok-ctf-1234-admin} 解题步骤: 1.观察题目,发现规律 2.发现所有内容都是ook写的, 直接上网搜索一下原因,发现 ...
- XCTF练习题---MISC---坚持60S
XCTF练习题---MISC---坚持60S flag:flag{DajiDali_JinwanChiji} 解题步骤: 1.观察题目,下载附件,是一个java文件 2.打开玩了一会,真鸡儿难,试着反 ...
- Python 树表查找_千树万树梨花开,忽如一夜春风来(二叉排序树、平衡二叉树)
什么是树表查询? 借助具有特殊性质的树数据结构进行关键字查找. 本文所涉及到的特殊结构性质的树包括: 二叉排序树. 平衡二叉树. 使用上述树结构存储数据时,因其本身对结点之间的关系以及顺序有特殊要求, ...
- 初探webpack之编写loader
初探webpack之编写loader loader加载器是webpack的核心之一,其用于将不同类型的文件转换为webpack可识别的模块,即用于把模块原内容按照需求转换成新内容,用以加载非js模块, ...
- SpringBoot从0到0.7——序言
SpringBoot从0到0.7-- 序言 最近做java代码审计发现很多地方看不懂,所以就开始学框架,自己做网站来了解网站的运行原理.函数.接口.参数等等,通过学习SpringBoot框架来从点到面 ...
- ElasticSearch7.3学习(二十五)----Doc value、query phase、fetch phase解析
1.Doc value 搜索的时候,要依靠倒排索引: 排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序. 所谓的正排索引,其实就是doc values. 在建立索引 ...
- GitHub 毕业年鉴「GitHub 热点速览 v.22.20」
GitHub 毕业需要什么呢?一个 PR!那么提交一个 PR 需要什么?也许你是使用终端命令来提交 git 操作的,那么你可以了解下 Bash-Oneliner,收录了大量好用的 bash 命令,虽然 ...
- Redis设计与实现2.1:数据库和事件
数据库和事件 这是<Redis设计与实现>系列的文章,系列导航:Redis设计与实现笔记 数据库 数据库的结构定义在 redis.h/redisServer 这个结构体中,这个结构体有许多 ...
- ArcGIS和ArcEngine导出地图时,png格式支持背景透明
1.ArcGIS支持导出PNG,背景透明 导出png时,背景色和透明色不能设置为空,必须设置为同一个颜色,通常使用白色. 2.ArcEngine支持导出PNG,背景透明 //1.创建export IE ...
- DeepPrivacy: A Generative Adversarial Network for Face Anonymization阅读笔记
DeepPrivacy: A Generative Adversarial Network for Face Anonymization ISVC 2019 https://arxiv.org/pdf ...