The article gives an overview of

  • Continue above, describe admin background code to directly execute how to achieve
  • The explanation of relevant technical points

Admin support for Java/other scripts

  • Take a look at the types of scheduling tasks supported by Admin
    • A total of 7 modes are supported (bean mode is a pre-written task class, with executor startup /GLUE_GROOVY is code written on admin)
public enum GlueTypeEnum {
    BEAN("BEAN".false.null.null),
    GLUE_GROOVY("GLUE(Java)".false.null.null),
    GLUE_SHELL("GLUE(Shell)".true."bash".".sh"),
    GLUE_PYTHON("GLUE(Python)".true."python".".py"),
    GLUE_PHP("GLUE(PHP)".true."php".".php"),
    GLUE_NODEJS("GLUE(Nodejs)".true."node".".js"),
    GLUE_POWERSHELL("GLUE(PowerShell)".true."powershell".".ps1");
}    
Copy the code
  • How does an executor implement a request carrying GLUE_GROOVY
    • Positioning to com. XXL. Job. Core. Biz. Impl. ExecutorBizImpl# run
if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
      // valid handler
  if (jobHandler == null) {
    try {
      / / triggerParam getGlueSource () to obtain the string came the admin to send the source code
      // We initialize the glue language factory to get a singleton instance
      IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
      // After jobHandler is constructed, subsequent execution is the same as the Bean execution described in the previous article
      jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
      return newReturnT<String>(ReturnT.FAIL_CODE, e.getMessage()); }}}Copy the code
  • GlueFactory
public class GlueFactory {
  private static GlueFactory glueFactory = new GlueFactory();
  public static GlueFactory getInstance(a) {
    return glueFactory;
  }
  // The factory to be constructed is native or Spring based on the runtime environment
  / / SpringGlueFactory GlueFactory compared simply realize the com. XXL. Job. Core. Glue. Impl. SpringGlueFactory# injectService
  InjectService methods are mainly used to handle dependency injection
  public static void refreshInstance(int type) {
    if (type == 0) {
      glueFactory = new GlueFactory();
    } else if (type == 1) {
      glueFactory = newSpringGlueFactory(); }}// Class loader
  private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
  // cache, key is md5 of source code
  privateConcurrentMap<String, Class<? >> CLASS_CACHE =new ConcurrentHashMap<>();
}
Copy the code
  • Load instance loadNewInstance
public IJobHandler loadNewInstance(String codeSource) throws Exception{
    if(codeSource! =null && codeSource.trim().length()>0) {
        // Convert the source code to classClass<? > clazz = getCodeSourceClass(codeSource);if(clazz ! =null) {
            // Call the class method to convert to an instance
            Object instance = clazz.newInstance();
            if(instance! =null) {
                // Classes written by admin must implement IJobHandler
                if (instance instanceof IJobHandler) {
                    // Do field injection here, such as @autowired
                    this.injectService(instance);
                    // The returned instance is no different from our own implementation bean task class
                    return (IJobHandler) instance;
                } else {
                    throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, "
                            + "cannot convert from instance["+ instance.getClass() +"] to IJobHandler"); }}}}throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, instance is null");
}
// Convert string source code to class, that is, to class loading
privateClass<? > getCodeSourceClass(String codeSource){try {
        // Convert the source to an MD5 string
        byte[] md5 = MessageDigest.getInstance("MD5").digest(codeSource.getBytes());
        String md5Str = new BigInteger(1, md5).toString(16);
        // Check to see if the corresponding class exists in the cacheClass<? > clazz = CLASS_CACHE.get(md5Str);if(clazz == null) {// Use GroovyClassLoader for class loading
            clazz = groovyClassLoader.parseClass(codeSource);
            CLASS_CACHE.putIfAbsent(md5Str, clazz);
        }
        return clazz;
    } catch (Exception e) {
        returngroovyClassLoader.parseClass(codeSource); }}Copy the code
  • Look at the injected code that implements injectService
@Override
public void injectService(Object instance){
    // Determine which instance is passed in first
    / / return empty
    if (instance==null) {
        return;
    }
    // Check if the Spring context exists
    // The context-carried beanFactory in the Spring environment is used to hold bean instances
    if (XxlJobSpringExecutor.getApplicationContext() == null) {
        return;
    }
    // Get the field of the instance
    // There is no way to get the superclass field, so the best way to write the code is to implement IJobHandler directly according to its specification
    // Of course you can modify the source code so that fields of the parent class can also be injected
    Field[] fields = instance.getClass().getDeclaredFields();
    for (Field field : fields) {
        // Exclude static fields
        if (Modifier.isStatic(field.getModifiers())) {
            continue;
        }

        Object fieldBean = null;
        
        // The next step is to handle the injection logic
        if(AnnotationUtils.getAnnotation(field, Resource.class) ! =null) {
            try {
                // Check the Resource field injection
                // The logic for Resource injection is to get the value of the annotation first, if the value does not exist, the name of the field is taken
                Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
                if(resource.name()! =null && resource.name().length()>0){
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(resource.name());
                } else{ fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getName()); }}catch (Exception e) {
            }
            if (fieldBean==null ) {
                // Get by typefieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType()); }}else if(AnnotationUtils.getAnnotation(field, Autowired.class) ! =null) {
            Autowired injection can specify the name of the bean with a Qualifier. If this annotation does not exist, the bean type is used by default
            Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
            if(qualifier! =null&& qualifier.value()! =null && qualifier.value().length()>0) {
                fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(qualifier.value());
            } else{ fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType()); }}if(fieldBean! =null) {
            field.setAccessible(true);
            try {
                field.set(instance, fieldBean);
            } catch (IllegalArgumentException e) {
                logger.error(e.getMessage(), e);
            } catch(IllegalAccessException e) { logger.error(e.getMessage(), e); }}}}Copy the code
  • The mechanics of GroovyClassLoader will be covered in the next article!
  • How does the xxL-job executor support other scripting languages?
    • ScriptJobHandler indicates that it is a script file executor
