好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring Boot支持Crontab任务改造的方法

在以往的 tomcat 项目中,一直习惯用 ant 打包,使用 build.xml 配置,通过 ant -buildfile 的方式在机器上执行定时任务。虽然 spring 本身支持定时任务,但都是服务一直运行时支持。其实在项目中,大多数定时任务,还是借助 linux crontab 来支持,需要时运行即可,不需要一直占用机器资源。但 spring boot 项目或者普通的 jar 项目,就没这么方便了。

spring boot 提供了类似 commandlinerunner 的方式,很好的执行常驻任务;也可以借助 applicationlistener 和 contextrefreshedevent 等事件来做很多事情。借助该容器事件,一样可以做到类似 ant 运行的方式来运行定时任务,当然需要做一些项目改动。

1. 监听目标对象

借助容器刷新事件来监听目标对象即可,可以认为,定时任务其实每次只是执行一种操作而已。

比如这是一个写好的例子,注意不要直接用 @service 将其放入容器中,除非容器本身没有其它自动运行的事件。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

package com.github.zhgxun.learn测试数据mon.task;

 

import com.github.zhgxun.learn测试数据mon.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<contextrefreshedevent> {

   /**

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

    * 即是 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<method> 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. 标识目标方法

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

package com.github.zhgxun.learn测试数据mon.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. 编写任务

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

package com.github.zhgxun.learn.task;

 

import com.github.zhgxun.learn测试数据mon.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 < 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 + " 个线程启动..." + thread.currentthread().getname());

     if (i == 2 ) {

       throw new runtimeexception( "模拟运行时异常" );

     }

     if (i == 3 ) {

       // 除数不为0

       int a = i / j;

     }

     // 未对字符串对象赋值, 获取长度报空指针错误

     if (i == 4 ) {

       system.out.println(s.length());

     }

   }

}

4. 启动改造

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package com.github.zhgxun.learn;

 

import com.github.zhgxun.learn测试数据mon.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 即是启动注入标识,当然这个标识不要跟默认的参数混淆,需要区分开,否则可能始终获取到系统参数,而无法获取用户参数。

?

1

java -dspring.task. class =com.github.zhgxun.learn.task.testtask -jar target/learn.jar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:https://segmentfault测试数据/a/1190000017946999

查看更多关于Spring Boot支持Crontab任务改造的方法的详细内容...

  阅读:11次