手寫快速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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章