// The code will be truncated
// Leave only the key parts to explain
public class ScriptJobHandler extends IJobHandler {

    // constructor
    public ScriptJobHandler(int jobId, long glueUpdatetime, String gluesource, GlueTypeEnum glueType){
        // Omitted N lines of code
        // The main purpose of this code is to traverse the glue code store path
        // If there is a glue code file with the same jobid, delete it to prevent dirty logic
        File glueSrcPath = new File(XxlJobFileAppender.getGlueSrcPath());
        if (glueSrcPath.exists()) {
            File[] glueSrcFileList = glueSrcPath.listFiles();
            if(glueSrcFileList! =null && glueSrcFileList.length>0) {
                for (File glueSrcFileItem : glueSrcFileList) {
                    if (glueSrcFileItem.getName().startsWith(String.valueOf(jobId)+"_")) {
                        glueSrcFileItem.delete();
                    }
                }
            }
        }

    }
    // The actual code to execute
    @Override
    public void execute(a) throws Exception {
        // Execute the command
        // These are all defined by enumerations
        String cmd = glueType.getCmd();
        // This section creates the script file
        String scriptFileName = XxlJobFileAppender.getGlueSrcPath()
                .concat(File.separator)
                .concat(String.valueOf(jobId))
                .concat("_")
                .concat(String.valueOf(glueUpdatetime))
                .concat(glueType.getSuffix());
        File scriptFile = new File(scriptFileName);
        if(! scriptFile.exists()) { ScriptUtil.markScriptFile(scriptFileName, gluesource); }// Get the log file name
        // You can write execution logs later
        String logFileName = XxlJobContext.getXxlJobContext().getJobLogFileName();
        // script params: 0=param, 1= fragment number, 2= total number of fragments
        String[] scriptParams = new String[3];
        scriptParams[0] = XxlJobHelper.getJobParam();
        scriptParams[1] = String.valueOf(XxlJobContext.getXxlJobContext().getShardIndex());
        scriptParams[2] = String.valueOf(XxlJobContext.getXxlJobContext().getShardTotal());
        // invoke
        XxlJobHelper.log("----------- script file:"+ scriptFileName +"-- -- -- -- -- -- -- -- -- -- -");
        // Execute the script code
        int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, scriptParams);
        if (exitValue == 0) {
            XxlJobHelper.handleSuccess();
            return;
        } else {
            XxlJobHelper.handleFail("script exit value("+exitValue+") is failed");
            return; }}}Copy the code
  • ScriptUtil.execToFile
public static int execToFile(String command, String scriptFile, String logFile, String... params) throws IOException {
    FileOutputStream fileOutputStream = null;
    Thread inputThread = null;
    Thread errThread = null;
    try {
        // Open the file output stream
        fileOutputStream = new FileOutputStream(logFile, true);
        // Combine commands and arguments into arrays
        List<String> cmdarray = new ArrayList<>();
        cmdarray.add(command);
        cmdarray.add(scriptFile);
        if(params! =null && params.length>0) {
            for (String param:params) {
                cmdarray.add(param);
            }
        }
        String[] cmdarrayFinal = cmdarray.toArray(new String[cmdarray.size()]);
        // Java invokes system commands to start the process API
        final Process process = Runtime.getRuntime().exec(cmdarrayFinal);
        // Read the output stream of the started process into a file
        // The copy method is a copy of a stream
        final FileOutputStream finalFileOutputStream = fileOutputStream;
        inputThread = new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    copy(process.getInputStream(), finalFileOutputStream, new byte[1024]);
                } catch(IOException e) { XxlJobHelper.log(e); }}}); errThread =new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    copy(process.getErrorStream(), finalFileOutputStream, new byte[1024]);
                } catch(IOException e) { XxlJobHelper.log(e); }}}); inputThread.start(); errThread.start();// process-wait
        int exitValue = process.waitFor();      // exit code: 0=success, 1=error
        // log-thread join
        inputThread.join();
        errThread.join();
        return exitValue;
    } catch (Exception e) {
        XxlJobHelper.log(e);
        return -1;
    } finally {
        if(fileOutputStream ! =null) {
            try {
                fileOutputStream.close();
            } catch(IOException e) { XxlJobHelper.log(e); }}if(inputThread ! =null && inputThread.isAlive()) {
            inputThread.interrupt();
        }
        if(errThread ! =null&& errThread.isAlive()) { errThread.interrupt(); }}}Copy the code

At the end

  • We’ll look at GroovyClassLoader in the next article, and then we’ll try to make a simple Java platform for doing problems! Similar to Leetcode, but not as powerful