Ubuntu通过crontab定时执行java程序

在Linux上定时执行java的方式有很多种,例如可以直接使用Spring的定时器,但是唯一一点不好的地方就是Spring的定时器需要依赖容器.如果容器没有启动,那么程序是跑不起来的.

crontab的好处是它本身是Linux的一个程序,可以直接通过java命令执行java程序,无需容器的依赖.

1、使用命令执行java

首先要知道在Ubuntu上如何使用命令跑java程序。

1、先写一个简单main类,放在/root/sh目录下:

public class JobRunnerTest {
    public static void main(String[] args) {
        System.out.println("start job ...");
        for(String s : args){
            System.out.println(s);
        }
    }
}

2、编译:

cd /root/sh
javac JobRunnerTest

3、执行:

java -classpath /root/sh/ JobRunnerTest

其中classpath参数是加载一些依赖的jar包以及class文件,必须要加上,否则程序将会不知道去哪找JobRunnerTest.class。

4、输出:

start job ...

说明程序跑成功了。

如果把执行java程序的命令配置到crontab中,就已经可以定时执行java了。

2、结合Spring

大多数时候执行job并不是单一的一个类,而是多个类,并且有可能需要连接数据库,这个时候使用Spring管理多个类和数据库连接是非常方便的。

1、创建job接口Job.java:

package online.pangge.exam.job;


import java.util.List;


public interface Job<T> {
    /**
     * 获取数据
     * @return
     */
    List<T> dataSource();

    /**
     * 针对每一条数据都执行一次
     * @param t
     */
    void process(T t);
}

2、创建job执行类JobExecute.java:

package online.pangge.exam.job;

import java.util.List;

/**
 * Created by Pangge on 2017/11/5.
 */
public class JobExecute<T> {
    public void start(Job job){
        //调用job接口的dataSource方法获取数据
        List<T> datas =job.dataSource();
        //循环执行job的process方法,每次传入一个元素,也可以写成process方法接收List<T>,然后直接把datas传过去,这里写成循环调用process主要是考虑到可以使用callable实现并发调用process
        for (T t: datas){
            job.process(t);
        }
    }
}


3、创建job执行入口JobRunner.java

package online.pangge.exam.job;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Created by Pangge on 2017/11/5.
 */
public class JobRunner {
    public static void main(String[] args) {
        //获取spring配置文件
        ApplicationContext beanFactory = new FileSystemXmlApplicationContext("classpath:application.xml");
        //根据job名字获取job
        Job job = (Job) beanFactory.getBean(args[0]);
        //初始化job执行类
        JobExecute execute = new JobExecute();
        //调用job执行类的start方法,传入bean
        execute.start(job);
    }
}

4、创建执行java的shell脚本/root/sh/start.sh:

#!/bin/sh
#java所在目录
JAVA_HOME="/root/jdk1.7.0_45"
#执行的角色,一般不推荐使用root
RUNNING_USER=root
#class文件所在目录的上一级
APP_HOME=/root/weixin/tiger/website/target/website/WEB-INF
#job入口类带全限定名的名字,例如online.pangge.exam.job.JobRunner
APP_MAINCLASS=$1
CLASSPATH=$APP_HOME/classes
#拼接classpath变量,这里会逐个jar包都拼接进去
for i in "$APP_HOME"/lib/*.jar; do
   CLASSPATH="$CLASSPATH":"$i"
done
#运行时的参数,可以不提供,按照默认的参数运行,如果不提供注意下面JAVA_CMO也应该去掉$JAVA_OPTS参数
JAVA_OPTS="-ms512m -mx512m -Xmn256m -Djava.awt.headless=true -XX:MaxPermSize=128m"
#需要执行的job名称,不需要全限定名,因为是通过Spring获取的,知道名字即可
$PARAM=$2
#拼接执行的参数,>/root/sh/$PARAM-`date +%Y-%m-%d-%H%M%S`.log 2>&1 & 是重定向程序输出到/root/sh目录下,对应的job名称的.log文件中
JAVA_CMO="nohup $JAVA_HOME/bin/java $JAVA_OPT -classpath $CLASSPATH $APP_MAINCLASS $3 >/root/sh/$PARAM-`date +%Y-%m-%d-%H%M%S`.log 2>&1 &"
#执行的命令,注意- $RUNNING_USER中间是有空格的
su - $RUNNING_USER -c "$JAVA_CMO"

5、创建一个简单的job实现类TestJob.java

package online.pangge.exam.job;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Component("TestJob")
public class TestJob implements Job<String> {
    private static Logger log = LoggerFactory.getLogger(TestJob.class);
    @Override
    public List dataSource() {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        return list;
    }

    @Override
    public void process(String s) {
        log.info("do process");
        System.out.println("test job = = = = =,Time =  "+new Date());
    }
}

默认使用注解@Component,名称为TestJob。也可以使用xml方式配置在application.xml中。

6、编译整个项目:

这一步根据使用的编译工具,例如使用maven的,可以切换到pom.xml所在目录,使用mvn package进行打包编译。

7、使用命令执行job:

/root/sh/start.sh online.pangge.exam.job.JobRunner TestJob

可以看到/root/sh/目录下面已经有了log文件:

ls /root/sh

TestJob-2018-04-01-203609.log

cat TestJob-2018-04-01-203609.log

可以看到除了spring启动的信息外,还输出以下内容:

test job = = = = =,Time =  Sun Apr 01 20:36:12 CST 2018
test job = = = = =,Time =  Sun Apr 01 20:36:12 CST 2018
test job = = = = =,Time =  Sun Apr 01 20:36:12 CST 2018
test job = = = = =,Time =  Sun Apr 01 20:36:12 CST 2018

test job = = = = =,Time =  Sun Apr 01 20:36:12 CST 2018

说明job已经成功执行。

3、将job配置到crontab中:

Ubuntu默认安装了crontab,使用crontab -e即可打开配置文件,需要注意的是这样打开的配置文件只针对当前用户。

1、打开crontab的配置文件

crontab -e

输出:

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command

2、添加以下配置:

0 0 */1 * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob

