抽象思维、差异化接口提取以及反射的应用
虽然刚入门java的时候。我们就会学到类似于面向对象、面向接口。
方法重载重写的理论知识。
但是在实际的处理业务的过程中。为了开发进度的需要或者种种原因。
我们能体现这种面向接口。提取公共方法以及差异化接口的地方的确少之又少。
最主要的是思维认知的缺乏和时间的不允许。最起码在于我是这么认为的。
这次的项目我要记录一下。
因为我在顾问的帮助下运用到抽象提取接口。然后差异化代码加上反射的方式。
实现了定时任务的操作。
并且实现了其中的业务job与管理job的分离。
其中业务job又拆分出共通代码来增强代码的简洁程度。
detail
具体业务是这样的:主要是实现数据实时定时抽取。然后写入csv并上传服务器的操作。其中定时配置,包括要抽取的表名、sql、间隔时间、开始时间都在数据库里动态配置。
所以。业务都是共通的一套业务。业务上的唯一不同就是要抽取的表名不同 了。
主要的难点是如何设计动态的根据数据库里的配置来增加job这一层。以及代码的简洁程度和可扩展性。
具体最后的实现方法逻辑是这样的。
一共有两层定时任务。一层管理用任务。一层工作用任务。
管理层任务job负责首次把数据库里的配置job动态加载到Scheduler中、以及定时刷新既有job的配置。因为每一个抽取业务的定时信息配置都会在画面当中实时变化。
工作用任务负责调用具体的业务类型job。比如说分类表抽取、商品表抽取等等。
这样。既满足了实时刷新配置属性的功能。又能满足实时定时抽取数据的业务需要。
其中管理层job比较简单。就是项目启动。在Scheduler中加入管理job就好。
重点在于工作用job。不可能 每一个表都得建立一套job机制来满足。因为本身业务是有共同性的。就是写文件上传。
怎么能使代码更简洁、更有层次、更有扩展性的来做好工作用job是一个值得考虑的地方。
首先我们可以这样做。每个业务数据抽取指定相同的job类。意思是不管数据库里有几张表。要抽几个表的数据。我们都把他归于工作用job里。然后我们在工作用 job中根据不同表名来调用不同的类名实现业务调用。
再然后。我们现在到了工作用job中。因为每个具体业务都有写入csv然后上传至ftp的操作。唯一不同的是每个表对应的sql文查出来的bean可能会有所不同 。上传ftp都是相同的。这样。我们又可以抽象出来一个业务操作类。
重点的是。在业务操作类中。根据表名取到全类名。利用反射机制拿到不同的业务job类对象。执行对象的写入csv操作。然后再业务操作类中执行相同的上传操作。
这样首先是很繁琐。有几个表名就得有几个对象。这样我们就考虑一下。面向接口来编程。我们所有的业务job都实现相同 的接口。这下我们根据反射获取的业务job全类名就只有一个了。根据不同的全类名调用 的业务job又是我们想要的job。这样代码变的既简洁明了。又使业务操作类更有了可维护性和可扩展性。不失为一种好方法。这就是具体的面向接口。并且去提取公共方法和差异化代码的实现。
可能语言总结的还有欠缺。但是贴上代码就会好很多了。
下来就是贴几段主要的方式代码。
启动类
package cn.com.winpeace.kiban.web;
import cn.com.winpeace.kiban.winperpsrv.allofpackages.CheckHead;
import cn.com.winpeace.kiban.winperpsrv.persistence.dao.AllPackagesMapper;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
import cn.com.winpeace.kiban.winperpsrv.schedulerManager.SchedulerManager;
import cn.com.winpeace.kiban.winperpsrv.test.QuartzTriggerZero;
import cn.com.winpeace.kiban.winperpsrv.vo.JobInfoDto;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
import java.util.ArrayList;
import java.util.List;
/**
* @author nehcoab
* @date 2018/7/13
*/
@SpringBootApplication
@ComponentScan({"cn.com.winpeace.kiban"})
@MapperScan({"cn.com.winpeace.kiban.*.persistence.dao"})
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
//执行校验操作
CheckHead checkHead = new CheckHead();
//checkHead.checkSystemConfig();
//增加管理数据job
RealCrtMaterResultDO realCrtMaterResultDO =
new RealCrtMaterResultDO("RefreshJobManager", "RefreshJobManager","2019-10-13",
"09:16:10", 15, "1", "1",
"cn.com.winpeace.kiban.winperpsrv.allofpackages.ConfigurationJob");
// 任务管理启动
Scheduler scheduler = SchedulerManager.getScheduler();
SchedulerManager.addManagerJob(scheduler, realCrtMaterResultDO);
SchedulerManager.run(scheduler, 0);
}
}
管理用job代码:
package cn.com.winpeace.kiban.winperpsrv.allofpackages;
/**
* @ClassName ConfigurationJob
* @Description TODO
* @Author YPF
* @Date 2019/9/2814:04
* @Version 1.0
*/
import cn.com.winpeace.kiban.winperpsrv.logger.MySchedulerTestLogger;
import cn.com.winpeace.kiban.winperpsrv.persistence.dao.AllPackagesMapper;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
import cn.com.winpeace.kiban.winperpsrv.schedulerManager.SchedulerManager;
import cn.com.winpeace.kiban.winperpsrv.test.QuartzTriggerZero;
import cn.com.winpeace.kiban.winperpsrv.vo.JobInfoDto;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.*;
/**
* @ClassName ConfigurationJob
* @Description peizhiJOB 实时数据配置任务。实时配置调度刷新表实时任务
* @Author YPF
* @Date 2019/9/2814:04
* @Version 1.0
**/
@Component
public class ConfigurationJob implements Job{
@Autowired
AllPackagesMapper allPackagesMapper;
public static ConfigurationJob configurationJob;
@PostConstruct
public void init() {
configurationJob = this;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
MySchedulerTestLogger.log("实时配置数据刷新 is begin-----------------");
refresh(jobExecutionContext);
MySchedulerTestLogger.log("实时配置数据刷新 is end-----------------");
}
private void refresh(JobExecutionContext jobExecutionContext) {
try {
//最新任务管理数据读入
List<RealCrtMaterResultDO> realCrtMaterResultDOS = configurationJob.allPackagesMapper.selectConfigurationMessage();
//获取调度器
Scheduler scheduler = jobExecutionContext.getScheduler();
for(RealCrtMaterResultDO newJobInfo : realCrtMaterResultDOS){
//获取job任务明细
JobDetail job = SchedulerManager.getJobDetail(scheduler, newJobInfo);
if(job==null){
if(newJobInfo.getCron_status().toString().equals("1") && newJobInfo.getValid_status().toString().equals("1")){
//新规job 或 job再次开始
MySchedulerTestLogger.log("实时配置数据 is new JOB!" + newJobInfo.getCustomer_id() + "."
+ newJobInfo.getIf_id());
SchedulerManager.addJob(scheduler,newJobInfo);
}
}else{
RealCrtMaterResultDO oldJobInfo = SchedulerManager.getJobInfo(job);
if((oldJobInfo.getCron_status().toString().equals("0") && newJobInfo.getCron_status() .toString().equals("1"))
|| (oldJobInfo.getValid_status().toString().equals("0") && newJobInfo.getValid_status() .toString().equals("1"))){
//job状态从关闭改为激活。 job开始执行
MySchedulerTestLogger.log("ReschedulerJob is OFF -> ON!" + job.getKey());
}else if ((oldJobInfo.getCron_status() .toString().equals("0") && newJobInfo.getCron_status() .toString().equals("0"))
|| (oldJobInfo.getValid_status() .toString().equals("0") && newJobInfo.getValid_status() .toString().equals("0"))){
//job关闭改为关闭。 job无动作
MySchedulerTestLogger.log("ReschedulerJob is OFF -> OFF!" + job.getKey());
}else if ((oldJobInfo.getCron_status() .toString().equals("1") && newJobInfo.getCron_status() .toString().equals("0"))
|| (oldJobInfo.getValid_status() .toString().equals("1") && newJobInfo.getValid_status() .toString().equals("0"))){
//job状态从激活改为关闭。 job停止
SchedulerManager.unscheduleJob(scheduler,newJobInfo);
}else if((oldJobInfo.getCron_status() .toString().equals("1") && newJobInfo.getCron_status() .toString().equals("1"))
|| (oldJobInfo.getValid_status() .toString().equals("1") && newJobInfo.getValid_status() .toString().equals("1"))){
//job状态从激活改为激活。 有可能job间隔时间变更
if(oldJobInfo.getCron_intervals() != newJobInfo.getCron_intervals()){
//job时间变更
MySchedulerTestLogger.log("ReschedulerJob is change IntervalInSeconds!" + job.getKey());
SchedulerManager.rescheduleJob(scheduler,newJobInfo);
}
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
工作用job代码:
package cn.com.winpeace.kiban.winperpsrv.allofpackages;
/**
* @ClassName IfWorkJob
* @Description TODO
* @Author YPF
* @Date 2019/10/119:45
* @Version 1.0
*/
import cn.com.winpeace.kiban.winperpsrv.logger.MySchedulerTestLogger;
import cn.com.winpeace.kiban.winperpsrv.persistence.dao.AllPackagesMapper;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
import cn.com.winpeace.kiban.winperpsrv.schedulerManager.SchedulerManager;
import cn.com.winpeace.kiban.winperpsrv.util.ApplicationContextProvider;
import cn.com.winpeace.kiban.winperpsrv.util.TableNameLinkWorkClassUtil;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @ClassName IfWorkJob
* @Description TODO
* @Author YPF
* @Date 2019/10/119:45
* @Version 1.0
**/
@Component
public class IfWorkJob implements Job {
private static final String CURRENT_JOB_INFO_KEY = "CURRENT_JOB_INFO_KEY";
@Autowired
AllPackagesMapper allPackagesMapper;
public static IfWorkJob ifWorkJob;
@PostConstruct
public void init() {
ifWorkJob = this;
}
public void run(){
MySchedulerTestLogger.log("IfBuyerExecute is begin-----------------");
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try{
JobDetail jobDetail = jobExecutionContext.getJobDetail();
RealCrtMaterResultDO jobInfo = (RealCrtMaterResultDO) jobDetail.getJobDataMap().get(CURRENT_JOB_INFO_KEY);
CsvFTP csvFTP = (CsvFTP)ApplicationContextProvider.getBean("CsvFTP", CsvFTP.class);
csvFTP.run(jobInfo.getIf_id());
}catch (Exception e){
e.printStackTrace();
}
}
}
业务类代码:
package cn.com.winpeace.kiban.winperpsrv.allofpackages;
/**
* @ClassName CsvFTP
* @Description TODO
* @Author YPF
* @Date 2019/10/1311:16
* @Version 1.0
*/
import cn.com.winpeace.kiban.winperpsrv.util.ApplicationContextProvider;
import cn.com.winpeace.kiban.winperpsrv.util.TableNameLinkWorkClassUtil;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* @ClassName CsvFTP
* @Description TODO
* @Author YPF
* @Date 2019/10/1311:16
* @Version 1.0
**/
@Component("CsvFTP")
@Scope("prototype")
public class CsvFTP {
public void run(String jobInfo) throws Exception {
//根据表名取得csv编辑class名
String className = TableNameLinkWorkClassUtil.getWorkClass(jobInfo);
//实例化csv编辑class
IfInterfaceWorkClass csvEditClass = (IfInterfaceWorkClass) ApplicationContextProvider.getBean(jobInfo, Class.forName(className));
//取得sql文
//执行csv编辑
csvEditClass.run(jobInfo);
//ftp上传 共通方法
}
}
其中不同表业务以及接口代码如下
@Service("if_buyer")
@Scope("prototype")
public class IfBuyerExecute implements IfInterfaceWorkClass {
private final static String timeFormat = "HH:mm:ss";
public final static String shortDateFormat = "yyyy-MM-dd";
public final static String longDateFormat = "yyyy-MM-dd HH:mm:ss";
public final static String simpleDateFormat = "yyyyMMdd";
public final static String completeDateFormat = "yyyyMMddHHmmssSSS";
public final static String IFID ="if_buyer";
private static Logger logger = StdOutErrRedirect.logger;
public static IfBuyerExecute ifBuyerExecute;
@PostConstruct
public void init() {
ifBuyerExecute = this;
}
@Value("${config.cronUser}")
private String cronUser;
@Override
//表的csv编辑操作 根据sql文--》dao-》保存csv
public void run(String jobInfo){
MySchedulerTestLogger.log("IfBuyerExecute is begin-----------------"+jobInfo);
PumpEnv pumpEnv = ifBuyerExecute.pumpEnvMapper.getPumpEnv();
MySchedulerTestLogger.log(pumpEnv.toString());
}
package cn.com.winpeace.kiban.winperpsrv.allofpackages;
/**
* @ClassName IfCategoryExecute
* @Description TODO
* @Author YPF
* @Date 2019/9/2820:16
* @Version 1.0
*/
import cn.com.winpeace.kiban.winperpsrv.logger.MySchedulerTestLogger;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* @ClassName IfCategoryExecute
* @Description TODO
* @Author YPF
* @Date 2019/9/2820:16
* @Version 1.0
**/
@Component("if_category")
@Scope("prototype")
public class IfCategoryExecute implements IfInterfaceWorkClass {
@Override
public void run(String jobInfo){
MySchedulerTestLogger.log("IfCategoryExecute is begin-----------------"+jobInfo);
}
}
package cn.com.winpeace.kiban.winperpsrv.allofpackages;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
/**
* @ClassName ifInterfaceWorkClass
* @Description TODO
* @Author YPF
* @Date 2019/10/1311:04
* @Version 1.0
*/
public interface IfInterfaceWorkClass {
public void run(String jobInfo);
}
job工具类代码:
package cn.com.winpeace.kiban.winperpsrv.schedulerManager;
import cn.com.winpeace.kiban.winperpsrv.allofpackages.IfWorkJob;
import cn.com.winpeace.kiban.winperpsrv.persistence.model.RealCrtMaterResultDO;
import cn.com.winpeace.kiban.winperpsrv.vo.JobInfoDto;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class SchedulerManager {
private static final String CURRENT_JOB_INFO_KEY = "CURRENT_JOB_INFO_KEY";
/**
* SchedulerFactory 用于Scheduler的创建和管理
* getSchedule():创建调度者
* @return
* @throws Exception
*/
public static Scheduler getScheduler() throws Exception {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
return schedulerFactory.getScheduler();
}
/**
* 初始化scheduler 获取Scheduler 增加job任务
* @param scheduler
* @param jobInfos
* @throws Exception
*/
public static void init(Scheduler scheduler, List<RealCrtMaterResultDO> jobInfos) throws Exception {
for (RealCrtMaterResultDO jobInfo : jobInfos) {
if (jobInfo.getCron_status().toString().equals("1") && jobInfo.getValid_status().equals("1")) {
addJob(scheduler, jobInfo);
}
}
}
/**
* 指定时间间隔启动调度
* @param scheduler
* @param delayedSeconds
* @throws Exception
*/
public static void run(Scheduler scheduler, int delayedSeconds) throws Exception {
scheduler.startDelayed(delayedSeconds);
}
/**
* 代码用法解析:
* JobDetail
* 增加job执行任务。JobBuilder --建造者模式。链式建造。
* newJob():定义job任务。每个JobInfoDto是真正执行逻辑所在。
* withIdentity(): 定义name/group
* jobDetail.getJobDataMap().put():实现了java.util.Map 接口。可以向 JobDataMap 中存入键/值对,那些数据对可在的 Job 类中传递和进行访问。这是一个向 Job 传送配置的信息便捷方法。
* Trigger:触发条件
* 一个Job可以对应多个Trigger。当多个Trigger同一时间点出发,那么根据优先级判断。数字越大,优先级越高。默认优先级为5。同一个任务有多个trigger时,触发先后顺序:时间->优先级->字母排序
* newTrigger()定义触发器。TriggerBuilder --建造者模式。链式建造。
* withIdentity():定义name/group
* startAt():根据job任务间隔时间来触发执行job任务。 【startNow():一旦加入scheduler,立即生效】
* withSchedule():增加Schedule 使用SimpleSchedule简单调度器,指定时间间隔触发。
* scheduleJob():注册Trigger和job并进行调度
* @param scheduler
* @param jobInfo
* @throws Exception
*/
public static void addJob(Scheduler scheduler, RealCrtMaterResultDO jobInfo) throws Exception {
Class<Job> cls = (Class<Job>) Class.forName("cn.com.winpeace.kiban.winperpsrv.allofpackages.IfWorkJob");
JobDetail jobDetail = JobBuilder.newJob(cls)
.withIdentity(jobInfo.getIf_id(), jobInfo.getCustomer_id())
.build();
jobDetail.getJobDataMap().put(CURRENT_JOB_INFO_KEY, jobInfo);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobInfo.getIf_id(), jobInfo.getCustomer_id())
.startAt(getStartTime(jobInfo.getCron_intervals()))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(jobInfo.getCron_intervals()).repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
public static void addManagerJob(Scheduler scheduler, RealCrtMaterResultDO jobInfo) throws Exception {
Class<Job> cls = (Class<Job>) Class.forName(jobInfo.getJobClass());
JobDetail jobDetail = JobBuilder.newJob(cls)
.withIdentity(jobInfo.getIf_id(), jobInfo.getCustomer_id())
.build();
jobDetail.getJobDataMap().put(CURRENT_JOB_INFO_KEY, jobInfo);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobInfo.getIf_id(), jobInfo.getCustomer_id())
.startAt(getStartTime(jobInfo.getCron_intervals()))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(jobInfo.getCron_intervals()).repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 刷新job属性
* JobKey:JobKey是表明Job身份的一个对象
* getJobDetail();根据jobkey获取job详情
* getJobDataMap():根据创建时放入的map的key查询job配置信息
* 重新创建触发器
* 使用更新后的job间隔时间
* @param scheduler
* @param jobInfo
* @throws Exception
*/
public static void rescheduleJob(Scheduler scheduler, RealCrtMaterResultDO jobInfo) throws Exception {
JobKey jobKey = JobKey.jobKey(jobInfo.getIf_id(), jobInfo.getCustomer_id());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
RealCrtMaterResultDO oldJobInfo = (RealCrtMaterResultDO) jobDetail.getJobDataMap().get(CURRENT_JOB_INFO_KEY);
oldJobInfo.setCron_intervals(jobInfo.getCron_intervals());
oldJobInfo.setCron_date(jobInfo.getCron_date());
oldJobInfo.setCron_time(jobInfo.getCron_time());
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getIf_id(), jobInfo.getCustomer_id());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobInfo.getIf_id(), jobInfo.getCustomer_id())
.startAt(getStartTime(jobInfo.getCron_intervals(), jobInfo.getCron_date()+" "+jobInfo.getCron_time()))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(jobInfo.getCron_intervals()).repeatForever())
.build();
scheduler.rescheduleJob(triggerKey, trigger);
}
/**
* 停止job任务 unscheduleJob针对TriggerKey【deleteJob针对jobKey】
* @param scheduler
* @param jobInfo
* @throws Exception
*/
public static void unscheduleJob(Scheduler scheduler, RealCrtMaterResultDO jobInfo) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getIf_id(), jobInfo.getCustomer_id());
scheduler.unscheduleJob(triggerKey);
}
private static Date getStartTime(int delayedSeconds) {
Date date = new Date(System.currentTimeMillis() + 1000 * delayedSeconds);
return date;
}
private static Date getStartTime(int intervalInSeconds, String delayedSeconds) {
//date转化为毫秒数
Date date = null;
try {
System.out.println(delayedSeconds);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long time = simpleDateFormat.parse(delayedSeconds).getTime();
long diff=0;
if(time>System.currentTimeMillis()){
//若设定时间比之当前时间大再计算推迟时间
diff = System.currentTimeMillis()-time;
}
date = new Date(System.currentTimeMillis() + 1000 * (intervalInSeconds + diff));
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public static JobDetail getJobDetail(Scheduler scheduler, RealCrtMaterResultDO jobInfo) throws Exception {
JobKey jobKey = JobKey.jobKey(jobInfo.getIf_id(), jobInfo.getCustomer_id());
return scheduler.getJobDetail(jobKey);
}
public static RealCrtMaterResultDO getJobInfo(JobDetail jobDetail) throws Exception {
return (RealCrtMaterResultDO) jobDetail.getJobDataMap().get(CURRENT_JOB_INFO_KEY);
}
public static long getSecond(String time){
long s = 0;
if(time.length()==8){ //时分秒格式00:00:00
int index1=time.indexOf(":");
int index2=time.indexOf(":",index1+1);
s = Integer.parseInt(time.substring(0,index1))*3600;//小时
s+=Integer.parseInt(time.substring(index1+1,index2))*60;//分钟
s+=Integer.parseInt(time.substring(index2+1));//秒
}
if(time.length()==5){//分秒格式00:00
s = Integer.parseInt(time.substring(time.length()-2)); //秒 后两位肯定是秒
s+=Integer.parseInt(time.substring(0,2))*60; //分钟
}
return s;
}
}
最重要的是。面向接口编程。以及抽取共通业务和实现代码差异化。
怎样以更简洁的方式实现业务的可扩展性、可维护性 。以及降低调查问题的难度是比较重要的。还是需要思维的更抽象化来实现。以后也要尽量往这方面更高的层次去想。去提高自己提取抽象业务的能力。