造个轮子-任务调度执行小框架-任务清单解析实现

前言

okey~每日编码一坤时,昨天的话我们已经实现了这个框架的IOC容器。通过这个IOC容器,我们就可以非常轻松地进行后续的操作,于是,我们接着这个工作,去完成这个任务清单的解析。

昨天的话,阐述了一下这个框架解决了哪些问题,那么接下来,是如何使用这个家伙。以及今天的任务解析清单主要解决了哪些问题,也就是实现了哪些特性。

当然这边这个项目是开源的,只是还没有做好没有上传仓库而已。

实现特性

okey,我们先来看看实现的特性,能够做什么。先前我们说过,这样的一个场景:

假设你要去超市买菜:为了方便买菜,你可以写一个购物清单,然后按照购物清单上面的每一个项,去购买东西。每一次写购物清单的时候,都要写上物品的名字,每次这样写实在是太麻烦了。不过,好在超市的商品有限,于是我们把这些商品都编上序号。于是下一次在书写购物清单的时候,只需要写上这个商品对应的编号就可以了,如果有特殊需求,只需要在商品序号上写上注释即可。同时有些常用的注释也有对应的序号,如果不是很特别的需求的话,直接写上这些注释序号就可以了。

然后我们先前还举个例子:

java 复制代码
功能方法代码
class A{
	A1(){};
	A2(){};
}

class B{
	B1(){};
	B2(){};
}

class C{
	C1(){};
	C2(){};
}

class 购物{
	执行业务的方法(){
		B.B1();
		A.A2();
		C.C1();
	}
}

那么今天我们做到工作就是,这段代码变成了这样:

java 复制代码
@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篇博文完成这个框架的编写。

涉及改动

那么关于这方面的话,主要就是实现注解,对应配置组件以及,我们的容器。

然后是这里:

那么这个的话就是基本改动的地方,那么接下来我们一一说明。

解析流程

所属启动流程

那么在开始之前的话,我们来看到这部分的所属整个项目启动的一个流程: 所以的话,这个部分的话,其实还是我们的初始化部分,当我们的模板容器初始化完毕之后的话,之后就是我们的任务执行组件了。任务执行组件也是分为两大块,这里的话明天再说,重点是实现它的状态存储,让用户丝滑调用。

解析流程

那么现在我们来关系,这个这个家伙内部的一个流程:

存储结构

这里的话,流程非常清晰,所以这里的话,我先来说说使用到的数据结构有哪些。

清单模板容器

首先是我们的清单模板容器:

java 复制代码
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对象

java 复制代码
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;
    }
}

任务清单任务项

之后的话是我们对应的任务项,这个玩意的话,其实就是开头提到的这个:

所以的话,它是作用到方法上面的,我们也是把这个方法拿过来。

java 复制代码
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;
    }
}

这样一来的话,我们就把这些方法都封装好了,然后创建我们的任务清单。

定义清单

之后的话,是关于我们清单的定义,就是这个清单的结构有哪些?

任务清单

我们先来看到清单这个家伙:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TodoList {

    String TodoListName() default "";
    TodoListElementType TodoType() default TodoListElementType.StrongConsistency;

}

清单的话,其实就只有两个基本属性,一个是名字,然后是类型。这里又两个类型:

java 复制代码
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服务对应的数据。这个过程中,你只需要实现几个接口即可,状态的保存,宕机恢复完全不需要你处理。

那么弱类型的话,就是,只是恢复当前失败的某一个任务项,以及执行完毕的不进行恢复。

之后的话,我们的任务清单又两个方式加入创建,一个的话,就是这个注解,还有一个就是这个: 这里的话,把昨天说要暴露的给暴露了,这个给用户用的。

定义任务项

这个的话,就只能通过注解创建了,在你要作为清单项执行的方法上面打上注解。

java 复制代码
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,说完了前置的内容,我们来看到具体的实现,当然这里的话,还有这个对应的这个错位类型,当然这个没有啥,聪明如你一看就知道里面有啥,所以就不提了。我们直接看到这个解析的创建实现:

java 复制代码
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,那么接下来是我们的最后一个部分,那就是这个家伙的生命周期。

当然我们还有对应的失败的恢复周期。当然过程是一样的,只是叫你多实现一个恢复接口和错误处理接口,不实现,那就走默认

总结

okey,以上就是全部内容了,包括博文编写差不多三个小时了,不说了晚上加个班干高数去。现在是比赛中场,选手准备开启加速燃烧室。there is no way to pay nothing to get what you want!!!

相关推荐
2401_857622663 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589363 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没4 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
杨哥带你写代码6 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
AskHarries7 小时前
读《show your work》的一点感悟
后端
A尘埃7 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23077 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code7 小时前
(Django)初步使用
后端·python·django
代码之光_19807 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端