Springboot动态生成一个项目中没有的类(class对象)

本文介绍了如何在Springboot应用中根据用户上传的拦截器源码动态生成类对象并调用其方法。首先利用JavaCompiler进行源码编译为class文件,再通过自定义类加载器将class文件加载到项目中,实现动态加载和执行。

这两天新接到一个需求,是这样。从页面上文本写一个拦截器,然后上传这个拦截器的源码,生成对象并调用对象的方法。

 

我当时的反应就是很懵逼的 。。。这个操作也太骚了吧

年前写了个用groovy来执行,但是会出现一些问题,不能满足需求。

年后开始重新思考这个问题,然后在网上找到了一篇资料

http://blog.sina.com.cn/s/blog_70279be20101dk0j.html

重点是找到了一个东西  

jdk提供一个动态编译的类。JavaCompiler javac;

这样首先是解决了,将源码编译成为对应的class文件,接下来是动态加载class文件到项目中生成。这一步就通过类加载器来完成。

 

理清除思路后,我就开始做一个最简单的通过main方法执行生成一个动态类 ,然后将代码移植到springboot项目中。

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: Bobby
 * @create: 2020-02-11 15:29
 * @description:动态执行
 **/
public class DynamicLoader {

    /**
     * auto fill in the java-name with code, return null if cannot find the public class
     *
     * @param javaSrc source code string
     * @return return the Map, the KEY means ClassName, the VALUE means bytecode.
     */
    public static Map<String, byte[]> compile(String javaSrc) {
        Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
        Matcher matcher = pattern.matcher(javaSrc);
        if (matcher.find()) {
            return compile(matcher.group(1) + ".java", javaSrc);
        }
        return null;
    }

    public static Map<String, byte[]> compile(String javaName, String javaSrc) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
        try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
            JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            if (task.call())
                return manager.getClassBytes();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }


}
import javax.tools.*;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Bobby
 * @create: 2020-02-11 16:10
 * @description:
 **/
public class MemoryClassLoader extends URLClassLoader {
    Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

    public MemoryClassLoader(Map<String, byte[]> classBytes) {
        super(new URL[0], MemoryClassLoader.class.getClassLoader());
        this.classBytes.putAll(classBytes);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] buf = classBytes.get(name);
        if (buf == null) {
            return super.findClass(name);
        }
        classBytes.remove(name);
        return defineClass(name, buf, 0, buf.length);
    }

}


/**
 * * MemoryJavaFileManager.java
 * JavaFileManager that keeps compiled .class bytes in memory.
 */
@SuppressWarnings("unchecked")
final class MemoryJavaFileManager extends ForwardingJavaFileManager {
    /**
     * Java source file extension.
     */
    private final static String EXT = ".java";
    private Map<String, byte[]> classBytes;

