使用asm動態爲redisson定時任務生成目標類

Redisson定時任務目標類只接收Runnable實例,我想要的是它可以使用任意類的無參方法作爲目標類,所以選擇用asm實現了一個動態生成目標類的方法。


public class TaskGenerator  extends ClassLoader{


    private TaskGenerator() {
        super(TaskGenerator.class.getClassLoader());
    }

    public final static TaskGenerator classLoader=new TaskGenerator();

    private static ConcurrentMap<String,byte[]> clzBytesCache=new ConcurrentHashMap<>();


    private Class loadTemp(String generatedClass,String className,String method){
        synchronized (getClassLoadingLock(generatedClass)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(generatedClass);
            if (c == null) {
                String description=generatedClass.replace('.','/');
                byte[] bytes=make(description,className,method);
                clzBytesCache.put(description+".class",bytes);
                c=defineClass(generatedClass,bytes,0,bytes.length);
            }
            return c;
        }
    }

    /**
    * Redisson序列化目標類時會調用getResourceAsStream獲取原始class byte數組,
    *  因爲是動態生成的找不到class文件,所以需要覆蓋下
    */
    @Override
    public InputStream getResourceAsStream(String name) {
        //
        if(clzBytesCache.containsKey(name)){
            return new ByteArrayInputStream(clzBytesCache.get(name));
        }
        return super.getResourceAsStream(name);
    }

    /**
     * 構造一個類,形如
     *
     * public class RedissonTaskXXXXX implements Runnable, Serializable {
     *     public static final long serialVersionUID = -45329617L;
     *
     *     \\@Autowired
     *     public ScheduleTest task;//不手工實例化對象,依賴調用時Redisson自動注入
     *
     *     public RedssionTaskMBean460b2509() {
     *     }
     *
     *     public void run() {
     *         try {
     *             this.task.test123();
     *         } catch (Exception var2) {
     *             var2.printStackTrace();
     *         }
     *
     *     }
     * }
     *
     * @param generatedClass 生成類類名
     * @param className 目標類名
     * @param method 目標類方法
     * @return
     */
    private static byte[] make(String generatedClass, String className, String method) {

        String nest=className.replace('.','/');
        String nestDescriptor="L"+nest+";";
        ClassWriter classWriter = new ClassWriter(0);

        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC ,
                generatedClass, null, "java/lang/Object", new String[]{"java/lang/Runnable","java/io/Serializable"});

        classWriter
                .visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC+Opcodes.ACC_FINAL, "serialVersionUID","J", null, (long)(className+":"+method).hashCode())
                .visitEnd();
        classWriter
                .visitField(Opcodes.ACC_PUBLIC, "task",nestDescriptor, null, null)
                .visitAnnotation("Lorg.springframework.beans.factory.annotation.Autowired;".replaceAll("\\.","/"),true)
                .visitEnd();
        // 添加空構造器
        MethodVisitor mw = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
                null, null);

        mw.visitVarInsn(Opcodes.ALOAD, 0); // this 入棧
        mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
                "()V",false);
        mw.visitInsn(Opcodes.RETURN);
        mw.visitMaxs(1, 1);
        mw.visitEnd();



        mw =classWriter
                .visitMethod(Opcodes.ACC_PUBLIC,"run","()V",null,null);

        Label start=new Label(), ret=new Label(), exception=new Label();

        mw.visitCode();

        mw.visitTryCatchBlock(start,exception,exception,"java/lang/Exception");
        mw.visitLabel(start);
        mw.visitVarInsn(Opcodes.ALOAD, 0);
        //task 入棧
        mw.visitFieldInsn(Opcodes.GETFIELD,generatedClass,"task",nestDescriptor);

        mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, nest,
                method, "()V",false);

        mw.visitJumpInsn(Opcodes.GOTO,ret);

        mw.visitLabel(exception);

        mw.visitFrame(Opcodes.F_FULL,1,new Object[]{generatedClass},1,new Object[]{"java/lang/Exception"});

        mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception",
                "printStackTrace", "()V",false);

        mw.visitLabel(ret);
        mw.visitFrame(Opcodes.F_SAME,0,null,0,null);
        mw.visitInsn(Opcodes.RETURN);

        mw.visitMaxs(2, 2);
        mw.visitEnd();
        classWriter.visitEnd();

        byte[] bytes=classWriter.toByteArray();
        return bytes;

    }

    public static Class<Runnable> loadClass(String className,String methodName) throws CodeGenerateException {
        Class clz;
        try {
            clz=Class.forName(className);
            if(!Modifier.isPublic(clz.getModifiers())){
                throw new CodeGenerateException("任務類不可爲非public類型 "+className);
            }
        } catch (ClassNotFoundException e) {
            throw new CodeGenerateException("不存在此任務類:"+className);
        }

        try {
            Method method=clz.getDeclaredMethod(methodName);
            if(Modifier.isStatic(method.getModifiers())){
                throw new CodeGenerateException("任務類不可使用靜態方法 "+className+"."+methodName);
            }
            if(!Modifier.isPublic(method.getModifiers())){
                throw new CodeGenerateException("任務類不可使用非public方法 "+className+"."+methodName);
            }
        } catch (NoSuchMethodException e) {
            throw new CodeGenerateException("任務類不存在無參方法 "+className+"."+methodName);
        }

        String generatedClass="com.xxxx.yyyyy.autotask.RedissonTask"+Integer.toString(Math.abs((className+methodName).hashCode()),16);

        return classLoader.loadTemp(generatedClass,className,methodName);
    }

    public static class CodeGenerateException extends Exception {
        public CodeGenerateException(String message) {
            super(message);
        }

        public CodeGenerateException(String message, Throwable cause) {
            super(message, cause);
        }
    }

}

Redisson任務創建代碼


Runnable target=TaskGenerator.loadClass(task.beanClass,task.method).newInstance();
                
RScheduledFuture scheduledFuture=service.schedule(target,CronSchedule.of(task.cronExpression));
                    String taskId=scheduledFuture.getTaskId();

......

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章