【SpringBatch】03步骤对象 (Step)控制与执行流程设计
目录标题
- 六、步骤对象 Step
- 6.1 步骤介绍
- 6.2 简单Tasklet
- 6.3 chunk居于块Tasklet
- **ChunkTasklet 泛型**
- 6.4 步骤监听器
- 6.5 多步骤执行
- 6.6 步骤控制
- 6.6.1 条件分支控制-使用默认返回状态
- 6.6.2 条件分支控制-使用自定义状态值
- 6.7 步骤状态
- 6.8 流式步骤
六、步骤对象 Step
前面一章节讲完了作业JOB的相关介绍,本章节重点讲解步骤。
6.1 步骤介绍
一般认为步骤是一个独立功能组件,因为它包含了一个工作单元需要的所有内容,比如:输入模块,输出模块,数据处理模块等。这种设计好处在哪?给开发者带来更自由的操作空间。
目前Spring Batch 支持2种步骤处理模式:
-
简单具于Tasklet 处理模式
这种模式相对简单,前面讲的都是居于这个模式批处理
@Bean public Tasklet tasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("Hello SpringBatch....");return RepeatStatus.FINISHED;}}; }
只需要实现Tasklet接口,就可以构建一个step代码块。循环执行step逻辑,直到tasklet.execute方法返回RepeatStatus.FINISHED
-
居于块(chunk)的处理模式
居于块的步骤一般包含2个或者3个组件:1>ItemReader 2>ItemProcessor(可选) 3>ItemWriter 。 当用上这些组件之后,Spring Batch 会按块处理数据。
6.2 简单Tasklet
学到这,我们写过很多简单Tasklet模式步骤,但是都没有深入了解过,这节就细致分析一下具有Tasklet 步骤使用。
先看下Tasklet源码
public interface Tasklet {@NullableRepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;
}
Tasklet 接口有且仅有一个方法:execute,
参数有2个:
StepContribution:步骤信息对象,用于保存当前步骤执行情况信息,核心用法:设置步骤结果状态contribution.setExitStatus(ExitStatus status)
contribution.setExitStatus(ExitStatus.COMPLETED);
ChunkContext:chuck上下文,跟之前学的StepContext JobContext一样,区别是它用于记录chunk块执行场景。通过它可以获取前面2个对象。
返回值1个:
RepeatStatus:当前步骤状态, 它是枚举类,有2个值,一个表示execute方法可以循环执行,一个表示已经执行结束。
public enum RepeatStatus {/*** 当前步骤依然可以执行,如果步骤返回该值,会一直循环执行*/CONTINUABLE(true), /*** 当前步骤结束,可以为成功也可以表示不成,仅代表当前step执行结束了*/FINISHED(false);
}
需求:练习上面RepeatStatus状态
@SpringBootApplication
@EnableBatchProcessing
public class SimpleTaskletJob {@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet tasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------>" + System.currentTimeMillis());//return RepeatStatus.CONTINUABLE; //循环执行return RepeatStatus.FINISHED;}};}@Beanpublic Step step1(){return stepBuilderFactory.get("step1").tasklet(tasklet()).build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("step-simple-tasklet-job").start(step1()).build();}public static void main(String[] args) {SpringApplication.run(SimpleTaskletJob.class, args);}}
6.3 chunk居于块Tasklet
居于块的Tasklet相对简单Tasklet来说,多了3个模块:ItemReader( 读模块), ItemProcessor(处理模块),ItemWriter(写模块), 跟它们名字一样, 一个负责数据读, 一个负责数据加工,一个负责数据写。
结构图:
*
时序图:
*
*
需求:简单演示chunk Tasklet使用
ItemReader ItemProcessor ItemWriter 都接口,直接使用匿名内部类方式方便创建
package com.langfeiyes.batch._08_step_chunk_tasklet;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.*;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import java.util.Arrays;
import java.util.List;@SpringBootApplication
@EnableBatchProcessing
public class ChunkTaskletJob {@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic ItemReader itemReader(){return new ItemReader() {@Overridepublic Object read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {System.out.println("-------------read------------");return "read-ret";}};}@Beanpublic ItemProcessor itemProcessor(){return new ItemProcessor() {@Overridepublic Object process(Object item) throws Exception {System.out.println("-------------process------------>" + item);return "process-ret->" + item;}};}@Beanpublic ItemWriter itemWriter(){return new ItemWriter() {@Overridepublic void write(List items) throws Exception {System.out.println(items);}};}@Beanpublic Step step1(){return stepBuilderFactory.get("step1").chunk(3) //设置块的size为3次.reader(itemReader()).processor(itemProcessor()).writer(itemWriter()).build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("step-chunk-tasklet-job").start(step1()).incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(ChunkTaskletJob.class, args);}
}
执行完了之后结果
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret
-------------process------------>read-ret
-------------process------------>read-ret
[process-ret->read-ret, process-ret->read-ret, process-ret->read-ret]
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret
-------------process------------>read-ret
-------------process------------>read-ret
[process-ret->read-ret, process-ret->read-ret, process-ret->read-ret]
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret
-------------process------------>read-ret
-------------process------------>read-ret
[process-ret->read-ret, process-ret->read-ret, process-ret->read-ret]
....
观察上面打印结果,得出2个得出。
1>程序一直在循环打印,先循环打印3次reader, 再循环打印3次processor,最后一次性输出3个值。
2>死循环重复上面步骤
问题来了,为啥会出现这种效果,该怎么改进?
其实这个是ChunkTasklet 执行特点,ItemReader会一直循环读,直到返回null,才停止。而processor也是一样,itemReader读多少次,它处理多少次, itemWriter 一次性输出当前次输入的所有数据。
我们改进一下上面案例,要求只读3次, 只需要改动itemReader方法就行
int timer = 3;
@Bean
public ItemReader itemReader(){return new ItemReader() {@Overridepublic Object read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {if(timer > 0){System.out.println("-------------read------------");return "read-ret-" + timer--;}else{return null;}}};
}
结果不在死循环了
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret-3
-------------process------------>read-ret-2
-------------process------------>read-ret-1
[process-ret->read-ret-3, process-ret->read-ret-2, process-ret->read-ret-1]
思考一个问题, 如果将timer改为 10,而 .chunk(3) 不变结果会怎样?
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret-10
-------------process------------>read-ret-9
-------------process------------>read-ret-8
[process-ret->read-ret-10, process-ret->read-ret-9, process-ret->read-ret-8]
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret-7
-------------process------------>read-ret-6
-------------process------------>read-ret-5
[process-ret->read-ret-7, process-ret->read-ret-6, process-ret->read-ret-5]
-------------read------------
-------------read------------
-------------read------------
-------------process------------>read-ret-4
-------------process------------>read-ret-3
-------------process------------>read-ret-2
[process-ret->read-ret-4, process-ret->read-ret-3, process-ret->read-ret-2]
-------------read------------
-------------process------------>read-ret-1
[process-ret->read-ret-1]
找出规律了嘛?
当chunkSize = 3 表示 reader 先读3次,提交给processor处理3次,最后由writer输出3个值
timer =10, 表示数据有10条,一个批次(趟)只能处理3条数据,需要4个批次(趟)来处理。
是不是有批处理味道出来
结论:chunkSize 表示: 一趟需要ItemReader读多少次,ItemProcessor要处理多少次。
ChunkTasklet 泛型
上面案例默认的是使用Object类型读、写、处理数据,如果明确了Item的数据类型,可以明确指定具体操作泛型。
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.*;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import java.util.List;//开启 spring batch 注解--可以让spring容器创建springbatch操作相关类对象
@EnableBatchProcessing
//springboot 项目,启动注解, 保证当前为为启动类
@SpringBootApplication
public class ChunkTaskletJob {//作业启动器@Autowiredprivate JobLauncher jobLauncher;//job构造工厂---用于构建job对象@Autowiredprivate JobBuilderFactory jobBuilderFactory;//step 构造工厂--用于构造step对象@Autowiredprivate StepBuilderFactory stepBuilderFactory;int timer = 10;//读操作@Beanpublic ItemReader<String> itemReader(){return new ItemReader<String>() {@Overridepublic String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {if(timer > 0){System.out.println("-------------read------------");return "read-ret-->" + timer--;}else{return null;}}};}//处理操作@Beanpublic ItemProcessor<String, String> itemProcessor(){return new ItemProcessor<String, String>() {@Overridepublic String process(String item) throws Exception {System.out.println("-------------process------------>" + item);return "process-ret->" + item;}};}//写操作@Beanpublic ItemWriter<String> itemWriter(){return new ItemWriter<String>() {@Overridepublic void write(List<? extends String> items) throws Exception {System.out.println(items);}};}//构造一个step对象--chunk@Beanpublic Step step1(){//tasklet 执行step逻辑, 类似 Thread()--->可以执行runable接口return stepBuilderFactory.get("step1").<String, String>chunk(3) //暂时为3.reader(itemReader()).processor(itemProcessor()).writer(itemWriter()).build();}@Beanpublic Job job(){return jobBuilderFactory.get("chunk-tasklet-job").start(step1()).incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(ChunkTaskletJob.class, args);}}
6.4 步骤监听器
前面我们讲了作业的监听器,步骤也有监听器,也是执行步骤执行前监听,步骤执行后监听。
步骤监听器有2个分别是:StepExecutionListener ChunkListener 意义很明显,就是step前后,chunk块执行前后监听。
先看下StepExecutionListener接口
public interface StepExecutionListener extends StepListener {void beforeStep(StepExecution stepExecution);@NullableExitStatus afterStep(StepExecution stepExecution);
}
需求:演示StepExecutionListener 用法
自定义监听接口
public class MyStepListener implements StepExecutionListener {@Overridepublic void beforeStep(StepExecution stepExecution) {System.out.println("-----------beforeStep--------->");}@Overridepublic ExitStatus afterStep(StepExecution stepExecution) {System.out.println("-----------afterStep--------->");return stepExecution.getExitStatus(); //不改动返回状态}
}
package com.langfeiyes.batch._09_step_listener;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
@EnableBatchProcessing
public class StepListenerJob {@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet tasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------>" + System.currentTimeMillis());return RepeatStatus.FINISHED;}};}@Beanpublic MyStepListener stepListener(){return new MyStepListener();}@Beanpublic Step step1(){return stepBuilderFactory.get("step1").tasklet(tasklet()).listener(stepListener()) .build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("step-listener-job1").start(step1()).incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(StepListenerJob.class, args);}}
在step1方法中,加入:.listener(stepListener()) 即可
同理ChunkListener 操作跟上面一样
public interface ChunkListener extends StepListener {static final String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception";void beforeChunk(ChunkContext context);void afterChunk(ChunkContext context);void afterChunkError(ChunkContext context);
}
唯一的区别是多了一个afterChunkError 方法,表示当chunk执行失败后回调。
6.5 多步骤执行
到目前为止,我们演示的案例基本上都是一个作业, 一个步骤,那如果有多个步骤会怎样?Spring Batch 支持多步骤执行,以应对复杂业务需要多步骤配合执行的场景。
需求:定义2个步骤,然后依次执行
package com.langfeiyes.batch._10_step_multi;import com.langfeiyes.batch._09_step_listener.MyChunkListener;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
@EnableBatchProcessing
public class MultiStepJob {@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet tasklet1(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------tasklet1---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet tasklet2(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------tasklet2---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Step step1(){return stepBuilderFactory.get("step1").tasklet(tasklet1()).build();}@Beanpublic Step step2(){return stepBuilderFactory.get("step2").tasklet(tasklet2()).build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("step-multi-job1").start(step1()).next(step2()) //job 使用next 执行下一步骤.incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(MultiStepJob.class, args);}
}
定义2个tasklet: tasklet1 tasklet2, 定义2个step: step1 step2 修改 job方法,从.start(step1()) 然后执行到 .next(step2())
Spring Batch 使用next 执行下一步步骤,如果还有第三个step,再加一个next(step3)即可
6.6 步骤控制
上面多个步骤操作,先执行step1 然后是step2,如果有step3, step4,那执行顺序也是从step1到step4。此时爱思考的小伙伴肯定会想,步骤的执行能不能进行条件控制呢?比如:step1执行结束根据业务条件选择执行step2或者执行step3,亦或者直接结束呢?答案是yes:设置步骤执行条件即可
Spring Batch 使用 start next on from to end 不同的api 改变步骤执行顺序。
6.6.1 条件分支控制-使用默认返回状态
需求:作业执行firstStep步骤,如果处理成功执行sucessStep,如果处理失败执行failStep
package com.langfeiyes.batch._11_step_condition;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
@EnableBatchProcessing
public class ConditionStepJob {@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet firstTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------firstTasklet---------------");return RepeatStatus.FINISHED;//throw new RuntimeException("测试fail结果");}};}@Beanpublic Tasklet successTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------successTasklet---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet failTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------failTasklet---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Step firstStep(){return stepBuilderFactory.get("step1").tasklet(firstTasklet()).build();}@Beanpublic Step successStep(){return stepBuilderFactory.get("successStep").tasklet(successTasklet()).build();}@Beanpublic Step failStep(){return stepBuilderFactory.get("failStep").tasklet(failTasklet()).build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("condition-multi-job").start(firstStep()).on("FAILED").to(failStep()).from(firstStep()).on("*").to(successStep()).end().incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(ConditionStepJob.class, args);}
}
观察给出的案例,job方法以 .start(firstStep()) 开始作业,执行完成之后, 使用on 与from 2个方法实现流程转向。
.on(“FAILED”).to(failStep()) 表示当**firstStep()**返回FAILED时执行。
.from(firstStep()).on(“*”).to(successStep()) 另外一个分支,表示当**firstStep()**返回 * 时执行。
上面逻辑有点像 if / else 语法
if("FAILED".equals(firstStep())){failStep();
}else{successStep();
}
几个注意点:
1> on 方法表示条件, 上一个步骤返回值,匹配指定的字符串,满足后执行后续 to 步骤
2> * 为通配符,表示能匹配任意返回值
3> from 表示从某个步骤开始进行条件判断
4> 分支判断结束,流程以end方法结束,表示if/else逻辑结束
5> on 方法中字符串取值于 ExitStatus 类常量,当然也可以自定义。
6.6.2 条件分支控制-使用自定义状态值
前面也说了,on条件的值取值于ExitStatus 类常量,具体值有:UNKNOWN,EXECUTING,COMPLETED,NOOP,FAILED,STOPPED等,如果此时我想自定义返回值呢,是否可行?答案还是yes:Spring Batch 提供JobExecutionDecider 接口实现状态值定制。
需求:先执行firstStep,如果返回值为A,执行stepA, 返回值为B,执行stepB, 其他执行defaultStep
分析:先定义一个决策器,随机决定返回A / B / C
public class MyStatusDecider implements JobExecutionDecider {@Overridepublic FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {long ret = new Random().nextInt(3);if(ret == 0){return new FlowExecutionStatus("A");}else if(ret == 1){return new FlowExecutionStatus("B");}else{return new FlowExecutionStatus("C");}}
}
package com.langfeiyes.batch._11_step_condition_decider;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
@EnableBatchProcessing
public class CustomizeStatusStepJob {@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet taskletFirst(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------taskletFirst---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletA(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------taskletA---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletB(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------taskletB---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletDefault(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("--------------taskletDefault---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Step firstStep(){return stepBuilderFactory.get("firstStep").tasklet(taskletFirst()).build();}@Beanpublic Step stepA(){return stepBuilderFactory.get("stepA").tasklet(taskletA()).build();}@Beanpublic Step stepB(){return stepBuilderFactory.get("stepB").tasklet(taskletB()).build();}@Beanpublic Step defaultStep(){return stepBuilderFactory.get("defaultStep").tasklet(taskletDefault()).build();}//决策器@Beanpublic MyStatusDecider statusDecider(){return new MyStatusDecider();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("customize-step-job").start(firstStep()).next(statusDecider()) .from(statusDecider()).on("A").to(stepA()).from(statusDecider()).on("B").to(stepB()).from(statusDecider()).on("*").to(defaultStep()).end().incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(CustomizeStepJob.class, args);}}
反复执行,会返回打印的值有
--------------taskletA---------------
--------------taskletB---------------
--------------taskletDefault---------------
它们随机切换,为啥能做到这样?注意,并不是firstStep() 执行返回值为A/B/C控制流程跳转,而是由后面**.next(statusDecider())** 决策器。
6.7 步骤状态
Spring Batch 使用ExitStatus 类表示步骤、块、作业执行状态,大体上有以下几种:
public class ExitStatus implements Serializable, Comparable<ExitStatus> {//未知状态public static final ExitStatus UNKNOWN = new ExitStatus("UNKNOWN");//执行中public static final ExitStatus EXECUTING = new ExitStatus("EXECUTING");//执行完成public static final ExitStatus COMPLETED = new ExitStatus("COMPLETED");//无效执行public static final ExitStatus NOOP = new ExitStatus("NOOP");//执行失败public static final ExitStatus FAILED = new ExitStatus("FAILED");//执行中断public static final ExitStatus STOPPED = new ExitStatus("STOPPED");...
}
一般来说,作业启动之后,这些状态皆为流程自行控制。顺利结束返回:COMPLETED, 异常结束返回:FAILED,无效执行返回:NOOP, 这是肯定有小伙伴说,能不能编程控制呢?答案是可以的。
Spring Batch 提供 3个方法决定作业流程走向:
end():作业流程直接成功结束,返回状态为:COMPLETED
fail():作业流程直接失败结束,返回状态为:FAILED
stopAndRestart(step) :作业流程中断结束,返回状态:STOPPED 再次启动时,从step位置开始执行 (注意:前提是参数与Job Name一样)
**需求:当步骤firstStep执行抛出异常时,通过end, fail,stopAndRestart改变步骤执行状态 **
package com.langfeiyes.batch._12_step_status;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;//开启 spring batch 注解--可以让spring容器创建springbatch操作相关类对象
@EnableBatchProcessing
//springboot 项目,启动注解, 保证当前为为启动类
@SpringBootApplication
public class StatusStepJob {//作业启动器@Autowiredprivate JobLauncher jobLauncher;//job构造工厂---用于构建job对象@Autowiredprivate JobBuilderFactory jobBuilderFactory;//step 构造工厂--用于构造step对象@Autowiredprivate StepBuilderFactory stepBuilderFactory;//构造一个step对象执行的任务(逻辑对象)@Beanpublic Tasklet firstTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("----------------firstTasklet---------------");throw new RuntimeException("假装失败了");//return RepeatStatus.FINISHED; //执行完了}};}@Beanpublic Tasklet successTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("----------------successTasklet--------------");return RepeatStatus.FINISHED; //执行完了}};}@Beanpublic Tasklet failTasklet(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("----------------failTasklet---------------");return RepeatStatus.FINISHED; //执行完了}};}//构造一个step对象@Beanpublic Step firstStep(){//tasklet 执行step逻辑, 类似 Thread()--->可以执行runable接口return stepBuilderFactory.get("firstStep").tasklet(firstTasklet()).build();}//构造一个step对象@Beanpublic Step successStep(){//tasklet 执行step逻辑, 类似 Thread()--->可以执行runable接口return stepBuilderFactory.get("successStep").tasklet(successTasklet()).build();}//构造一个step对象@Beanpublic Step failStep(){//tasklet 执行step逻辑, 类似 Thread()--->可以执行runable接口return stepBuilderFactory.get("failStep").tasklet(failTasklet()).build();}//如果firstStep 执行成功:下一步执行successStep 否则是failStep@Beanpublic Job job(){return jobBuilderFactory.get("status-step-job").start(firstStep())//表示将当前本应该是失败结束的步骤直接转成正常结束--COMPLETED//.on("FAILED").end() //表示将当前本应该是失败结束的步骤直接转成失败结束:FAILED//.on("FAILED").fail() //表示将当前本应该是失败结束的步骤直接转成停止结束:STOPPED 里面参数表示后续要重启时, 从successStep位置开始.on("FAILED").stopAndRestart(successStep()).from(firstStep()).on("*").to(successStep()).end().incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(StatusStepJob.class, args);}}
6.8 流式步骤
FlowStep 流式步骤,也可以理解为步骤集合,由多个子步骤组成。作业执行时,将它当做一个普通步骤执行。一般用于较为复杂的业务,比如:一个业务逻辑需要拆分成按顺序执行的子步骤。
需求:先后执行stepA,stepB,stepC, 其中stepB中包含stepB1, stepB2,stepB3。
package com.langfeiyes.batch._13_flow_step;import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.builder.SimpleJobBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
@EnableBatchProcessing
public class FlowStepJob {@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Beanpublic Tasklet taskletA(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------------stepA--taskletA---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletB1(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------------stepB--taskletB1---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletB2(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------------stepB--taskletB2---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletB3(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------------stepB--taskletB3---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Tasklet taskletC(){return new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {System.out.println("------------stepC--taskletC---------------");return RepeatStatus.FINISHED;}};}@Beanpublic Step stepA(){return stepBuilderFactory.get("stepA").tasklet(taskletA()).build();}@Beanpublic Step stepB1(){return stepBuilderFactory.get("stepB1").tasklet(taskletB1()).build();}@Beanpublic Step stepB2(){return stepBuilderFactory.get("stepB2").tasklet(taskletB2()).build();}@Beanpublic Step stepB3(){return stepBuilderFactory.get("stepB3").tasklet(taskletB3()).build();}@Beanpublic Flow flowB(){return new FlowBuilder<Flow>("flowB").start(stepB1()).next(stepB2()).next(stepB3()).build();}@Beanpublic Step stepB(){return stepBuilderFactory.get("stepB").flow(flowB()).build();}@Beanpublic Step stepC(){return stepBuilderFactory.get("stepC").tasklet(taskletC()).build();}//定义作业@Beanpublic Job job(){return jobBuilderFactory.get("flow-step-job").start(stepA()).next(stepB()).next(stepC()).incrementer(new RunIdIncrementer()).build();}public static void main(String[] args) {SpringApplication.run(FlowStepJob.class, args);}}
此时的flowB()就是一个FlowStep,包含了stepB1, stepB2, stepB3 3个子step,他们全部执行完后, stepB才能算执行完成。下面执行结果也验证了这点。
2022-12-03 14:54:16.644 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepA]
------------stepA--taskletA---------------
2022-12-03 14:54:16.699 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepA] executed in 55ms
2022-12-03 14:54:16.738 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB]
2022-12-03 14:54:16.788 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB1]
------------stepB--taskletB1---------------
2022-12-03 14:54:16.844 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB1] executed in 56ms
2022-12-03 14:54:16.922 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB2]
------------stepB--taskletB2---------------
2022-12-03 14:54:16.952 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB2] executed in 30ms
2022-12-03 14:54:16.996 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB3]
------------stepB--taskletB3---------------
2022-12-03 14:54:17.032 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB3] executed in 36ms
2022-12-03 14:54:17.057 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB] executed in 318ms
2022-12-03 14:54:17.165 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepC]
------------stepC--taskletC---------------
2022-12-03 14:54:17.215 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepC] executed in 50ms
使用FlowStep的好处在于,在处理复杂额批处理逻辑中,flowStep可以单独实现一个子步骤流程,为批处理提供更高的灵活性。
相关文章:
【SpringBatch】03步骤对象 (Step)控制与执行流程设计
目录标题 六、步骤对象 Step6.1 步骤介绍6.2 简单Tasklet6.3 chunk居于块Tasklet**ChunkTasklet 泛型** 6.4 步骤监听器6.5 多步骤执行6.6 步骤控制6.6.1 条件分支控制-使用默认返回状态6.6.2 条件分支控制-使用自定义状态值 6.7 步骤状态6.8 流式步骤 六、步骤对象 Step 前面…...
走进Java:String字符串的基本使用
❀❀❀ 大佬求个关注吧~祝您开心每一天 ❀❀❀ 目录 一、什么是String 二、如何定义一个String 1. 用双引号定义 2. 通过构造函数定义 三、String中的一些常用方法 1 字符串比较 1.1 字符串使用 1.2 字符串使用equals() 1.3 使用 equalsIgnoreCase() 1.4 cpmpareTo…...
AI如何变革亚马逊广告投放?DeepBI的智能优化解析
在亚马逊平台上,广告投放的方式正经历着从人工精细化运营到AI自动化优化的深刻变革。传统的广告投放依赖人工操作,涉及海量数据分析、手动调价、竞价策略优化等环节,既耗时又易受人为因素影响。而AI驱动的智能投放系统,如DeepBI&a…...
小程序电子画册制作,用户体验为王!
家人们,现在小程序电子画册超火,不管是展示产品还是分享故事都超实用。但在小程序电子画册制作过程中,用户体验真的得好好考量!今天就和大家唠唠这其中的门道。 1、加载速度:快才是王道 打开小程序的瞬间,…...
【商城实战(49)】解锁小程序端适配与优化,让商城飞起来
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析
【完整版】3月19日,黄仁勋Nvidia GTC 2025 主题演讲|英伟达 英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析 一、引言 1.1 分析内容 本研究主要采用了文献研究法、数据分析以及专家观点引用相结合的方法。在文献研究方面,广泛收集了…...
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用 《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用内核对象内核对象的定义内核对象归操作系统所有 基于 Windows 的线程创建进程与线程的关系Windows 中线程的创建方…...
线性规划的基本解、基本可行解和可行解
在线性规划中,基本解、基本可行解和可行解是非常重要的概念,特别是在使用单纯形法求解时。下面详细解释这些概念,并说明如何计算它们。 1. 线性规划问题的标准形式 线性规划的标准形式为: 其中: A 是 mn 的矩阵&…...
【AVRCP】服务发现互操作性:CT 与 TG 的 SDP 协议契约解析
目录 一、服务发现的核心目标:能力画像对齐 二、控制器(CT)服务记录:控制能力的声明 2.1 必选字段:角色与协议的刚性契约 2.1.1 服务类标识(Service Class ID List) 2.1.2 协议描述列表&am…...
[从零开始学习JAVA] Stream流
前言: 本文我们将学习Stream流,他就像流水线一样,可以对我们要处理的对象进行逐步处理,最终达到我们想要的效果,是JAVA中的一大好帮手,值得我们了解和掌握。(通常和lambda 匿名内部类 方法引用相…...
K8S学习之基础三十八:Kube-static-metrics监控
Kube-static-metrics监控 kube-static-metrics组件可以通过监听apiserver生成有关资源对象的状态指标,比如Node、Pod,需要注意的是kube-state-metrics只是简单的提供一个metrics数据,并不会存储这些指标数据,所以可以使用Prom…...
JAVA-多线程join()等待一个线程
引言:更多线程的认识可以看一篇博客: JAVA-Thread类实现多线程-CSDN博客 一、join()的作用 我们知道线程是随机调度执行的,但是有时候我们需要另一个任务完成了,我们才能继续,这个时候我们就可以使用join去等待线程结束…...
HashMap 常用方法
HashMap 常用方法 方法作用示例put(K key, V value)添加键值对map.put("apple", 10);get(Object key)获取指定键的值map.get("apple"); → 10remove(Object key)删除指定键的键值对map.remove("orange");containsKey(Object key)检查是否包含指…...
LogicFlow介绍
LogicFlow介绍 LogicFlow是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批流配置、机器人逻辑编排、无代码平…...
Docker搭建MySQL主从服务器
一、在主机上创建MySQL配置文件——my.cnf master服务器配置文件路径:/data/docker/containers/mysql-cluster-master/conf.d/my.cnf slave服务器配置文件路径: /data/docker/containers/mysql-cluster-master/conf.d/my.cnf master服务配置文件内容 …...
计算机二级web易错点(4)-选择题
选项 A:<input type"radio"> 用于创建单选按钮,同一组单选按钮中只能选择一个选项,所以该选项不符合要求。选项 B:HTML 中没有 type"check" 这种类型,是错误的写法,不能产生复选…...
3.19学习总结
学习了Java中的面向对象的知识点 完成一道算法题,找树左下角的值,错误的以为左下角只能是最底层的左节点,但指的是最底层最左边的节点...
Swagger-告别手写文档
文章目录 1. 引言2. Swagger是什么?3. SpringBoot2.7.3集成Swagger4. 常见注解 1. 引言 在RESTful API开发中,维护准确、易读的接口文档是团队协作的核心挑战,通常接口文档分为离线的和实时的。离线的接口文档工具有 YAPI等,其中…...
LeetCode-回文数
原题链接:9. 回文数 - 力扣(LeetCode) 首先我会想到的是,将这个数字转成字符串,然后通过前后指针判断是否相等,最终返回结果是否为回文数,时间复杂度:O(n),空间复杂度&am…...
数据结构之链表(双链表)
目录 一、双向带头循环链表 概念 二、哨兵位的头节点 优点: 头节点的初始化 三、带头双向链表的实现 1.双链表的销毁 2.双链表的打印 3.双链表的尾插和头插 尾插: 头插: 4.双链表的尾删和头删 尾删: 头删: …...
硬件基础(5):(2)二极管分类
文章目录 📌 二极管的分类与详细介绍1. **整流二极管(Rectifier Diode)**特点:选型依据:补充说明: 2. **快恢复二极管(Fast Recovery Diode)**特点:选型依据:…...
MQTT 和 Modbus 的优缺点对比
MQTT和Modbus协议是物联网(IoT)躲不开的两种协议,市面上覆盖了百分之98的产品。 MQTT由IBM在1999年发布。2014年,MQTT成为OASIS(结构化信息标准促进组织)的标准,后来被ISO/IEC 20922正式采纳&a…...
Android14 系统左右声音通道设置代码
Android14 系统左右声音通道设置代码 文章目录 Android14 系统左右声音通道设置代码一、前言二、系统级设置左右声音通道分析1、各方案设置左右声音通道的主要代码(1)3588 Android13 方案的实现(2)9679 Android14 方案的实现&…...
【Golang】go如何通过atomic原子操作来确保数据一致性
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...
2025年汽车加气站操作工考试精选题库
汽车加气站操作工题库中题目及答案: 单项选择题 1、按压力容器的设计压力分为( )个压力等级。 A. 3 B. 4 C. 5 答案:B 2、缓冲罐的安装位置在天然气压缩机( )。 A. 前 B. 后 C. 中间 答案&#…...
LLVM学习--外部项目
不包含于核心LLVM和Clang存储库的项目需要单独下载。在本章中,我们将介绍各种其他官方LLVM项目,并介绍如何构建和安装它们。仅仅对核心LLVM工具感兴趣的读者可以跳过本章,或者在需要的时候翻阅。 在本章中,我们将介绍以下项目安装…...
AUTOSAR_DoIP_Detailed
AUTOSAR DoIP模块详解 基于AUTOSAR标准的诊断通信协议实现 目录 架构概述通信流程消息格式配置结构详细序列总结1. 架构概述 1.1 模块架构 DoIP模块是AUTOSAR基础软件中负责诊断通信的核心组件。它通过TCP/IP网络实现诊断工具与ECU之间的通信。主要功能包括: 基础功能 基于UD…...
C语言:(大数相加版)数字阶梯求和
题目: 给定a和n,计算aaaaaaa...a(n个a)的和。 输入 测试数据有多组,输入a,n(1<a<9,1<n<100)。 输出 对于每组输入,请输出结果。 分析: 1. 方式和规定:大数相加必然越界…...
Echarts 折线图
功能 每月记录值,当数据大于600画红线,小于300画蓝线,其他在中间值为黑线。鼠标移动上去后,现在数据值。 option {tooltip: {trigger: axis, // 触发类型:坐标轴触发show: true, // 显示提示框formatter: function …...
element-plus中Dropdown下拉菜单组件的使用
1、基本使用 复制下面的代码: <!-- 选择查询类型 --> <el-dropdown trigger"click"><span class"el-dropdown-link"><span style"width:60px;color:#404040">查询类型</span><el-icon class"e…...
Kafka详解——介绍与部署
1. 什么是 Kafka? Kafka 是一个分布式的消息队列系统,最初由 LinkedIn 开发,后来成为 Apache 开源项目。它的主要用途包括实时数据处理、日志收集、数据流管道构建等。Kafka 具备高吞吐量、可扩展性、持久性和容错性,广泛应用于大…...
ngx_http_core_srv_conf_t
定义在 src\http\ngx_http_core_module.h typedef struct {/* array of the ngx_http_server_name_t, "server_name" directive */ngx_array_t server_names;/* server ctx */ngx_http_conf_ctx_t *ctx;u_char *file_…...
4.angular 服务
服务是在controller里面引入的服务: 最好是内部服务在前面,自定义服务在后面 内部服务 $scope $scope.$watch(‘属性名’, function(newVal, oldVal) {}, true) true是深度监听,对象函数等$scope.$apply 触发页面更新,里面传入回调函数,比如说之前那个…...
[动手学习深度学习]26. 网络中的网络 NiN
前面的LeNet、AlexNet、VGG在设计上的共同之处在于:先以卷积层构成的模块充分抽取空间特征,再以全连接层构成的模块来输出分类结果 其中AlexNet和VGG对LeNet的改进主要在于如何对这两个模块价款(增加通道数)和加深 这一节的NiN提出…...
【设计模式】原型模式
三、原型模式 3.2 原型模式 同工厂模式一样,原型(Prototype) 模式也是一种创建型模式。原型模式通过一个对象 (原型对象)克隆出多个一模一样的对象。实际上,该模式与其说是一种设计模式,不如说是 一种创建对象的方法(对象克隆),尤其是创建给…...
力扣题目汇总 使用贪心算法解决问题
贪心算法是一种通过局部最优解来获得全局最优解的算法。它的核心思想是:在每一步中选择当前看起来最优的解,并希望通过一系列局部最优选择最终得到全局最优解。 121.买卖股票的最佳时机 分析: 在每一天求出当前最优的利润,也就…...
Mac下Ollama安装全攻略:开启本地大模型之旅
文章目录 Mac下Ollama安装全攻略:开启本地大模型之旅一、Ollama 是什么功能特点优势应用场景 二、安装前准备(一)系统要求(二)硬件要求 三、下载安装包(一)官网下载(二)其…...
[HelloCTF]PHPinclude-labs超详细WP-Level 1-FILE协议
源码分析 <?php include("get_flag.php");isset($_GET[wrappers]) ? include("file://".$_GET[wrappers]) : ;highlight_file(__FILE__); ?>第一句 include("get_flag.php");, 使代码包含了 get_flag.php 的内容 大概是生成 Flag 之类的…...
Skia 图形引擎介绍
文章目录 一、Skia 的基本概念1. 定位与作用2. 历史背景 二、Skia 的核心架构1. 模块化设计2. 渲染流程3. 跨平台适配 三、Skia 在 Flutter 中的角色1. 自绘 UI 的核心依赖2. 跨平台一致性3. 性能优化 四、Skia 的性能优势1. 高效的图形处理2. 与原生渲染的对比3. 性能瓶颈 五、…...
构建高可靠NFS存储:自动化挂载保障机制的设计与优势
一、背景与需求场景 在分布式系统或集群架构中,NFS(Network File System)是跨节点共享存储的经典方案。然而,传统/etc/fstab配置的静态挂载方式存在明显缺陷: 服务启动顺序不可控,网络未就绪时挂载失败临…...
Spring Boot对接twilio发送邮件信息
要在Spring Boot应用程序中对接Twilio发送邮件信息,您可以使用Twilio的SendGrid API。以下是一个简单的步骤指南,帮助您完成这一过程: 1. 创建Twilio账户并获取API密钥 注册一个Twilio账户(如果您还没有的话)。在Twi…...
如何创建并保存HTML文件?零基础入门教程
原文:如何创建并保存HTML文件?零基础入门教程 | w3cschool笔记 本文将以Windows系统为例,教你用最简单的记事本创建并保存第一个HTML网页。 📝 第一步:准备工具 文本编辑器:使用系统自带的记事本ÿ…...
vue3 + css 列表无限循环滚动+鼠标移入停止滚动+移出继续滚动
1.动画文件.vue <template><div class"dashboard" click"setFullScreen"><div class"warp-box"><el-scrollbar ref"scrollRef" height"100%" scroll"handelScroll"><div class"…...
C#的简单工厂模式、工厂方法模式、抽象工厂模式
工厂模式是一种创建型设计模式,主要将对象的创建和使用分离,使得系统更加灵活和可维护。常见的工厂模式有简单工厂模式、工厂方法模式和抽象工厂模式,以下是 C# 实现的三个案例: 简单工厂模式 简单工厂模式通过一个工厂类来创建…...
Vue:Vue2和Vue3创建项目的几种常用方式以及区别
前言 Vue.js 和 Element UI 都是用 JavaScript 编写的。 1、Vue.js 是一个渐进式 JavaScript 框架。2、Element UI 是基于 Vue.js 的组件库。3、JavaScript 是这两个项目的主要编程语言。 而Element Plus是基于TypeScript开发的。 一、Vue2 1、基于vuecli工具创建 vue2 …...
C++ list类
C list类 目录 C list类引言1.list的使用1.1 list的构造1.2 list的iterator的使用1.3 list capacity1.4 list element acess1.5 list modifiers 2. list的迭代器失效3. list的模拟实现3.1 List.h文件3.2 List的反向迭代器 4.list与vector的对比 引言 在C标准库中,l…...
LeetCode 热题 100_跳跃游戏(78_55_中等_C++)(贪心算法)
LeetCode 热题 100_跳跃游戏(78_55) 题目描述:输入输出样例:题解:解题思路:思路一(贪心算法): 代码实现代码实现(思路一(贪心算法)&am…...
【Redis】Redis的数据删除(过期)策略,数据淘汰策略。
如果问到:假如Redis的key过期之后,会立即删除吗? 其实就是想问数据删除(过期)策略。 如果面试官问到:如果缓存过多,内存是有限的,内存被占满了怎么办? 其实就是问:数据的淘汰策略。…...
C++和标准库速成(八)——指针、动态数组、const、constexpr和consteval
目录 1. 指针和动态数组1.1 栈和自由存储区1.2 使用指针1.3 动态分配的数组1.4 空指针常量 2. const2.1 const修饰类型2.2 const与指针2.3 使用const保护参数2.4 const方法(建议) 3. constexpr4. consteval参考 1. 指针和动态数组 动态内存允许所创建的程序具有在编…...
深入解析 Spring Boot 中的 FailureAnalyzer
深入解析 Spring Boot 中的 FailureAnalyzer 在 Spring Boot 应用中,我们难免会遇到启动失败的情况,而默认的异常信息往往过于复杂,导致排查问题变得困难。Spring Boot 提供了一套强大的 FailureAnalyzer 机制,能够捕获常见的异常…...