    public MemoryJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
        classBytes = new HashMap<String, byte[]>();
    }

    public Map<String, byte[]> getClassBytes() {
        return classBytes;
    }

    public void close() throws IOException {
        classBytes = new HashMap<String, byte[]>();
    }

    public void flush() throws IOException {
    }

    /**
     * A file object used to represent Java source coming from a string.
     */
    private static class StringInputBuffer extends SimpleJavaFileObject {
        final String code;

        StringInputBuffer(String name, String code) {
            super(toURI(name), Kind.SOURCE);
            this.code = code;
        }

        public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
            return CharBuffer.wrap(code);
        }

        public Reader openReader() {
            return new StringReader(code);
        }
    }

    /**
     * A file object that stores Java bytecode into the classBytes map.
     */
    private class ClassOutputBuffer extends SimpleJavaFileObject {
        private String name;

        ClassOutputBuffer(String name) {
            super(toURI(name), Kind.CLASS);
            this.name = name;
        }

        public OutputStream openOutputStream() {
            return new FilterOutputStream(new ByteArrayOutputStream()) {
                public void close() throws IOException {
                    out.close();
                    ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                    classBytes.put(name, bos.toByteArray());
                }
            };
        }
    }

    public JavaFileObject getJavaFileForOutput(Location location,
                                               String className,
                                               JavaFileObject.Kind kind,
                                               FileObject sibling) throws IOException {
        if (kind == JavaFileObject.Kind.CLASS) {
            return new ClassOutputBuffer(className);
        } else {
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    }

    static JavaFileObject makeStringSource(String name, String code) {
        return new StringInputBuffer(name, code);
    }

    static URI toURI(String name) {
        File file = new File(name);
        if (file.exists()) {
            return file.toURI();
        } else {
            try {
                final StringBuilder newUri = new StringBuilder();
                newUri.append("mfm:///");
                newUri.append(name.replace('.', '/'));
                if (name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
                return URI.create(newUri.toString());
            } catch (Exception exp) {
                return URI.create("mfm:///com/sun/script/java/java_source");
            }
        }
    }
}

然后开始通过接口测试

 

 

import com.ocft.gateway.clazzloader.DynamicLoader;
import com.ocft.gateway.clazzloader.MemoryClassLoader;
import com.ocft.gateway.common.context.GatewayContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

/**
 * @author: Bobby
 * @create: 2020-02-14 20:28
 * @description:
 **/
@RestController
@RequestMapping("/dynamicloader")
public class DynamicLoaderTestController {


    private static String javaSrc = "public class TestClass{" +
            "public void sayHello(String msg) {" +
            "System.out.printf(\"Hello %s! This message from a Java String.%n\",msg);" +
            "}" +
            "public int add(int a,int b){" +
            "return a+b;" +
            "}" +
            "}";


    private static String javaSrcInterreptor=
            "import com.alibaba.fastjson.JSONObject;\n" +
            "import com.ocft.gateway.common.context.GatewayContext;\n" +
            "import com.ocft.gateway.common.evaluator.JsonOperateEvalutor;\n" +
            "import com.ocft.gateway.common.exceptions.GatewayException;\n" +
            "import com.ocft.gateway.entity.GatewayInterface;\n" +
            "import com.ocft.gateway.entity.InterfaceConfig;\n" +
            "import com.ocft.gateway.entity.RequestAccessLimit;\n" +
            "import com.ocft.gateway.entity.RequestType;\n" +
            "import com.ocft.gateway.interceptor.GatewayInterceptor;\n" +
            "import com.ocft.gateway.service.IInterfaceConfigService;\n" +
            "import com.ocft.gateway.service.IRequestTypeService;\n" +
            "import com.ocft.gateway.utils.MathUtil;\n" +
            "import com.ocft.gateway.utils.RedisUtil;\n" +
            "import com.ocft.gateway.utils.WebUtil;\n" +
            "import lombok.extern.slf4j.Slf4j;\n" +
            "import org.apache.commons.lang3.StringUtils;\n" +
            "import org.slf4j.Logger;\n" +
            "import org.slf4j.LoggerFactory;\n" +
            "import org.springframework.beans.factory.annotation.Autowired;\n" +
            "import org.springframework.stereotype.Component;\n" +
            "import javax.servlet.http.HttpServletRequest;\n" +
            "import java.util.Date;\n" +
            "import java.util.List;" +
            "@Slf4j\n" +
            "@Component\n" +
            "public class TestIntercept implements GatewayInterceptor {" +
            "    @Override\n" +
            "    public void doInterceptor(GatewayContext context) {" +
            "       System.out.printf(\"Hello Bobby\");    " +
            "    }" +
            "}" +
            "";

    @RequestMapping("/test")
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Map<String, byte[]> bytecode = DynamicLoader.compile("TestIntercept.java", javaSrcInterreptor);
        MemoryClassLoader classLoader = new MemoryClassLoader(bytecode);
        Class clazz = classLoader.loadClass("TestIntercept");
        Object object = clazz.newInstance();
        Method doInterceptor = clazz.getMethod("doInterceptor", GatewayContext.class);
        doInterceptor.invoke(object, new GatewayContext());
    }



    public void testCompile() {
        Map<String, byte[]> bytecode = DynamicLoader.compile("TestClass.java", javaSrc);
        for (Iterator<String> iterator = bytecode.keySet().iterator(); iterator.hasNext(); ) {
            String key = iterator.next();
            byte[] code = bytecode.get(key);
            System.out.printf("Class: %s, Length: %d%n", key, code.length);
        }
    }

最后可以成功输出  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值