定时任务管理中心(dubbo+spring)-我们到底能走多远系列47
我们到底能走多远系列47
扯淡:
又是一年新年时,不知道上一年你付出了多少,收获了多少呢?也许你正想着老板会发多少奖金,也许你正想着明年去哪家公司投靠。
这个时间点好好整理一下,思考总结一下,的确是个非常好的机会。
年终的时候各个公司总会评一下绩效,拉出各位的成绩单,你是不是想说:去你妈的成绩单,我不是你的学生,老子努力工作不是为了看你脸色!当然啦,你想说这话的前提是:你很牛b,如果不是也可以想想,然后默默去变牛b。
我大多数的朋友同事都是漂在城市里的人,我们努力的活得更好,想过自己想过的生活,打心里佩服我们自己,选择这个行业,正尝试改变着世界。
所以,加油,各位!
另外,程序员过什么新年?写bug的时间都不够呢!
最后还是祝看到这个文字的朋友:身体健康,阖家欢乐,鸡年大吉,公司上市。
主题:
一般,开一个定时任务很简单,spring写个注解就能跑了,或者单应用的定时任务还有很多其他丰富jar支持。


public class TaskSupport implements BeanPostProcessor, ApplicationListener<ApplicationContextEvent>,
ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger(TaskSupport.class);
private ApplicationContext applicationContext;
private RegistryConfig registryConfig;
private ApplicationConfig applicationConfig;
private ProtocolConfig protocolConfig;
// 存储任务bean
private Map<String, Object> taskBeanMap = new HashMap<String, Object>();
// dubbo config
private ServiceConfig<Object> serviceConfig; @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
collectTaskBean(bean, beanName);
return bean;
} private Object getTarget(Object bean) {
Object target = bean;
while (target instanceof Advised) {
try {
target = ((Advised) bean).getTargetSource().getTarget();
} catch (Exception e) {
target = null;
break;
}
}
return target;
} private void collectTaskBean(Object bean, String beanName) {
Object target = getTarget(bean);
if (target != null) {
Class<?> clazz = target.getClass();
if (!clazz.isAnnotationPresent(Service.class) || !clazz.isAnnotationPresent(Task.class)) {
return;
}
if (!taskBeanMap.containsKey(beanName)) {
logger.info("add task bean {}", beanName);
taskBeanMap.put(beanName, bean);
}
}
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (isCurrentApplicationContextRefresh(event)) {
exportTaskDispatcher();
}
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
} /**
* 向Task暴露任务分发器服务
*/
protected void exportTaskDispatcher() {
if (serviceConfig != null && serviceConfig.isExported()) {
return;
}
applicationConfig = applicationContext.getBean(ApplicationConfig.class);
registryConfig = applicationContext.getBean("soaRegistryConfig", RegistryConfig.class);
protocolConfig = applicationContext.getBean(ProtocolConfig.class);
TaskDispatcherImpl taskServiceProxyImpl = wireTaskServiceProxy();
exportServiceConfig(taskServiceProxyImpl);
} protected void unexportTaskDispatcher() {
if (serviceConfig != null && serviceConfig.isExported()) {
serviceConfig.unexport();
}
} private TaskDispatcherImpl wireTaskServiceProxy() {
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
TaskDispatcherImpl taskServiceProxy = new TaskDispatcherImpl();
// ITaskDispatcher的实现bean载入到spring bean容器中,等待dubbo接口暴露
taskServiceProxy = (TaskDispatcherImpl) beanFactory.initializeBean(taskServiceProxy, "taskServiceProxy");
// 这里把筛选出的taskBeanMap注入到TaskDispatcherImpl,直接可以使用
taskServiceProxy.setTaskBeanMap(taskBeanMap);
taskServiceProxy.setApplicationConfig(applicationConfig);
taskServiceProxy.setRegistryConfig(registryConfig);
defaultListableBeanFactory.registerSingleton("taskServiceProxy", taskServiceProxy);
return taskServiceProxy;
}
/**
* dubbo接口暴露给任务调度系统
*
*/
private void exportServiceConfig(Object proxy) {
serviceConfig = new ServiceConfig<Object>();
serviceConfig.setApplication(applicationConfig);
serviceConfig.setRegistry(registryConfig);
serviceConfig.setProtocol(protocolConfig);
// 把这个接口暴露出去
serviceConfig.setInterface(ITaskDispatcher.class);
serviceConfig.setRef(proxy);
serviceConfig.setRetries(0);
// 各个业务系统的group不同,这里充分利用了dubbo的属性
serviceConfig.setGroup(applicationConfig.getName());
serviceConfig.export();
} /**
* 是否是当前上下文,防止重复加载和过早加载
*
* @param event
* @return
*/
private boolean isCurrentApplicationContextRefresh(ApplicationEvent event) {
return event instanceof ContextRefreshedEvent
&& ((ContextRefreshedEvent) event).getApplicationContext() == applicationContext;
}
}
这样一来,所有应用都会暴露一个ITaskDispatcher 类的方法出去,但是各个group不一样。ITaskDispatcher定义的方法:
public interface ITaskDispatcher {
public void dispatch(TaskInvokeInfoDto taskInvokeInfoDto);
}
dispatch方法是调度中心调度触发启动任务的方法,根据TaskInvokeInfoDto这个参数里的定义,需要定位到哪一个应用的哪一个类的那一个方法,这个方法的参数是什么,定位到后执行它,这就是dispatch要实现的功能。
先看一下TaskInvokeInfoDto的定义:
private String appName;//定位到哪个应用,dubbo的group区分
private String beanName;//定位到哪个类
private String methodName;// 定位到哪个方法
private String[] parameterTypes;//方法的参数类型,有重载的情况
private String[] args;//参数值
那么dispatch的核心代码:
public void dispatch(TaskInvokeInfoDto taskInvokeInfoDto) {
try {
Method method = findMethod(taskInvokeInfoDto);
Class<?>[] parameterClazzs = method.getParameterTypes();
if (parameterClazzs.length == 0) {
ReflectionUtils.invokeMethod(method, taskBeanMap.get(taskInvokeInfoDto.getBeanName()));
} else {
Object[] parameterObjs = new Object[parameterClazzs.length];
for (int i = 0; i < parameterClazzs.length; i++) {
parameterObjs[i] = Jackson.base().readValue(taskInvokeInfoDto.getArgs()[i], parameterClazzs[i]);
}
ReflectionUtils.invokeMethod(method, taskBeanMap.get(taskInvokeInfoDto.getBeanName()), parameterObjs);
}
} catch (Exception e) {
logger.error("execute error...", e);
}
}
// 上面将的定位逻辑
private Method findMethod(TaskInvokeInfoDto taskInvokeInfoDto) {
Object bean = taskBeanMap.get(taskInvokeInfoDto.getBeanName());
Method method = null;
if (ArrayUtils.isEmpty(taskInvokeInfoDto.getParameterTypes())) {
method = ReflectionUtils.findMethod(bean.getClass(), taskInvokeInfoDto.getMethodName());
} else {
final int paramCount = taskInvokeInfoDto.getParameterTypes().length;
Class<?>[] clazzArray = new Class<?>[paramCount];
for (int i = 0; i < paramCount; i++) {
try {
clazzArray[i] = ClassUtils.getClass(taskInvokeInfoDto.getParameterTypes()[i]);
} catch (ClassNotFoundException e) {
logger.info("根据参数类型的字符串创建class对象时失败", e);
return null;
}
}
method = ReflectionUtils.findMethod(bean.getClass(), taskInvokeInfoDto.getMethodName(), clazzArray);
}
return method;
}
以上只要在调度中心处调用dubbo来控制任务执行就可以实现整个任务中心的核心功能。
当然,这里只是简单的尝试性的实现,还有很多优化和扩展可以做,比如任务日志打印收集,任务应用存活状态心跳监控,等等。
以前看到过一篇去哪网的吹b文章,吹了半天,仔细看了他提到的功能和没实现的功能,搞过的人都会觉得做一个其实不难,只是人家分享的时候感觉很厉害,其实他自己心里清楚自己这个系统也是处处是坑。虽然吹b,不过也会给我们各种启发。
总结:
1,代码中利用spring的BeanPostProcessor,筛选出自己需要的bean的方式又是一种新的技巧,我在《请求路由到业务方法设计(2)》中需要筛选bean map用了另一种方式。不知道网友还有其他的想法吗?
2,反射相关的api还可以继续深入学习。
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。
定时任务管理中心(dubbo+spring)-我们到底能走多远系列47的更多相关文章
- Spring mvc源码url路由-我们到底能走多远系列(38)
我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...
- 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)
我们到底能走多远系列(40) 扯淡: 判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...
- Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)
我们到底能走多远系列(33) 扯淡: 各位: 命运就算颠沛流离 命运就算曲折离奇 命运就算恐吓着你做人没趣味 别流泪 心酸 更不应舍弃 ... 主题: Spring源码阅读还在继 ...
- 初始化IoC容器(Spring源码阅读)-我们到底能走多远系列(31)
我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? 毕竟,对自己的收入的分配差不多体现了自己的 ...
- node实现http上传文件进度条 -我们到底能走多远系列(37)
我们到底能走多远系列(37) 扯淡: 又到了一年一度的跳槽季,相信你一定准备好了,每每跳槽,总有好多的路让你选,我们的未来也正是这一个个选择机会组合起来的结果,所以尽可能的找出自己想要的是什么再做决定 ...
- Sharded实现学习-我们到底能走多远系列(32)
我们到底能走多远系列(32) 扯淡: 工作是容易的赚钱是困难的 恋爱是容易的成家是困难的 相爱是容易的相处是困难的 决定是容易的可是等待是困难的 主题: 1,Sharded的实现 Sharded ...
- Spring3整合Hibernate4-我们到底能走多远系列(30)
我们到底能走多远系列(30) 扯淡: 30篇啦!从2012-08-15开始的系列,东平西凑将近一年的时间也就这么几篇.目标的100篇,按这个速度也要再搞两年呢. 发博客果然不是件容易的事,怪不得更多的 ...
- JMS生产者+单线程发送-我们到底能走多远系列(29)
我们到底能走多远系列(29) 扯淡: “然后我俩各自一端/望着大河弯弯/终于敢放胆/嘻皮笑脸/面对/人生的难” --- <山丘> “迎着风/迎向远方的天空/路上也有艰难/也有那解 ...
- ArrayBlockingQueue-我们到底能走多远系列(42)
我们到底能走多远系列(42) 扯淡: 乘着有空,读些juc的源码学习下.后续把juc大致走一边,反正以后肯定要再来. 主题: BlockingQueue 是什么 A java.util.Queue t ...
随机推荐
- C++ Builder多线程编程技术经验谈(转)
源:C++ Builder多线程编程技术经验谈 线程之可行性 在很多情况下,可能需要为程序创建线程.这里给出其中一些可能性: (1)如果创建的是一个多文档接口(Multiple Document ...
- Fruit Feast
Fruit Feast 题目描述 Bessie has broken into Farmer John's house again! She has discovered a pile of lemo ...
- Linux启动时显示Grub命令行及修改
1.在启动Linux系统时,如果/boot/grub/grub.cfg文件损坏或者不存在时,启动Linux时,就会有Grub命令行的提示. 如下操作,将系统自带的grub.cfg文件改名.重新启动系统 ...
- PHP操作mysql类
<?php class Mysql{ //数据库连接句柄 private $link; //返回结果集 private $result; //返回查询数据 private $data; //执行 ...
- (中等) POJ 1436 Horizontally Visible Segments , 线段树+区间更新。
Description There is a number of disjoint vertical line segments in the plane. We say that two segme ...
- 为什么无线信号(RSSI)是负值(转)
源:为什么无线信号(RSSI)是负值 为什么无线信号(RSSI)是负值 答:其实归根到底为什么接收的无线信号是负值,这样子是不是容易理解多了.因为无线信号多为mW级别,所以对它进行了极化,转化为dBm ...
- matlab find函数
find函数用来获取向量或矩阵中非0元素的索引 假设X是行向量,则find(X)返回的是一个行向量:X'为列向量,find(X')返回的是一个列向量. 看一个例子:A是一个行向量,B是一个列向量,将B ...
- @synthesize和@dynamic
@synthesize 除非开发人员已经做了,否则由编译器自动生成getter/setter方法.当开发人员自定义存或取方法时,自定义会屏蔽自动生成该方法. @dynamic 告诉编译器,不自动生成g ...
- JdbcTemplate的主要用法
JdbcTemplate主要提供以下五类方法: execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句: update方法及batchUpdate方法:update方法用于执行新增.修 ...
- 微信小程序-未接入app.json错误
微信小程序建立新项目之后会出现app.json文件未接入错误如下图: 一般是因为在下图添加新项目,项目目录这一列,如果不事先建立一个空的文件夹,直接选择则不会出现quickstartup界面 所以在建 ...