java调crontab_java相关:Spring Boot支持Crontab任务改造的方法

java相关:Spring Boot支持Crontab任务改造的方法

发布于 2020-6-17|

复制链接

摘记: 在以往的 Tomcat 项目中,一直习惯用 Ant 打包,使用 build.xml 配置,通过 ant -buildfile 的方式在机器上执行定时任务。虽然 Spring 本身支持定时任务,但都是服务一直运行时支持。其实在项目中,大多数定时任务,还是借助 Linux Crontab 来支持,需要时运行即可,不需要一直占用机器资源。但 Spring B ..

在以往的 Tomcat 项目中,一直习惯用 Ant 打包,使用 build.xml 配置,通过 ant -buildfile 的方式在机器上执行定时任务。虽然 Spring 本身支持定时任务,但都是服务一直运行时支持。其实在项目中,大多数定时任务,还是借助 Linux Crontab 来支持,需要时运行即可,不需要一直占用机器资源。但 Spring Boot 项目或者普通的 jar 项目,就没这么方便了。Spring Boot 提供了类似 CommandLineRunner 的方式,很好的执行常驻任务;也可以借助 ApplicationListener 和 ContextRefreshedEvent 等事件来做很多事情。借助该容器事件,一样可以做到类似 Ant 运行的方式来运行定时任务,当然需要做一些项目改动。1. 监听目标对象借助容器刷新事件来监听目标对象即可,可以认为,定时任务其实每次只是执行一种操作而已。比如这是一个写好的例子,注意不要直接用 @Service 将其放入容器中,除非容器本身没有其它自动运行的事件。

```java

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;

/**

* 不自动加入容器, 用于区分是否属于任务启动, 否则放入容器中, Spring 无法选择性执行

* 需要根据特殊参数在启动时注入

* 该监听器本身不能访问容器变量, 如果需要访问, 需要从上下文中获取对象实例后方可继续访问实例信息

* 如果其它类中启动了多线程, 是无法接管异常抛出的, 需要子线程中正确处理退出操作

* 该监听器最好不用直接做线程操作, 子类的实现不干预

*/

@Slf4j

public class TaskApplicationListener implements ApplicationListener {

/**

* 任务启动监听类标识, 启动时注入

* 即是 java -Dspring.task.class=com.github.zhgxun.learn.task.TestTask -jar learn.jar

*/

private static final String SPRING_TASK_CLASS = "spring.task.class";

/**

* 支持该注解的方法个数, 目前仅一个

* 可以理解为控制台一次执行一个类, 依赖的任务应该通过其它方式控制依赖

*/

private static final int SUPPORT_METHOD_COUNT = 1;

/**

* 保存当前容器运行上下文

*/

private ApplicationContext context;

/**

* 监听容器刷新事件

*

* @param event 容器刷新事件

*/

@Override

@SuppressWarnings("unchecked")

public void onApplicationEvent(ContextRefreshedEvent event) {

context = event.getApplicationContext();

// 不存在时可能为正常的容器启动运行, 无需关心

String taskClass = System.getProperty(SPRING_TASK_CLASS);

log.info("ScheduleTask spring task Class: {}", taskClass);

if (taskClass != null) {

try {

// 获取类字节码文件

Class clazz = findClass(taskClass);

// 尝试从内容上下文中获取已加载的目标类对象实例, 这个类实例是已经加载到容器内的对象实例, 即可以获取类的信息

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 {

// 需要确保容器正常出发停止事件, 否则容器会僵尸卡死

shutdown();

}

}

}

/**

* 根据class路径名称查找类文件

*

* @param clazz 类名称

* @return 类对象

* @throws ClassNotFoundException ClassNotFoundException

*/

private Class findClass(String clazz) throws ClassNotFoundException {

return Class.forName(clazz);

}

/**

* 获取目标对象中符合条件的方法

*

* @param object 目标对象实例

* @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);

}

/**

* 执行目标对象方法

*

* @param method 目标方法

* @param object 目标对象实例

* @throws IllegalAccessException IllegalAccessException

* @throws InvocationTargetException InvocationTargetException

*/

private void invoke(Method method, Object object) throws IllegalAccessException, InvocationTargetException {

method.invoke(object);

}

/**

* 执行完毕退出运行容器, 并将返回值交给执行环节, 比如控制台等

*/

private void shutdown() {

log.info("shutdown ...");

System.exit(SpringApplication.exit(context));

}

}

```

其实该处仅需要启动执行即可,容器启动完毕事件也是可以的。2. 标识目标方法

目标方法的标识,最方便的是使用注解标注。

```java

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. 编写任务

```java

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("模拟启动线程操作");

for (int i = 0; i 4. 启动改造

启动时需要做一些调整,即跟普通的启动区分开。这也是为什么不要把监听目标对象直接放入容器中的原因,在这里显示添加到容器中,这样就不影响项目中类似 CommandLineRunner 的功能,毕竟这种功能是容器启动完毕就能运行的。如果要改造,会涉及到很多硬编码。

```java

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);

// 根据启动注入参数判断是否为任务动作即可, 否则不干预启动

if (System.getProperty("spring.task.class") != null) {

builder.listeners(new TaskApplicationListener()).run(args);

} else {

builder.run(args);

}

}

}

```

5. 启动注入

-Dspring.task.class 即是启动注入标识,当然这个标识不要跟默认的参数混淆,需要区分开,否则可能始终获取到系统参数,而无法获取用户参数。

```java

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

```

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值