手写快速web开发框架--集成定时任务

 

今天写的东西有点多,集成了RabbitMQ/定时任务到手写的web开发框架里,并且写了定时任务热部署

接下来看定时任务的集成

首先你懂得,定义一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Scheduling {
    String cron();
}

 

然后写了一个类处理器来处理项目中的所有类,这个类会在初始化框架的时候执行

  private void instanceTask() throws Exception {
        TaskClassHandler taskClassHandler=new TaskClassHandler();
        for (Class c : classes) {
            taskClassHandler.handleClass(c);
        }
    }

我们来看看这个handleClass方法到底做了什么


public class TaskClassHandler extends AbsClassHandler {

    @Override
    public void handleClass(Class c) throws Exception {
        for(Method m:c.getDeclaredMethods()){
            if(!m.isAnnotationPresent(Scheduling.class))
                continue;

            Scheduling scheduling= m.getAnnotation(Scheduling.class);
            String cron=scheduling.cron();

            //开始创建任务
            TaskUtil.startTask(cron,m,c.newInstance());
        }
    }
}

遍历类的每个方法,如果没有加Scheduling注解,就跳过

然后通过cron表达式/方法/实例对象来开启定时任务

我们来看看工具类完成了什么操作

/**
     * 通过cron 表达式和方法和对象来开启任务
     * @param cron
     * @param method
     * @param obj
     */
    public static void startTask(String cron, Method method,Object obj) throws ParseException, SchedulerException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException {
        CronTriggerImpl cronTrigger = new CronTriggerImpl();
        CronExpression cexp = new CronExpression(cron);

        cronTrigger.setCronExpression(cexp);

        SchedulerFactory schedulerFactory = new StdSchedulerFactory();

        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail=new JobDetailImpl(System.currentTimeMillis()+"", new Job() {
            @Override
            public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
                try {
                    method.invoke(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }.getClass());

        cronTrigger.setName(System.currentTimeMillis()+"");
        scheduler.scheduleJob(jobDetail, cronTrigger);

        scheduler.start();
    }

创建trigger并且设置了cron表达式进去,然后创建调度器,并且创建了一个jobDetail并设置了具体要做的任务,那就是通过反射执行方法,然后再给调度器设置任务和cron表达式进去,然后开启任务

这里我遇到了一个问题,那就是给jobDetail直接通过new 接口,然后override方法,然后把得到的class设置进去,这样任务开启后并不会执行,但是如果我手动创建一个实现了Job接口的类进去,然后设置到jobDetail,任务启动后能正常执行,不清楚原因,希望有大佬给我指点指点

然后这样就执行任务了

 

接下来还没完,我们在运行项目的时候可能会遇到热部署任务的需求

于是,我又写了一个方法来热部署定时任务

首先,我们定义一个配置文件,这个配置文件的格式是

[{
   classPath:"d:\java\base\WinterBatis\src\main\java\org\zj\winterbatis\jj.java",
   cron:"*/5 * * * * *"
 }]

然后我们解析这个配置文件成List<Task> ,你懂得,这个bean有 两个字段,java文件的绝对路径,还有cron表达式

得到这两个属性后,我们接下来就要考虑怎么把这个java文件弄成class文件了

正常情况下,我们可以通过ide来编译成class文件,这里我们要通过java代码来编译java文件,是不是觉得有点奇怪

编译成class文件后,后面的就好说了,直接遍历这个类的方法,然后对每个方法调用上面的开启任务的方法就行了

接下来看代码

/**
     * 监听指定的配置文件,然后通过配置文件来添加任务
     * @param file
     */
    public static void listenConfig(File file) throws IOException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(;;){
                    try{
                    //把json读取出来
                    byte[] buffer=new byte[1024];
                    FileInputStream fis=new FileInputStream(file);
                    int len=-1;
                    StringBuilder sb=new StringBuilder();
                    while((len=fis.read(buffer))!=-1){
                        sb.append(new String(buffer,0,len));
                    }
                    fis.close();

                    JavaType javaType = new ObjectMapper().getTypeFactory().constructParametricType(ArrayList.class, Task.class);

                    List<Task> tasks = new ObjectMapper().readValue(sb.toString(), javaType);

                    for(Task t:tasks){
                        String javaPath = t.getJavaPath();
                        String className=t.getClassName();
                        //把这个路径的java文件编译成class,然后
                        Class compile = ClassUtil.getCompile(className, new File(javaPath), new File("d:/cc"));

                        //如果这个任务已经在执行了就返回
                        if(autoTaskClassNames.contains(className))
                            continue;

                        //这里先对里面的字段进行注入吧,后面加
                        Object o = compile.newInstance();
                        //获得所有方法,然后再编译
                        for(Method m:compile.getDeclaredMethods()){
                            if(!m.isAnnotationPresent(Scheduling.class))
                                return;
                            Scheduling annotation = m.getAnnotation(Scheduling.class);
                            startTask(annotation.cron(),m,o);
                        }

                    }

                }catch (Exception e){

                    }
                }
            }
        }).start();

    }

解析json然后编译java文件为class文件,然后看任务是否已经在执行,然后再遍历每个方法来开启任务,接下来看怎么编译java文件为class文件

 

/**
     * 把指定的java文件编译成class然后放到指定路径
     *
     * @param from
     * @param to
     * @return
     * @throws Exception
     */
    public static Class getCompile(String className,File from, File to) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        
        DiagnosticCollector diagnostics = new DiagnosticCollector();

        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        
        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays
                .asList(from.getAbsolutePath()));

        // options命令行选项
        Iterable<String> options = Arrays.asList("-d",
                to.getAbsolutePath());// 指定的路径一定要存在,javac不会自己创建文件夹
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null,
                compilationUnits);

        //编译java文件
        boolean success = task.call();
        fileManager.close();
        System.out.println((success) ? "编译成功" : "编译失败");
        return getClass(className,new File(getClassPath(to,className)));
    }

通过JavaCompiler来开启编译任务并且放到指定的位置

编译成class文件后,然后再通过自定义的类加载器来获得Class对象

/**
     * 通过class文件来获得class
     * @param className
     * @param file
     * @return
     * @throws IOException
     */
    public Class getClass(String className, File file) throws IOException, ClassNotFoundException {
        if(file.isDirectory())
            return null;
        byte[] bytes=getByte(file);
        Class c;
        try {
            c=this.defineClass(className, bytes, 0, bytes.length);
        }
        catch (Exception e){
            c=Class.forName(className);
        }
        return c;
    }

首先把Class文件转成byte[]数组,然后通过defineClass文件传递类名和类数据的数组来获得一个Class对象

获得对象后,直接遍历每个方法,然后通过方法上的注解信息来开启定时任务执行

头疼,我休息下(打游戏)

完整代码在我的 GitHub

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值