成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Spring Boot支持Crontab任務(wù)改造

fou7 / 1475人閱讀

摘要:雖然本身支持定時(shí)任務(wù),但都是服務(wù)一直運(yùn)行時(shí)支持。監(jiān)聽目標(biāo)對(duì)象借助容器刷新事件來(lái)監(jiān)聽目標(biāo)對(duì)象即可,可以認(rèn)為,定時(shí)任務(wù)其實(shí)每次只是執(zhí)行一種操作而已。

在以往的 Tomcat 項(xiàng)目中,一直習(xí)慣用 Ant 打包,使用 build.xml 配置,通過(guò) ant -buildfile 的方式在機(jī)器上執(zhí)行定時(shí)任務(wù)。雖然 Spring 本身支持定時(shí)任務(wù),但都是服務(wù)一直運(yùn)行時(shí)支持。其實(shí)在項(xiàng)目中,大多數(shù)定時(shí)任務(wù),還是借助 Linux Crontab 來(lái)支持,需要時(shí)運(yùn)行即可,不需要一直占用機(jī)器資源。但 Spring Boot 項(xiàng)目或者普通的 jar 項(xiàng)目,就沒這么方便了。

Spring Boot 提供了類似 CommandLineRunner 的方式,很好的執(zhí)行常駐任務(wù);也可以借助 ApplicationListener 和 ContextRefreshedEvent 等事件來(lái)做很多事情。借助該容器事件,一樣可以做到類似 Ant 運(yùn)行的方式來(lái)運(yùn)行定時(shí)任務(wù),當(dāng)然需要做一些項(xiàng)目改動(dòng)。

1. 監(jiān)聽目標(biāo)對(duì)象

借助容器刷新事件來(lái)監(jiān)聽目標(biāo)對(duì)象即可,可以認(rèn)為,定時(shí)任務(wù)其實(shí)每次只是執(zhí)行一種操作而已。

比如這是一個(gè)寫好的例子,注意不要直接用 @Service 將其放入容器中,除非容器本身沒有其它自動(dòng)運(yùn)行的事件。

package com.github.zhgxun.learn.common.task;

import com.github.zhgxun.learn.common.task.annotation.ScheduleTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 不自動(dòng)加入容器, 用于區(qū)分是否屬于任務(wù)啟動(dòng), 否則放入容器中, Spring 無(wú)法選擇性執(zhí)行
 * 需要根據(jù)特殊參數(shù)在啟動(dòng)時(shí)注入
 * 該監(jiān)聽器本身不能訪問(wèn)容器變量, 如果需要訪問(wèn), 需要從上下文中獲取對(duì)象實(shí)例后方可繼續(xù)訪問(wèn)實(shí)例信息
 * 如果其它類中啟動(dòng)了多線程, 是無(wú)法接管異常拋出的, 需要子線程中正確處理退出操作
 * 該監(jiān)聽器最好不用直接做線程操作, 子類的實(shí)現(xiàn)不干預(yù)
 */
@Slf4j
public class TaskApplicationListener implements ApplicationListener {
    /**
     * 任務(wù)啟動(dòng)監(jiān)聽類標(biāo)識(shí), 啟動(dòng)時(shí)注入
     * 即是 java -Dspring.task.class=com.github.zhgxun.learn.task.TestTask -jar learn.jar
     */
    private static final String SPRING_TASK_CLASS = "spring.task.class";

    /**
     * 支持該注解的方法個(gè)數(shù), 目前僅一個(gè)
     * 可以理解為控制臺(tái)一次執(zhí)行一個(gè)類, 依賴的任務(wù)應(yīng)該通過(guò)其它方式控制依賴
     */
    private static final int SUPPORT_METHOD_COUNT = 1;

    /**
     * 保存當(dāng)前容器運(yùn)行上下文
     */
    private ApplicationContext context;

