基于jdk1.6、springmvc3分离系统定时任务的实现

概要

背景:960系统架构老旧,采用过时的JKD1.6,并且还是单服务架构,为了实现集群部署,提高系统的健壮性,必须要分离系统定时器(针对jdk1.6未找到可以使用的调度系统),特此自研一套定时任务调度系统,可以实现自动轮询、故障转移等功能。设计思路:960实现动态配置,自定义注解,通过暴露的接口参数反射到特定的定时任务上;建立调度系统,实现手动自动注册的方式,实现定时任务动态管理,可查询执行日志;使用http/https进行交互,减轻使用门槛及提高跨平台兼容性。

调度平台就只展示一下几个关键页面吧,后面再来写相关文档,本次重点介绍基于jdk1.6、springmvc3分离系统定时任务的实现:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

整体架构

jdk1.6、 spingmvc、反射 系统启动后自动注册调度系统,通过在调度系统后台配置与@AsdJob("staticBuild")对应的名称,通过接口回调回来,再通过反射执行定时任务方法

技术名词解释

  • 集群部署:系统冗余部署,主要好处如下:1、均衡系统压⼒,减少单个服务器因压⼒过于集成⽽超负荷运转的发⽣⼏率。2 加快⽤户的访问的速度。3 ⾃动备援,避免因单点故障⽽导致整个系统瘫痪的情况

技术细节

960系统改造部分

  • 配置文件
asd-job.url=http://192.0.1.0:8034/Cate/register
asd-job.access_token=677c71c7b5xxxxxxxxxx9952b0
asd-job.heart_time=60000
asd-job.address=http://192.2.0.1:8011/asdJob/execute.jhtml
asd-job.name=960-job
  • 配置类JobConfig
package net.shopxx.job.config;

import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author zhanqi
 * @since 2023/3/24 16:07
 */
@Component
public class JobConfig {
    private String asdJobUrl;
    private String address;
    private String name;
    private String accessToken;
    private Integer heartTime;

    public String getAsdJobUrl() {
        return asdJobUrl;
    }

    public void setAsdJobUrl(String asdJobUrl) {
        this.asdJobUrl = asdJobUrl;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public Integer getHeartTime() {
        return heartTime;
    }

    public void setHeartTime(Integer heartTime) {
        this.heartTime = heartTime;
    }

    public JobConfig() {
    }

    public JobConfig(String asdJobUrl, String address, String name, String accessToken,Integer hearttime) {
        this.asdJobUrl = asdJobUrl;
        this.address = address;
        this.name = name;
        this.accessToken = accessToken;
        this.heartTime = heartTime;
    }

    public void execute() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(doGet(String.format("%s?auth=%s&accessToken=%s&name=%s&address=%s", asdJobUrl, "xxxxxxxxx",accessToken, name, address)));
            }
        }, 0,heartTime);
    }

    public String doGet(String httpUrl) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;
        try {
            URL url = new URL(httpUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(15000);
            connection.setReadTimeout(60000);
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 封装输入流is,并指定字符集
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
                // 存放数据
                StringBuilder sb = new StringBuilder();
                String temp;
                while ((temp = bufferedReader.readLine()) != null) {
                    sb.append(temp);
                    sb.append("\n");
                }
                result = sb.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (null != connection) {
                connection.disconnect();
            }
        }
        return result;
    }

}

  • applicationContext.xml 加入配置
	<bean id="jobConfig" class="net.shopxx.job.config.JobConfig">
		<property name="asdJobUrl" value="${asd-job.url}" />
		<property name="address" value="${asd-job.address}" />
		<property name="name" value="${asd-job.name}" />
		<property name="accessToken" value="${asd-job.access_token}"/>
		<property name="heartTime" value="${asd-job.heart_time}"/>
	</bean>
  • 定义一个自定义注解
package net.shopxx.job.annotation;

/**
 * @author zhanqi
 * @since 2023/3/24 16:07
 */

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AsdJob {
    String value();
}

  • 写一个BaseJob类方便扫描特定类
package net.shopxx.job.base;

/**
 * @author zhanqi
 * @since 2023/3/23 19:04
 */
public class BaseJob {
}

  • 实现一个api接口来与调度平台交互,通过参数反射方法执行定时任务
/*
 * Copyright 2005-2013 shopxx.net. All rights reserved.
 * Support: http://www.shopxx.net
 * License: http://www.shopxx.net/license
 */
package net.shopxx.job.server;

import com.alibaba.fastjson.JSONObject;
import net.shopxx.controller.mobile.BaseController;
import net.shopxx.job.annotation.AsdJob;
import net.shopxx.job.base.BaseJob;
import net.shopxx.util.SpringUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

@Controller("asdJob")
@RequestMapping("/asdJob")
public class AsdJobController extends BaseController {

	Set<BeanDefinition> components =new ConcurrentSkipListSet<BeanDefinition>();

	/**
	 * 执行定时器
	 */
	@RequestMapping(value = "/execute", method = RequestMethod.POST)
	@ResponseBody
	public Object execute(@RequestBody JSONObject json) {
		Object obj = exeMethod(json);
		return obj;
	}

	/**
	 * 心跳检查
	 */
	@RequestMapping(value = "/check", method = RequestMethod.POST)
	@ResponseBody
	public Integer check() {
		return 200;
	}
	/**
	 * 反射执行方法
	 * @param json
	 * @return
	 */
	private Object exeMethod(JSONObject json){
		if(components.size()<1){
			ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
			provider.addIncludeFilter(new AssignableTypeFilter(BaseJob.class));
			components = provider.findCandidateComponents("net.shopxx.job");
		}
		String params = json.getString("params");
		String url = json.getString("url");
		Iterator<BeanDefinition> it = components.iterator();
		while (it.hasNext()) {
			try {
				Class<?> cls = Class.forName(it.next().getBeanClassName());
				Object o=  SpringUtils.getBean(cls.getName());
				System.out.println(o);
				Method[] methods=o.getClass().getMethods();
				for (int i = 0; i < methods.length; i++) {
					AsdJob asdJob=  methods[i].getAnnotation(AsdJob.class);
					if(asdJob!=null&&asdJob.value().equals(url)){
						try {
							if (StringUtils.isBlank(params)) {
								return methods[i].invoke(o);
							}
							return methods[i].invoke(o,params);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

		}
		return "找不到对应的任务名称";
	}
}

  • 定时任务改造示例
package net.shopxx.job;

import net.shopxx.job.annotation.AsdJob;
import net.shopxx.job.base.BaseJob;
import net.shopxx.service.StaticService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * Job - 静态化
 *
 * @author SHOP++ Team
 * @version 3.0
 */
@Component("staticJob")
@Lazy(false)
public class StaticJob extends BaseJob {

	@Resource(name = "staticServiceImpl")
	private StaticService staticService;

	/**
	 * 生成静态
	 */
	//	@Scheduled(cron = "${job.staticBuild.cron}")
	@AsdJob("staticBuild")
	public void build() {
		staticService.buildAll();
	}

}

  • 实现类初始化赋值StartupListener,后期可以不用配置applicationContext.xml
package net.shopxx.listener;

import net.shopxx.job.config.JobConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

/**
 * @author zhanqi
 * @since 2023/3/24 17:07
 */

/**
 * 启动监听器
 *
 * @author Storezhang
 */
@Service("startupListener")
@Lazy
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private JobConfig jobConfig;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent evt) {
        jobConfig.execute();
    }

}


小结

只要你想做这件事是正确的,任何妖魔鬼怪都阻止不了你

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值