当前位置 博文首页 > 少年时未觉悟 ,觉悟时不再年少,还危机四伏!:Quartz 创建 Job

    少年时未觉悟 ,觉悟时不再年少,还危机四伏!:Quartz 创建 Job

    作者:[db:作者] 时间:2021-07-16 15:31

    背景

    任务调度框架 ElasticJob 是基于 Quartz 开发的,看它的源码时,发现它封装的 LiteJob 类包含了两个成员变量,但是调试任务调度的过程中,这两个成员变量是有数据的。

    它们究竟是什么时候被赋值的呢?

    以往我用 Quartz 的时候,如果自定义的 Job 类中包含成员变量的话,都是在无参构造函数中初始化的。一直以为,Job 的实现类都不适合包含成员变量呢!

    ElasticJob 调度过程

    ElasticJob 的调度流程是从 JobScheduler 类的 init 方法开始的,由它回归到了 Quartz 的任务创建过程,底层是用 API 创建了一个 LiteJob 任务类。

        public void init() {
            LiteJobConfiguration liteJobConfigFromRegCenter = schedulerFacade.updateJobConfiguration(liteJobConfig);
            JobRegistry.getInstance().setCurrentShardingTotalCount(liteJobConfigFromRegCenter.getJobName(), liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getShardingTotalCount());
            JobScheduleController jobScheduleController = new JobScheduleController(
                    createScheduler(), createJobDetail(liteJobConfigFromRegCenter.getTypeConfig().getJobClass()), liteJobConfigFromRegCenter.getJobName());
            JobRegistry.getInstance().registerJob(liteJobConfigFromRegCenter.getJobName(), jobScheduleController, regCenter);
            schedulerFacade.registerStartUpInfo(!liteJobConfigFromRegCenter.isDisabled());
            jobScheduleController.scheduleJob(liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getCron());
        }
    

    这里的 createJobDetail 方法,它是架起 ElasticJob 和 Quartz 的桥梁。

    private JobDetail createJobDetail(final String jobClass) {
            JobDetail result = JobBuilder.newJob(LiteJob.class).withIdentity(liteJobConfig.getJobName()).build();
            result.getJobDataMap().put(JOB_FACADE_DATA_MAP_KEY, jobFacade);
            Optional<ElasticJob> elasticJobInstance = createElasticJobInstance();
            if (elasticJobInstance.isPresent()) {
                result.getJobDataMap().put(ELASTIC_JOB_DATA_MAP_KEY, elasticJobInstance.get());
            } else if (!jobClass.equals(ScriptJob.class.getCanonicalName())) {
                try {
                    result.getJobDataMap().put(ELASTIC_JOB_DATA_MAP_KEY, Class.forName(jobClass).newInstance());
                } catch (final ReflectiveOperationException ex) {
                    throw new JobConfigurationException("Elastic-Job: Job class '%s' can not initialize.", jobClass);
                }
            }
            return result;
        }
    

    这段代码就是我们所熟悉的 Quartz 调度 API 了啦, LiteJob 的定义非常简单:

    public final class LiteJob implements Job {
        
        @Setter
        private ElasticJob elasticJob;
        
        @Setter
        private JobFacade jobFacade;
        
        @Override
        public void execute(final JobExecutionContext context) throws JobExecutionException {
            JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();
        }
    }
    

    它由两个成员变量,它们的赋值在 createJobDetail 方法中存储到 JobDataMap 中了,数据的 key 与变量名称一致。

    接着到了 Quartz 执行任务调度时,创建实例的实例会自动完成成员变量的赋值操作。

    Quartz 创建 Job 的过程

    PropertySettingJobFactory 创建 Job 实例时,会获取 JobDataMap 中的数据,通过反射设置实例的成员变量。

    具体源码是:

     @Override
     public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
    
         Job job = super.newJob(bundle, scheduler);
         
         JobDataMap jobDataMap = new JobDataMap();
         jobDataMap.putAll(scheduler.getContext());
         jobDataMap.putAll(bundle.getJobDetail().getJobDataMap());
         jobDataMap.putAll(bundle.getTrigger().getJobDataMap());
    
         setBeanProps(job, jobDataMap);
         
         return job;
     }
    

    setBeanProps 完成对 Job 实例类的属性设置,它会遍历 JobDataMap 中的所有数据,然后根据数据类型,和 key 找到对应的 setter 方法,去掉了细节判断的主要代码如下:

    protected void setBeanProps(Object obj, JobDataMap data) throws SchedulerException {
    
            for (Iterator<?> entryIter = data.getWrappedMap().entrySet().iterator(); entryIter.hasNext();) {
                Map.Entry<?,?> entry = (Map.Entry<?,?>)entryIter.next();
                
                String name = (String)entry.getKey();
                String c = name.substring(0, 1).toUpperCase(Locale.US);
                String methName = "set" + c + name.substring(1);
            
                java.lang.reflect.Method setMeth = getSetMethod(methName, propDescs);
            
                Class<?> paramType = null;
                Object o = null;
                
                try { 
                    paramType = setMeth.getParameterTypes()[0];
                    o = entry.getValue();
                    
                    Object parm = null;
                    if (paramType.isPrimitive()) {
                        if (paramType.equals(int.class)) {
                            if (o instanceof String) {                            
                                parm = Integer.valueOf((String)o);
                            } else if (o instanceof Integer) {
                                parm = o;
                            }
                        } else if (paramType.equals(long.class)) {
                            if (o instanceof String) {
                                parm = Long.valueOf((String)o);
                            } else if (o instanceof Long) {
                                parm = o;
                            }
                        } else if (paramType.equals(float.class)) {
                            if (o instanceof String) {
                                parm = Float.valueOf((String)o);
                            } else if (o instanceof Float) {
                                parm = o;
                            }
                        } else if (paramType.equals(double.class)) {
                            if (o instanceof String) {
                                parm = Double.valueOf((String)o);
                            } else if (o instanceof Double) {
                                parm = o;
                            }
                        } else if (paramType.equals(boolean.class)) {
                            if (o instanceof String) {
                                parm = Boolean.valueOf((String)o);
                            } else if (o instanceof Boolean) {
                                parm = o;
                            }
                        } else if (paramType.equals(byte.class)) {
                            if (o instanceof String) {
                                parm = Byte.valueOf((String)o);
                            } else if (o instanceof Byte) {
                                parm = o;
                            }
                        } else if (paramType.equals(short.class)) {
                            if (o instanceof String) {
                                parm = Short.valueOf((String)o);
                            } else if (o instanceof Short) {
                                parm = o;
                            }
                        } else if (paramType.equals(char.class)) {
                            if (o instanceof String) {
                                String str = (String)o;
                                if (str.length() == 1) {
                                    parm = Character.valueOf(str.charAt(0));
                                }
                            } else if (o instanceof Character) {
                                parm = o;
                            }
                        }
                    } else if ((o != null) && (paramType.isAssignableFrom(o.getClass()))) {
                        parm = o;
                    }
                                         
                    setMeth.invoke(obj, new Object[]{ parm });
      }
    

    这就解释了为什么 LiteJob 类包含两个成员变量,但是实例还被正常创建且属性被正常赋值的问题。

    启示录

    通过跟踪 ElasticJob 源码,发现了自己对 Quartz 的知识盲点,也是一个神奇收获。

    以后有机会再使用 Quartz 的时候,就又多了一种设置 Job 成员变量的途径了。LiteJob 这种优雅的类定义,还是可以学一下的!

    cs