    /**
     * 監(jiān)聽容器刷新事件
     *
     * @param event 容器刷新事件
     */
    @Override
    @SuppressWarnings("unchecked")
    public void onApplicationEvent(ContextRefreshedEvent event) {
        context = event.getApplicationContext();
        // 不存在時(shí)可能為正常的容器啟動(dòng)運(yùn)行, 無(wú)需關(guān)心
        String taskClass = System.getProperty(SPRING_TASK_CLASS);
        log.info("ScheduleTask spring task Class: {}", taskClass);
        if (taskClass != null) {
            try {
                // 獲取類字節(jié)碼文件
                Class clazz = findClass(taskClass);

                // 嘗試從內(nèi)容上下文中獲取已加載的目標(biāo)類對(duì)象實(shí)例, 這個(gè)類實(shí)例是已經(jīng)加載到容器內(nèi)的對(duì)象實(shí)例, 即可以獲取類的信息
                Object object = context.getBean(clazz);

                Method method = findMethod(object);

                log.info("start to run task Class: {}, Method: {}", taskClass, method.getName());
                invoke(method, object);
            } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            } finally {
                // 需要確保容器正常出發(fā)停止事件, 否則容器會(huì)僵尸卡死
                shutdown();
            }
        }
    }

    /**
     * 根據(jù)class路徑名稱查找類文件
     *
     * @param clazz 類名稱
     * @return 類對(duì)象
     * @throws ClassNotFoundException ClassNotFoundException
     */
    private Class findClass(String clazz) throws ClassNotFoundException {
        return Class.forName(clazz);
    }

    /**
     * 獲取目標(biāo)對(duì)象中符合條件的方法
     *
     * @param object 目標(biāo)對(duì)象實(shí)例
     * @return 符合條件的方法
     */
    private Method findMethod(Object object) {
        Method[] methods = object.getClass().getDeclaredMethods();
        List schedules = Stream.of(methods)
                .filter(method -> method.isAnnotationPresent(ScheduleTask.class))
                .collect(Collectors.toList());
        if (schedules.size() != SUPPORT_METHOD_COUNT) {
            throw new IllegalStateException("only one method should be annotated with @ScheduleTask, but found "
                    + schedules.size());
        }
        return schedules.get(0);
    }

    /**
     * 執(zhí)行目標(biāo)對(duì)象方法
     *
     * @param method 目標(biāo)方法
     * @param object 目標(biāo)對(duì)象實(shí)例
     * @throws IllegalAccessException    IllegalAccessException
     * @throws InvocationTargetException InvocationTargetException
     */
    private void invoke(Method method, Object object) throws IllegalAccessException, InvocationTargetException {
        method.invoke(object);
    }

    /**
     * 執(zhí)行完畢退出運(yùn)行容器, 并將返回值交給執(zhí)行環(huán)節(jié), 比如控制臺(tái)等
     */
    private void shutdown() {
        log.info("shutdown ...");
        System.exit(SpringApplication.exit(context));
    }
}

其實(shí)該處僅需要啟動(dòng)執(zhí)行即可,容器啟動(dòng)完畢事件也是可以的。

2. 標(biāo)識(shí)目標(biāo)方法

目標(biāo)方法的標(biāo)識(shí),最方便的是使用注解標(biāo)注。

package com.github.zhgxun.learn.common.task.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ScheduleTask {
}
3. 編寫任務(wù)
package com.github.zhgxun.learn.task;