这条命令是每天的0点0时执行TestJob。

3、crontab执行时间的格式:


另外需要注意的是通配符:

星号(*):代表所有可能的值。

例如:

* * * * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob
代表每分钟执行一次脚本。

逗号(,):可以用逗号隔开的值指定一个列表范围。

例如:

1,3,5,7,9 * * * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob

代表每小时的第1、3、5、7、9分钟均执行一次脚本。

中杠(-):可以用整数之间的中杠表示一个整数范围。

例如:

1-30 * * * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob
代表每个小时的前三十分钟每分钟都执行一次脚本。

正斜线(/):可以用正斜线指定时间的间隔频率。

例如:

*/10 * * * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob

代表每十分钟执行一次脚本。

这些通配符可以同时使用,例如:

1-30/10 * * * * /root/sh/start.sh online.pangge.exam.job.JobRunner TestJob
代表每小时的前三十分钟里面,每十分钟执行一次脚本,也就是说一个小时里面只执行三次。

4、异常解决

ClassDefFoundError: javax/servlet/ServletContext
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
	at org.springframework.context.support.FileSystemXmlApplicationContext.<init>(FileSystemXmlApplicationContext.java:140)
	at org.springframework.context.support.FileSystemXmlApplicationContext.<init>(FileSystemXmlApplicationContext.java:84)
	at online.pangge.exam.job.JobRunner.main(JobRunner.java:11)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: public online.pangge.exam.service.ISubjectService online.pangge.wechat.web.controller.ExamController.subjectService; nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletContext
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:573)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
	... 13 more
Caused by: java.lang.NoClassDefFoundError: javax/servlet/ServletContext
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2615)
	at java.lang.Class.getDeclaredMethods(Class.java:1860)
	at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:609)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:521)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:507)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:241)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1069)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getSingletonFactoryBeanForTypeCheck(AbstractAutowireCapableBeanFactory.java:865)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:796)
	at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:544)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:447)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:423)
	at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:220)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545)
	... 15 more
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletContext
	at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:358)

找不到类,这明显是没有加载到某一个类,之前怀疑是application.xml没有正确加载,但是考虑到没有加载application.xml文件spring应该无法启动,排除这个原因。

其中无法加载的类是javax.servlet.ServletContext,猜测应该是jar包没有正确导入,因为这些jar包在tomcat中已经有了,所以一般不会编译到lib目录中。

检查/root/weixin/tiger/website/target/website/WEB-INF/lib目录,发现有几个jar没有正确加载:


将这几个jar导入到lib目录中,重新执行job,可以正常执行。

不过每次都这样手动复制太麻烦,于是干脆在start.sh中添加复制的语句:

先把这四个jar复制到/root/jar目录中,然后在start.sh中添加以下语句:

#!/bin/sh
#java所在目录
JAVA_HOME="/root/jdk1.7.0_45"
#执行的角色,一般不推荐使用root
RUNNING_USER=root
#class文件所在目录的上一级
APP_HOME=/root/weixin/tiger/website/target/website/WEB-INF
#job入口类带全限定名的名字,例如online.pangge.exam.job.JobRunner
APP_MAINCLASS=$1
CLASSPATH=$APP_HOME/classes
#复制jar包一定要在循环前面,否则复制的jar包无法加载
cp /root/jars/* /root/weixin/tiger/website/target/website/WEB-INF/lib/
#拼接classpath变量,这里会逐个jar包都拼接进去
for i in "$APP_HOME"/lib/*.jar; do
	CLASSPATH="$CLASSPATH":"$i"
done
#运行时的参数,可以不提供,按照默认的参数运行,如果不提供注意下面JAVA_CMO也应该去掉$JAVA_OPTS参数
JAVA_OPTS="-ms512m -mx512m -Xmn256m -Djava.awt.headless=true -XX:MaxPermSize=128m"
#需要执行的job名称,不需要全限定名,因为是通过Spring获取的,知道名字即可
PARAM=$2
#拼接执行的参数,>/root/sh/$PARAM-`date +%Y-%m-%d-%H%M%S`.log 2>&1 & 是重定向程序输出到/root/sh目录下,对应的job名称的.log文件中
JAVA_CMO="nohup $JAVA_HOME/bin/java $JAVA_OPT -classpath $CLASSPATH $APP_MAINCLASS $PARAM >/root/sh/$PARAM-`date +%Y-%m-%d-%H%M%S`.log 2>&1 &"
#执行的命令,注意- $RUNNING_USER中间是有空格的
su - $RUNNING_USER -c "$JAVA_CMO"


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值