文章连接:liuyueyi.github.io/hexblog/201…java
相关博文:git
QuickTask这个项目主要就是为了解决数据订正和接口验证不方便的场景,设计的一个及其简单的动态脚本调度框架,前面一篇总体介绍篇博文,主要介绍了这是个什么东西,总体的运行原理,以及一些简单的使用demogithub
本篇博文将主要放在应用场景的探讨上,在实际的项目环境中,能够怎么用spring
支目前来讲,有两种简单的使用方式,一是以独立的jar包来运行,二是集成在已有的项目中运行;下面分别给出介绍缓存
独立jar包下载,首先下载原始工程,而后打出一个可执行的jar包便可app
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脚本,并执行框架
当脚本发生变更时,一样会从新加载更新后的groovy并执行,且会停掉原来的脚本maven
做为一个依赖来使用也是能够的,首先是添加pom依赖ide
<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或者有更好的建议,欢迎批评指正,不吝感激