今天寫的東西有點多,集成了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