import com.github.zhgxun.learn.common.task.annotation.ScheduleTask;
import com.github.zhgxun.learn.service.first.LaunchInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TestTask {

    @Autowired
    private LaunchInfoService launchInfoService;

    @ScheduleTask
    public void test() {
        log.info("Start task ...");
        log.info("LaunchInfoList: {}", launchInfoService.findAll());

        log.info("模擬啟動(dòng)線程操作");
        for (int i = 0; i < 5; i++) {
            new MyTask(i).start();
        }

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyTask extends Thread {
    private int i;
    private int j;
    private String s;

    public MyTask(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("第 " + i + " 個(gè)線程啟動(dòng)..." + Thread.currentThread().getName());
        if (i == 2) {
            throw new RuntimeException("模擬運(yùn)行時(shí)異常");
        }
        if (i == 3) {
            // 除數(shù)不為0
            int a = i / j;
        }
        // 未對(duì)字符串對(duì)象賦值, 獲取長(zhǎng)度報(bào)空指針錯(cuò)誤
        if (i == 4) {
            System.out.println(s.length());
        }
    }
}
4. 啟動(dòng)改造

啟動(dòng)時(shí)需要做一些調(diào)整,即跟普通的啟動(dòng)區(qū)分開。這也是為什么不要把監(jiān)聽目標(biāo)對(duì)象直接放入容器中的原因,在這里顯示添加到容器中,這樣就不影響項(xiàng)目中類似 CommandLineRunner 的功能,畢竟這種功能是容器啟動(dòng)完畢就能運(yùn)行的。如果要改造,會(huì)涉及到很多硬編碼。

package com.github.zhgxun.learn;

import com.github.zhgxun.learn.common.task.TaskApplicationListener;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class LearnApplication {

    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(LearnApplication.class);
        // 根據(jù)啟動(dòng)注入?yún)?shù)判斷是否為任務(wù)動(dòng)作即可, 否則不干預(yù)啟動(dòng)
        if (System.getProperty("spring.task.class") != null) {
            builder.listeners(new TaskApplicationListener()).run(args);
        } else {
            builder.run(args);
        }
    }
}
5. 啟動(dòng)注入

-Dspring.task.class 即是啟動(dòng)注入標(biāo)識(shí),當(dāng)然這個(gè)標(biāo)識(shí)不要跟默認(rèn)的參數(shù)混淆,需要區(qū)分開,否則可能始終獲取到系統(tǒng)參數(shù),而無(wú)法獲取用戶參數(shù)。

java -Dspring.task.class=com.github.zhgxun.learn.task.TestTask -jar target/learn.jar

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73078.html

相關(guān)文章

  • springboot整合quarzt實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)

    摘要:而我這里定時(shí)任務(wù)的觸發(fā)是要通過(guò)接口的方式來(lái)觸發(fā),所以只用實(shí)現(xiàn)以下的調(diào)度器即可。我這里簡(jiǎn)單說(shuō)下任務(wù)的調(diào)度器,具體的任務(wù)類,觸發(fā)器,任務(wù)什么時(shí)候執(zhí)行是由它決定的。遇到的坑解決方式這個(gè)是因?yàn)椴患嫒莸膯?wèn)題,所以使用是不會(huì)出現(xiàn)這個(gè)錯(cuò)誤的。 實(shí)現(xiàn)定時(shí)任務(wù)的幾種方式: 1.使用linux的crontab 優(yōu)點(diǎn): 1.使用方式很簡(jiǎn)單,只要在crontab中寫好 2.隨時(shí)可以修改,不需要...

    hoohack 評(píng)論0 收藏0
  • 海航生態(tài)科技輿情大數(shù)據(jù)平臺(tái)容器化改造

    摘要:本文轉(zhuǎn)載自微信公眾號(hào)賬號(hào),作者為海航生態(tài)科技技術(shù)研究院大數(shù)據(jù)開發(fā)工程師高顏。文章介紹了海航生態(tài)科技輿情大數(shù)據(jù)平臺(tái)的容器化改造經(jīng)驗(yàn),包括初期技術(shù)架構(gòu)應(yīng)用容器化架構(gòu)遷移持續(xù)發(fā)布與部署。 本文轉(zhuǎn)載自微信公眾號(hào)Docker(賬號(hào):dockerone),作者為海航生態(tài)科技技術(shù)研究院大數(shù)據(jù)開發(fā)工程師高顏。 文章介紹了海航生態(tài)科技輿情大數(shù)據(jù)平臺(tái)的容器化改造經(jīng)驗(yàn),包括初期技術(shù)架構(gòu)、應(yīng)用容器化、架構(gòu)遷...

    idealcn 評(píng)論0 收藏0
  • SpringBoot 1024行代碼 - 定時(shí)任務(wù)

    摘要:前言用組件實(shí)現(xiàn)簡(jiǎn)易的定時(shí)任務(wù)功能。步驟創(chuàng)建一個(gè)啟動(dòng)類注意,是關(guān)鍵,加了這個(gè)注解才能啟動(dòng)定時(shí)任務(wù)。編寫定時(shí)任務(wù)方法可以實(shí)現(xiàn)兩種定時(shí),一種是每個(gè)一段時(shí)間執(zhí)行一次方法,另一種是執(zhí)行一次方法之后間隔若干時(shí)間后再執(zhí)行下一次。 前言 用Spring-Context組件實(shí)現(xiàn)簡(jiǎn)易的定時(shí)任務(wù)功能。只可以支持較簡(jiǎn)單的業(yè)務(wù)場(chǎng)景,實(shí)用價(jià)值不高。如果想要投放到生產(chǎn)環(huán)境,需要進(jìn)行一些改造。 步驟 1. pom.x...

    fou7 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<