前言
okey~每日编码一坤时,昨天的话我们已经实现了这个框架的IOC容器。通过这个IOC容器,我们就可以非常轻松地进行后续的操作,于是,我们接着这个工作,去完成这个任务清单的解析。
昨天的话,阐述了一下这个框架解决了哪些问题,那么接下来,是如何使用这个家伙。以及今天的任务解析清单主要解决了哪些问题,也就是实现了哪些特性。
当然这边这个项目是开源的,只是还没有做好没有上传仓库而已。
实现特性
okey,我们先来看看实现的特性,能够做什么。先前我们说过,这样的一个场景:
假设你要去超市买菜:为了方便买菜,你可以写一个购物清单,然后按照购物清单上面的每一个项,去购买东西。每一次写购物清单的时候,都要写上物品的名字,每次这样写实在是太麻烦了。不过,好在超市的商品有限,于是我们把这些商品都编上序号。于是下一次在书写购物清单的时候,只需要写上这个商品对应的编号就可以了,如果有特殊需求,只需要在商品序号上写上注释即可。同时有些常用的注释也有对应的序号,如果不是很特别的需求的话,直接写上这些注释序号就可以了。
然后我们先前还举个例子:
功能方法代码
class A{
A1(){};
A2(){};
}
class B{
B1(){};
B2(){};
}
class C{
C1(){};
C2(){};
}
class 购物{
执行业务的方法(){
B.B1();
A.A2();
C.C1();
}
}
那么今天我们做到工作就是,这段代码变成了这样:
@TodoComponent
class A{
A1(){};
@TodoItem("买西红柿",index=1)
A2(){};
}
@TodoComponent
class B{
@TodoItem("买西红柿",index=0)
B1(){};
B2(){};
}
@TodoComponent
class C{
@TodoItem("买西红柿",index=2)
C1(){};
C2(){};
}
@TodoList("买西红柿")
class 购物{
//执行业务的方法(){
// B.B1();
// A.A2();
// C.C1();
//}
执行业务的方法(){
调度器.执行("买西红柿")
}
}
当然这还是伪代码,因为还没有正式完工,不过也快了,因为接下来的工作并不难,预计6~8篇博文完成这个框架的编写。
涉及改动
那么关于这方面的话,主要就是实现注解,对应配置组件以及,我们的容器。
然后是这里:
那么这个的话就是基本改动的地方,那么接下来我们一一说明。
解析流程
所属启动流程
那么在开始之前的话,我们来看到这部分的所属整个项目启动的一个流程:
所以的话,这个部分的话,其实还是我们的初始化部分,当我们的模板容器初始化完毕之后的话,之后就是我们的任务执行组件了。任务执行组件也是分为两大块,这里的话明天再说,重点是实现它的状态存储,让用户丝滑调用。
解析流程
那么现在我们来关系,这个这个家伙内部的一个流程:
存储结构
这里的话,流程非常清晰,所以这里的话,我先来说说使用到的数据结构有哪些。
清单模板容器
首先是我们的清单模板容器:
package com.huterox.todoscheduler.core.global;
import com.huterox.todoscheduler.core.wapper.TodoListWrapper;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 这里存放的是一个清单的模板,后期清单工厂通过这个清单模板去生成这个
* 需要运行的对象,然后的话,执行器将这个对象进行执行
*
* 这里的话,它的数据结构是这样的:
* {
* 清单名字:{
* 类型,等等信息
* 执行方法:{
* 0:需要执行的第一个方法,
* 1:需要执行的第二个方法
* }
* }
* }
* */
public class TodoListTemplateMap implements Serializable {
private static volatile TodoListTemplateMap INSTANCE;
private Map<String, TodoListWrapper> map;
private TodoListTemplateMap() {
map = new ConcurrentHashMap<>();
}
public static TodoListTemplateMap getInstance() {
if (INSTANCE == null) {
synchronized (TodoListTemplateMap.class) {
if (INSTANCE == null) {
INSTANCE = new TodoListTemplateMap();
}
}
}
return INSTANCE;
}
public Integer getSize(){
return this.map.size();
}
public void put(String key, TodoListWrapper value) {
map.put(key, value);
}
public TodoListWrapper get(String key) {
return map.get(key);
}
public boolean containKey(String key){
return map.containsKey(key);
}
}
任务清单存储
之后的话是我们的任务清单的存储,这里的话我们封装为一个Wrapper对象
package com.huterox.todoscheduler.core.wapper;
import com.huterox.todoscheduler.core.enumType.TodoListElementType;
import com.huterox.todoscheduler.core.enumType.TodoListTempleCreateType;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
* 对清单的简单封装,主要是给创建模板使用的
* */
public class TodoListWrapper implements Serializable {
private String todoListName;
private TodoListElementType todoListElementType;
private TodoListTempleCreateType createType;
private final Comparator<Integer> keyComparator = new Comparator<Integer>() {
@Override
public int compare(Integer key1, Integer key2) {
// 根据键的大小进行比较
return key1.compareTo(key2);
}
};
private final Map<Integer, TodoItemMethodWrapper> sortedMap = new TreeMap<>(keyComparator);
public TodoListTempleCreateType getCreateType() {
return createType;
}
public void setCreateType(TodoListTempleCreateType createType) {
this.createType = createType;
}
public TodoListWrapper() {
}
public String getTodoListName() {
return todoListName;
}
public void setTodoListName(String todoListName) {
this.todoListName = todoListName;
}
public TodoListElementType getTodoListElementType() {
return todoListElementType;
}
public void setTodoListElementType(TodoListElementType todoListElementType) {
this.todoListElementType = todoListElementType;
}
public Comparator<Integer> getKeyComparator() {
return keyComparator;
}
public Map<Integer, TodoItemMethodWrapper> getItemMethodMap() {
return sortedMap;
}
}
任务清单任务项
之后的话是我们对应的任务项,这个玩意的话,其实就是开头提到的这个:
所以的话,它是作用到方法上面的,我们也是把这个方法拿过来。
package com.huterox.todoscheduler.core.wapper;
import com.huterox.todoscheduler.core.enumType.TodoItemElementType;
import java.lang.reflect.Method;
/**
* 将对应的方法和类存储起来,主要是为了创建模板
* */
public class TodoItemMethodWrapper {
private Object wrapperInstance;
private Class<?> wrapperClass;
private Method wrapperMethod;
private TodoItemElementType todoItemElementType;
public TodoItemMethodWrapper() {
}
public void setWrapperInstance(Object wrapperInstance) {
this.wrapperInstance = wrapperInstance;
}
public TodoItemElementType getTodoItemElementType() {
return todoItemElementType;
}
public void setTodoItemElementType(TodoItemElementType todoItemElementType) {
this.todoItemElementType = todoItemElementType;
}
public void setWrapperClass(Class<?> wrapperClass) {
this.wrapperClass = wrapperClass;
}
public Method getWrapperMethod() {
return wrapperMethod;
}
public void setWrapperMethod(Method wrapperMethod) {
this.wrapperMethod = wrapperMethod;
}
public TodoItemMethodWrapper(Object instance) {
this.wrapperInstance = instance;
this.wrapperClass = this.wrapperInstance.getClass();
}
public Object getWrapperInstance() {
return wrapperInstance;
}
public Class<?> getWrapperClass() {
return wrapperClass;
}
}
这样一来的话,我们就把这些方法都封装好了,然后创建我们的任务清单。
定义清单
之后的话,是关于我们清单的定义,就是这个清单的结构有哪些?
任务清单
我们先来看到清单这个家伙:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TodoList {
String TodoListName() default "";
TodoListElementType TodoType() default TodoListElementType.StrongConsistency;
}
清单的话,其实就只有两个基本属性,一个是名字,然后是类型。这里又两个类型:
public enum TodoListElementType {
StrongConsistency("StrongConsistency"),
WeakConsistency("WeakConsistency");
private String elementCode;
TodoListElementType(String s) {
this.elementCode = s;
}
public String getElementCode() {
return elementCode;
}
public void setElementCode(String elementCode) {
this.elementCode = elementCode;
}
}
强类型是指,当任务失败之后,恢复全部执行前的状态,这个过程其实和你手动开启事务的过程是类似的
没错,其实这个家伙被设计出来的原因之一的话,也就是简化手动开启事务的流程,打上几个注解,实现恢复状态的方法就可以了,其他的你什么都不用管,就算服务器宕机,我们这里也有日志系统保证恢复现场。尤其是多个方法处理的时候,用这个会舒服好多。并且在微服务场景之下,可以保证每一个微服务的任务正常执行,当出现服务调用失败的时候,通过编写失败状体回调,或者状态恢复代码,你完全可以选择,在调用B服务之前,获取到B服务对应数据的状态,然后用这个框架存起来,然后编写失败恢复代码,你可以手动复原B服务对应的数据。这个过程中,你只需要实现几个接口即可,状态的保存,宕机恢复完全不需要你处理。
那么弱类型的话,就是,只是恢复当前失败的某一个任务项,以及执行完毕的不进行恢复。
之后的话,我们的任务清单又两个方式加入创建,一个的话,就是这个注解,还有一个就是这个:
这里的话,把昨天说要暴露的给暴露了,这个给用户用的。
定义任务项
这个的话,就只能通过注解创建了,在你要作为清单项执行的方法上面打上注解。
package com.huterox.todoscheduler.annotate;
import com.huterox.todoscheduler.core.enumType.TodoItemElementType;
import java.lang.annotation.*;
/**
* 任务清单当中的item,主要作用在方法上面
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TodoItem {
String[] TodoListNames();
int[] TodoListItemIndex();
TodoItemElementType[] TodoItemType();
}
这里的话,类型是数组,因为啥呢,一个方法可能在多个清单上面。这里你不用担心状态会混淆啥的,但是为了便于代码阅读,也是建议在使用的时候,不要重复太多,不然你在业务上面不好区分。
解析创建实现
okey,说完了前置的内容,我们来看到具体的实现,当然这里的话,还有这个对应的这个错位类型,当然这个没有啥,聪明如你一看就知道里面有啥,所以就不提了。我们直接看到这个解析的创建实现:
package com.huterox.todoscheduler.core.suports;
import com.huterox.todoscheduler.annotate.TodoItem;
import com.huterox.todoscheduler.annotate.TodoList;
import com.huterox.todoscheduler.common.BeanNameConvert;
import com.huterox.todoscheduler.core.enumType.TodoItemElementType;
import com.huterox.todoscheduler.core.enumType.TodoListElementType;
import com.huterox.todoscheduler.core.enumType.TodoListTempleCreateType;
import com.huterox.todoscheduler.core.global.TodoListTemplateMap;
import com.huterox.todoscheduler.core.wapper.BeanWrapper;
import com.huterox.todoscheduler.core.wapper.TodoItemMethodWrapper;
import com.huterox.todoscheduler.core.wapper.TodoListWrapper;
import com.huterox.todoscheduler.exception.TodoDuplicateDefinitionException;
import com.huterox.todoscheduler.exception.TodoItemMisMatchError;
import com.huterox.todoscheduler.exception.TodoMissingDefinitionException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 解析出清单模板,然后的话,把这个清单模板放在我们的global
* 的TodListTemplateMap当中,因为后面清单工厂和任务调度器都要用到这个玩意
* 所以TodoListTemplateMap是作为全局变量使用的
* */
public class TodoListTemplateContext {
private final TodoApplicationContext todoApplicationContext;
private final TodoListTemplateMap todoListTemplateMap;
//统计有TodoList的数量和实际清单的数量是否相同,如果不相同不好意思,创建失败
private final Map<String,Integer> ListFromTodoItemAn = new HashMap<>();
public TodoListTemplateContext() {
//拿到容器
todoApplicationContext = new TodoApplicationContext();
//拿到TodoListTemplateMap
todoListTemplateMap = TodoListTemplateMap.getInstance();
//创建清单
doCreateTodoListTemplate();
//是否为安全创建
doCheckSafeCreate();
}
private void doCheckSafeCreate(){
if(todoListTemplateMap.getSize()!=this.ListFromTodoItemAn.size()){
new TodoMissingDefinitionException("Missing task list definition",-1)
.printStackTrace();
System.exit(-1);
}
}
private void doCreateTodoListTemplate() {
for (Map.Entry<String, BeanWrapper> beanWrapperEntry : this.todoApplicationContext
.getFactoryBeanInstanceCache().entrySet())
{
/*
* 由于历史遗留问题,我们在创建IOC容器的时候,beanName和全包名都放在了
* map当中,这里主要是为了getBean方法,通过class也可以调用导致的,也就是有通过
* className.getName() 获取到容器,所以存了两个,并且这两个对象是不同的可以删掉,但是先
* 留着也许那天用上了。
* */
BeanWrapper beanWrapper = beanWrapperEntry.getValue();
Class<?> clazz = beanWrapper.getWrapperClass();
String beanName = BeanNameConvert.toLowerFirstCase(clazz.getSimpleName());
String willCreateBeanName = beanWrapperEntry.getKey();
if(beanName.equals(willCreateBeanName)){
analysisTemplate(beanWrapper);
}
}
}
/**
* 负责解析清单了,先解析出有哪些些清单
* 注意由于这里支持两种模式创建清单:
* 1. 像RabbitMQ一样直接在类上面创建
* 2. 直接通过配置类创建
* */
private void analysisTemplate(BeanWrapper beanWrapper) {
Class<?> clazz = beanWrapper.getWrapperClass();
String beanName = BeanNameConvert.toLowerFirstCase(clazz.getSimpleName());
//先解析这个TodoList类生成标签
if(clazz.isAnnotationPresent(TodoList.class)){
//发现是TodoList注解的类,于是先初始化生成
TodoList todoListAn = clazz.getAnnotation(TodoList.class);
String todoListName = todoListAn.TodoListName();
TodoListElementType todoListElementType = todoListAn.TodoType();
if("".equals(todoListName)){
//如果没有写清单的名字,那么把当前的beanName作为清单的名字
todoListName = beanName;
}
//获取到清单的名字
if(todoListTemplateMap.containKey(todoListName)){
TodoListWrapper todoListWrapper = todoListTemplateMap.get(todoListName);
if(todoListWrapper.getCreateType()==TodoListTempleCreateType.UserCreateByCode){
//用户以及用代码创建了这个玩意,现在用注解又创建了一个是不允许的
new TodoDuplicateDefinitionException("There is a duplicate task list in the configuration",-1)
.printStackTrace();
System.exit(-1);
} else if (todoListWrapper.getCreateType()==TodoListTempleCreateType.TodoListAnnotate) {
new TodoDuplicateDefinitionException("There is a duplicate task list in the annotation",-1)
.printStackTrace();
System.exit(-1);
}else if (todoListWrapper.getCreateType()==TodoListTempleCreateType.TodoItemEarly){
//如果是提取创建的,将对应的信息进行修正
todoListWrapper.setTodoListElementType(todoListElementType);
}
}else {
TodoListWrapper todoListWrapper = new TodoListWrapper();
todoListWrapper.setTodoListName(todoListName);
todoListWrapper.setTodoListElementType(todoListElementType);
todoListWrapper.setCreateType(TodoListTempleCreateType.TodoListAnnotate);
todoListTemplateMap.put(todoListName,todoListWrapper);
}
}
//解析方法,把方法加入进来
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method method:declaredMethods){
method.setAccessible(true);
if(!(method.isAnnotationPresent(TodoItem.class))) continue;
TodoItem todoItemAn = method.getAnnotation(TodoItem.class);
//获取到这个家伙对应的清单信息
String[] todoListNames = todoItemAn.TodoListNames();
int[] itemIdxes = todoItemAn.TodoListItemIndex();
TodoItemElementType[] todoItemElementTypes = todoItemAn.TodoItemType();
if(!(todoListNames.length == itemIdxes.length && todoListNames.length == todoItemElementTypes.length)){
new TodoItemMisMatchError("任务项数量与清单数量不匹配",-1).printStackTrace();
System.exit(-1);
}
//将当前的方法,加入到对应的清单当中
int indexM = 0;
for(String todoListName:todoListNames){
//进行一个简单记录,在方法当中清单的数量和实际通过配置以及TodoList注解创建的
//清单数量是不是匹配的,因为存在,清单没有先初始化,但是方法先扫描到的情况。
//所以为了安全,我们还是会选择先创建任务清单模板。最后在比对一下有没有对上数量
//如果没有对上数量,不好意思,非法创建
this.ListFromTodoItemAn.put(todoListName,1);
TodoListWrapper todoListWrapper = null;
//1. 先判断当前有没有这个清单,如果没有进行创建
if(todoListTemplateMap.containKey(todoListName)){
todoListWrapper = todoListTemplateMap.get(todoListName);
}else {
todoListWrapper = new TodoListWrapper();
todoListWrapper.setTodoListName(todoListName);
//这个信息暂时不知道,先默认生成先,后面等检测到了TodoList注解对应的真正的
//定义好了的信息,再修正就好了
todoListWrapper.setTodoListElementType(TodoListElementType.StrongConsistency);
todoListWrapper.setCreateType(TodoListTempleCreateType.TodoListAnnotate);
todoListTemplateMap.put(todoListName,todoListWrapper);
}
Map<Integer, TodoItemMethodWrapper> itemMethodMap = todoListWrapper.getItemMethodMap();
TodoItemMethodWrapper todoItemMethodWrapper = new TodoItemMethodWrapper();
todoItemMethodWrapper.setWrapperMethod(method);
todoItemMethodWrapper.setWrapperClass(clazz);
todoItemMethodWrapper.setWrapperInstance(beanWrapper.getWrapperInstance());
todoItemMethodWrapper.setTodoItemElementType(todoItemElementTypes[indexM]);
itemMethodMap.put(itemIdxes[indexM],todoItemMethodWrapper);
indexM++;
}
}
}
}
任务清单生命周期
okey,那么接下来是我们的最后一个部分,那就是这个家伙的生命周期。
当然我们还有对应的失败的恢复周期。当然过程是一样的,只是叫你多实现一个恢复接口和错误处理接口,不实现,那就走默认文章来源:https://www.toymoban.com/news/detail-638099.html
总结
okey,以上就是全部内容了,包括博文编写差不多三个小时了,不说了晚上加个班干高数去。现在是比赛中场,选手准备开启加速燃烧室。there is no way to pay nothing to get what you want!!!文章来源地址https://www.toymoban.com/news/detail-638099.html
到了这里,关于造个轮子-任务调度执行小框架-任务清单解析实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!