Quartz ศึกษาด้วยตนเอง part 2
Job ใน JobDetail จะถูก fires (ได้หลายครั้งแล้วแต่เงื่อนไข) โดย Trigger แน่นอนว่าพวกมันจะทำงานได้ก็ต่อเมื่ออยู่พร้อมกันใน Scheduler
ความเดิมจากตอนที่แล้ว
เราได้รู้จัก Quartz ไปบ้างแล้ว รู้ว่ามันประกอบด้วย Job, JobDetail, Trigger และ Scheduler ดังนั้นใน part นี้เราจะมาเขียนโค้ดกันครับ
เริ่มจาก
Hello World
สร้างโปรเจกต์ด้วยภาษา Kotlin จากนั้นทดสอบ Hello World กันก่อน
Hello Quartz
- โหลด Quartz library ได้ที่นี่ เลือกเป็น latest stable releases
- แกะออกแล้วมองหา folder ที่ชื่อ lib เฉพาะ folder นี้ลากไปวางไว้ที่ root project
- คลิกขวาที่ lib เลือก Add as Library…
- เลือก Level เป็น Project Library
- หรือเลือก File > Project Structure… ตรวจดูที่ Libraries ก็จะพบ lib ดังกล่าว
และเนื่องจากว่า Quarzt ใช้ log4j เราจึงควร configuration ให้เรียบร้อย
- ที่ src ของโปรเจกต์ คลิกขวาสร้างไฟล์ log4j.properties
- เพิ่ม config ต่อไปนี้ลงไปใน log4j.properties
log4j.rootLogger=INFO, theConsoleAppender
log4j.appender.theConsoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.theConsoleAppender.layout=org.apache.log4j.PatternLayout
ถึงตอนนี้เราก็พร้อม Hello Quartz แล้วครับ
เดิมเรามีฟังก์ชัน main อยู่แล้วก็ให้เพิ่มคลาส SimpleJob ที่ใช้ Job interface ดังนี้
import org.quartz.Job
import org.quartz.JobExecutionContext
class SimpleJob: Job {
override fun execute(context: JobExecutionContext?) {
println("Hello Quartz")
}
}
ที่ฟังก์ชัน main สร้าง JobDetail ของ SimpleJob
fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
}
หลังจากนั้นสร้างกลไกของเวลารอไว้ด้วย
fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger().build()
}
สุดท้ายสร้าง scheduler แล้วมอบ jobDetail กับ trigger ให้กับมัน
fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger().build()
val scheduler = StdSchedulerFactory.getDefaultScheduler()
scheduler.scheduleJob(jobDetail, trigger)
scheduler.start()
}
ผลลัพธ์
Hello Quartz
เพื่อนๆจะพบว่าโค้ดข้างต้นนั้นเรียบง่าย ประกอบด้วย 1 JobDetail, 1 Trigger และ 1 Scheduler
หากเราเปลี่ยน “Hello Quartz” เป็น java.util.Date เราก็จะได้ค่าของเวลาปัจจุบันของเครื่องออกมา
ผลลัพธ์
Tue Jun 16 14:25:40 ICT 2020
repeatForever and withIntervalInSeconds
เราจะกำหนดให้ค่าของ Date นี้ถูกพิมพ์ออกมาทุกๆ 3 วินาที เรื่อยไปครับ จุดหมายนี้จึงต้องไปบอกกับ Trigger
fun main() {
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java).build()
val trigger = TriggerBuilder.newTrigger()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.repeatForever()
.withIntervalInSeconds(3)
)
.build()
val scheduler = StdSchedulerFactory.getDefaultScheduler()
scheduler.scheduleJob(jobDetail, trigger)
scheduler.start()
}
ผลลัพธ์
Tue Jun 16 14:33:09 ICT 2020
Tue Jun 16 14:33:12 ICT 2020
Tue Jun 16 14:33:15 ICT 2020
Tue Jun 16 14:33:18 ICT 2020
Tue Jun 16 14:33:21 ICT 2020
Tue Jun 16 14:33:24 ICT 2020
Tue Jun 16 14:33:27 ICT 2020
Tue Jun 16 14:33:30 ICT 2020
…
มาถึงตรงนี้เราก็พอจะเห็นภาพการทำงานของ Scheduler บ้างแล้ว มาต่อที่ตัวอย่างถัดไป
JobDataMap
หนึ่งในวิธีการส่ง data จากภายนอกให้กับ Job (ที่ถูก JobDetail หุ้มไว้) ในขณะ runtime คือการใช้ JobDataMap
JobDataMap ตัวมันเองสร้างจาก Map interface (Data Structure) มี key กับ value เป็นคู่กัน
ตัวอย่าง
val jobDetail = JobBuilder.newJob().ofType(SimpleJob::class.java)
.usingJobData("msg1", "Job Execute Time")
.usingJobData("count", 1)
.build()
จากนั้นแก้ไขใน SimpleJob เพื่อเรียกใช้
class SimpleJob: Job {
override fun execute(context: JobExecutionContext?) {
val jobDataMap = context!!.jobDetail.jobDataMap
println(jobDataMap.getInt("count").toString() + " job is running...")
println(jobDataMap.getString("msg1") + ": "+ Date())
}
}
ผลลัพธ์
1 job is running…
Job Execute Time: Sat Jun 20 01:20:12 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 01:20:15 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 01:20:18 ICT 2020
…
CronTrigger
CronTrigger เป็น interface ที่สืบทอดจาก Trigger มีความสามารถเสมือนตารางเวลาในปฏิทิน เราจึงกำหนดได้ว่าจะให้ทำงานวันที่เท่าไรเวลาไหนหรือช่วงไหนของวัน เรียกได้ว่ามีประโยชน์มากหากต้องการระบบอัตโนมัติที่ตั้งเวลาต่อเนื่องได้
CronTrigger ใช้สิ่งที่เรียกว่า Cron-Expressions เป็นเงื่อนไขกำหนดตารางเวลาทำงาน (เรียกว่าการ fire) อ่านเพิ่มเติมได้ที่นี่
ประกอบด้วยสตริงที่สร้างจาก 6 ถึง 7 นิพจน์ย่อย แต่ละนิพจน์มีความหมายของมันเอง
นำมาเรียงต่อกัน คั่นแต่ละนิพจน์ด้วยช่องว่าง ได้ดังนี้
Seconds
Minutes
Hours
Day of month
Month
Day of week
Year
โดยที่ Year
ไม่ใส่ก็ได้
เช่น
0 0 12 * * ?
fire เวลา 12:00 PM (เที่ยงวัน) ในทุกวัน
0 15 10 ? * *
fire เวลา 10:15 AM ในทุกวัน
0 15 10 * * ?
fire เวลา 10:15 AM ในทุกวัน
0 15 10 * * ? *
fire เวลา 10:15 AM ในทุกวัน
0 15 10 * * ? 2005
fire เวลา 10:15 AM ทุกวัน ตลอดปี 2005
0 * 14 * * ?
fire ทุกนาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:59 PM ในทุกวัน
0 0/5 14 * * ?
fire ทุก 5 นาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:55 PM ในทุกวัน
0 0/5 14,18 * * ?
fire ทุก 5 นาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:55 PM
และ fire ทุก 5 นาที เริ่มที่เวลา 6:00 PM จบที่เวลา 6:55 PM ในทุกวัน
0 0–5 14 * * ?
fire ทุกนาที เริ่มที่เวลา 2:00 PM จบที่เวลา 2:05 PM ในทุกวัน
0 10,44 14 ? 3 WED
fire เวลา 2:10 PM และเวลา 2:44 PM ทุกวันพุธในเดือนมีนาคม
0 15 10 ? * MON-FRI
fire เวลา 10:15 AM ทุกวันจันทร์ อังคาร พุธ พฤหัสบดีและศุกร์
0 15 10 15 * ?
fire เวลา 10:15 AM ทุกวันที่ 15 ของทุกเดือน
0 15 10 L * ?
fire เวลา 10:15 AM ทุกวันสิ้นเดือน
0 15 10 ? * 6L
fire เวลา 10:15 AM ทุกศุกร์สุดท้ายของทุกเดือน
0 15 10 ? * 6L 2002–2005
fire เวลา 10:15 AM ทุกศุกร์สุดท้ายของทุกเดือน ระหว่างปี 2002, 2003, 2004 และ 2005
0 15 10 ? * 6#3
fire เวลา 10:15 AM ของศุกร์ที่สามของทุกเดือน
0 0 12 1/5 * ?
fire เวลา 12 PM (เที่ยงคืน) ทุก 5 วันในทุกเดือน โดยเริ่มต้นที่วันแรกของเดือน
0 11 11 11 11 ?
fire ทุกเดือนพฤศจิกายน เวลา 11 ถึง 11:11 AM
ตัวอย่าง
val trigger = TriggerBuilder.newTrigger()
.withSchedule(
CronScheduleBuilder.cronSchedule("0 0-20 3 * * ?")
)
.build()
ผลลัพธ์
1 job is running…
Job Execute Time: Sat Jun 20 03:17:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:18:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:19:00 ICT 2020
1 job is running…
Job Execute Time: Sat Jun 20 03:20:00 ICT 2020
ตัวอย่างข้างต้น (ในขณะที่เขียนบทความอยู่นี้) จะ fire ทุกนาที เวลาตีสามถึงตีสามยี่สิบนาทีในทุกวัน (ผมเริ่ม execute เวลา ตีสามสิบหกนาที)
หมายเหตุ
การจะหยุด schedule ได้ต้องเรียกคำสั่ง scheduler.shutdown()
ดังนั้นจึงต้องหาวิธีเก็บค่าของตัวแปร scheduler
นี้ไว้เพื่อเรียกใช้ในภายหลัง
อ้างอิง
https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm
https://www.baeldung.com/quartz