今天写的东西有点多,集成了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