文章連接:https://liuyueyi.github.io/hexblog/2018/07/19/180719-Quick-Task-動態腳本支持框架之使用介紹篇/java
相關博文:git
QuickTask這個項目主要就是爲了解決數據訂正和接口驗證不方便的場景,設計的一個及其簡單的動態腳本調度框架,前面一篇總體介紹篇博文,主要介紹了這是個什麼東西,總體的運行原理,以及一些簡單的使用demogithub
本篇博文將主要放在應用場景的探討上,在實際的項目環境中,能夠怎麼用spring
<!-- more -->緩存
支目前來講,有兩種簡單的使用方式,一是以獨立的jar包來運行,二是集成在已有的項目中運行;下面分別給出介紹app
獨立jar包下載,首先下載原始工程,而後打出一個可執行的jar包便可框架
git clone https://github.com/liuyueyi/quick-task cd quick-task/task-core mvn clean package -Dmaven.test.skip cd target java -jar task-core-0.0.1.jar --task /tmp/script
注意上面的jar包執行中,傳入的--task參數,這個就是制定監聽動態腳本的目錄,如上面的腳本,表示框架會自動加載 /tmp/script
目錄下的Groovy腳本,並執行maven
當腳本發生變更時,一樣會從新加載更新後的groovy並執行,且會停掉原來的腳本ide
做爲一個依賴來使用也是能夠的,首先是添加pom依賴工具
<repositories> <repository> <id>yihui-maven-repo</id> <url>https://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository</url> </repository> </repositories> <dependency> <groupId>com.git.hui</groupId> <artifactId>task-core</artifactId> <version>0.0.1</version> </dependency>
而後在本身的代碼中,顯示的調用下面一行代碼便可,其中run方法的參數爲動態腳本的目錄
new ScriptExecuteEngine().run("/tmp/script");
對於SpringBoot項目而言,能夠在入口Application
類的run方法中調用,一個demo以下
@SpringBootApplication public class Application implements CommandLineRunner { public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(Application.class); app.run(args); } @Override public void run(String... strings) throws Exception { new ScriptExecuteEngine().run("/tmp/script"); } }
對於傳統的Spring項目而言,能夠新建一個Listener, 監聽全部的bean初始化完成以後,開始註冊任務引擎,一個可參考的使用case以下
import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SmartApplicationListener; import org.springframework.stereotype.Component; @Component public class RegisterTaskEngineListener implements SmartApplicationListener { @Override public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) { return aClass == ContextRefreshedEvent.class; } @Override public boolean supportsSourceType(Class<?> aClass) { return true; } @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { new ScriptExecuteEngine().run("/tmp/script"); } @Override public int getOrder() { return 0; } }
兩種使用方式,從我的角度出發,並無什麼優劣之別,主要仍是看具體的業務場景,當但願部署一個獨立的任務腳本支持時,可能獨立的部署更加的方便,能夠在內部進行資源隔離,減小對線上生產環境的影響;
如果單純的把這個做爲一個檢測項目運行的輔助工具時,如回調線上的服務接口,判斷輸出,獲取運行項目中的內部參數等,集成在已有的項目中也是比較簡單的
使用了這個框架,到底有什麼用處呢?或者說是否有一些適用的經典case呢?
這種場景比較常見,但通常配套設施齊全的公司,也不會出現這個問題,咱們最多見的查看數據有如下幾類
對於DB查看,通常沒啥問題,要麼能夠直連查詢要麼就是有查詢工具;而緩存數據的查詢,主要是咱們經過序列化後存入的數據,直接從緩存中獲取可能並不太友好;對於運行時內存中的數據,就不太好獲取了,特別是咱們使用Guava緩存的數據,如何在項目運行中判斷緩存中的數據是否有問題呢?
一個查看內存的僞代碼
class DemoScript implements ITask { @Override void run() { // 獲取目標對象 xxxBean = ApplicationContextHolder.getBean(xxx.class); xxxBean.getXXX(); } }
上面的腳本中,關鍵就是在於獲取目標對象,拿到目標對象以後,再獲取內部的局部變量或者內存數據就比較簡單了(不能直接訪問的局部變量能夠經過反射獲取)
因此關鍵就是獲取目標對象,有下面幾種思路可供參考:
ApplicationContext#getBean
方式獲取在問題復現的場景下,比較經常使用了,傳入相同的參數,判斷接口的返回結果是否ok,用於定位數據異常
class DemoScript implements ITask { @Override void run() { // 獲取目標對象 xxxService = ApplicationContextHolder.getBean(xxx.class); req = buildRequest(); result = xxxService.execute(req); log.info("result: {}", result); } }
其實實際使用起來和前面沒什麼區別,無非是線獲取到對應的Service,而後執行接口,固然在Spring的生態體系中,一個可展望的點就是支持自動注入依賴的bean
首先明確一點,在咱們的框架中,全部的任務都是隔離的,獨立的線程中調度的,當咱們但願一個新的任務每隔多久執行一次,能夠怎麼作?
一個簡單的僞代碼以下
class DemoScript implements ITask { private volatile boolean run = false; @Override void run() { run = true; while(true) { doXXX(); try { Thread.sleep(1000); } catch(Exception e) { } if(!run) break; } } @Override void interrupt() { run = false; } }
注意下上面的實現,在run方法中,有一個死循環,一直在重複的調用 doxxx()
方法,在內部經過 Thread.sleep()
來控制頻率
在腳本改變或刪除以後,框架會回調 interrupt
方法,所以會將上面的run變量設置爲false,從而結束死循環
注意:
ScheduleTask
抽象類出來,將循環和中斷的邏輯封裝一下,對於使用方而言,只須要寫業務邏輯便可,不須要關心這些重複的邏輯這種更多的是把這個框架做爲一個調度來用,咱們接收mq的消息,而後在動態腳本中進行處理,再傳給第三方(若是集成在本身的項目中時,一個demo就是能夠直接調用項目中的Dao保存數據)
一個RabbitMq的消費任務,對應的僞代碼以下
class DemoScript implements ITask { @Override void run() { ConnectionFactory fac = new CachingConnectionFactory(); fac.setHost("127.0.0.1"); fac.setPort(5672); fac.setUsername("admin") fac.setPassword("admin") fac.setVirtualHost("/") //建立鏈接 Connection connection = factory.newConnection(); //建立消息信道 final Channel channel = connection.createChannel(); //消息隊列 channel.queueDeclare(queue, true, false, false, null); //綁定隊列到交換機 channel.queueBind(queue, exchange, routingKey); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); try { System.out.println(" [" + queue + "] Received '" + message); } finally { channel.basicAck(envelope.getDeliveryTag(), false); } } }; // 取消自動ack channel.basicConsume(queue, false, consumer); } }
注意:
博文:
項目:
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激