动态加载类(热部署类)

原理

要实现动态加载一个类(可能会修改该类),一般有两种方法,实例化不同的classloader来加载,另一种方法是用同一个classloader来加载,但类名会修改。用的还是JDK编译工具来编译代码,classloader用自定义的classloader。代码如下:


        /**
 * Created by gaofla on 2018/3/14.
 */
public class ClassEngine {

    private URLClassLoader parentClassLoader;
    //单例
    private static ClassEngine customClassCompiler = new ClassEngine();

    private ClassEngine() {
        this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
    }


    public static ClassEngine getInstance() {
        if (customClassCompiler == null) {
            try {
                synchronized (ClassEngine.class) {
                    if (customClassCompiler == null) {
                        customClassCompiler = new ClassEngine();
                    }
                }
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
        return customClassCompiler;
    }

    /**
     * 编译并加载类
     *
     * @param className
     * @param javaCode
     * @param javaCodeVersion
     * @param newClassLoader
     * @return
     * @throws Exception
     */
    public Class<?> compileAndLoadClass(String className, String javaCode, String javaCodeVersion, boolean newClassLoader) throws Exception {
        // TODO newClassLoader 每次可以新new一个classloader来加载
        // 类名= 原来的名字+ "_版本号"
        String classNameSuffix = "_" + javaCodeVersion;

        return compileAndLoadClass(className, javaCode, classNameSuffix);
    }

    /**
     * 真正的编译和类加载实现
     *
     * @param className
     * @param javaCode
     * @param classNameSuffix
     * @return
     * @throws Exception
     */
    public Class<?> compileAndLoadClass(String className, String javaCode, String classNameSuffix) throws Exception {
        Class clz = null;

        String newClassName = className + classNameSuffix;
        // Step 1: 类名替换
        String newJavaCode = getNewJavaCode(javaCode, className, classNameSuffix);
        // Step 2: 编译代码
        ClassFileManager classFileManager = compile(newClassName, newJavaCode);
        // Step 3: 加载类
        clz = loadClass(classFileManager, newClassName, CustomerClassLoader.getDefaultSameCustomClassLoader(ClassEngine.getInstance().getParentClassLoader()));

        return clz;
    }

    /**
     * 加载类
     *
     * @param fileManager
     * @param className
     * @param customClassLoader
     * @return
     */
    private Class<?> loadClass(ClassFileManager fileManager, String className, CustomerClassLoader customClassLoader) {
        JavaClassObject jco = fileManager.getMainJavaClassObject();
        Class clz = customClassLoader.loadClass(className, jco);

        return clz;
    }

    /**
     * 更换类名
     *
     * @param originJavaCode
     * @param className
     * @param classNameSuffix
     * @return
     */
    private String getNewJavaCode(String originJavaCode, String className, String classNameSuffix) {
        Pattern pattern = Pattern.compile("([^A-Za-z0-9_])" + className + "(?![A-Za-z0-9_]+)");
        Matcher matcher = pattern.matcher(originJavaCode);
        boolean find = matcher.find();
        StringBuffer sb = new StringBuffer();
        while (find) {
            matcher.appendReplacement(sb, matcher.group() + classNameSuffix);
            find = matcher.find();
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 编译类名
     *
     * @param fullClassName
     * @param javaCode
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private ClassFileManager compile(String fullClassName, String javaCode) throws IllegalAccessException, InstantiationException {

        //获取系统编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 建立DiagnosticCollector对象
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

        // 建立用于保存被编译文件名的对象
        // 每个文件被保存在一个从JavaFileObject继承的类中
        ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));

        List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
        jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));

        //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
        List<String> options = new ArrayList<String>();
        options.add("-encoding");
        options.add("UTF-8");

        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        // 编译源程序不成功
        if (!task.call()) {
            System.out.println(compileError(diagnostics));
            return null;
        }

        return fileManager;
    }

    /**
     * 编译错误信息
     *
     * @param diagnostics
     * @return
     */
    private static String compileError(DiagnosticCollector<JavaFileObject> diagnostics) {
        StringBuilder sb = new StringBuilder();
        for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
            sb.append(compileError(diagnostic)).append("\n");
        }
        return sb.toString();
    }

    /**
     * 编译错误信息
     *
     * @param diagnostic
     * @return
     */
    private static String compileError(Diagnostic<?> diagnostic) {
        StringBuilder sb = new StringBuilder();
        sb.append("Code:[" + diagnostic.getCode() + "]\n");
        sb.append("Kind:[" + diagnostic.getKind() + "]\n");
        sb.append("Position:[" + diagnostic.getPosition() + "]\n");
        sb.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
        sb.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
        sb.append("Source:[" + diagnostic.getSource() + "]\n");
        sb.append("Message:[" + diagnostic.getMessage(null) + "]\n");
        sb.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
        sb.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
        return diagnostic.toString();
    }

    public URLClassLoader getParentClassLoader() {
        return parentClassLoader;
    }

    public void setParentClassLoader(URLClassLoader parentClassLoader) {
        this.parentClassLoader = parentClassLoader;
    }
}


/**
 * Created by gaofla on 2018/3/14.
 */
public class CustomerClassLoader extends URLClassLoader {
    private static CustomerClassLoader customClassLoader;

    private CustomerClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }

    public Class findClassByClassName(String className) throws ClassNotFoundException {
        return this.findClass(className);
    }

    /**
     * 加载类
     *
     * @param fullName
     * @param jco
     * @return
     */
    public Class loadClass(String fullName, JavaClassObject jco) {
        byte[] classData = jco.getBytes();
        return this.defineClass(fullName, classData, 0, classData.length);
    }

    /**
     * 获取相同的类加载器实例
     *
     * @param parent
     * @return
     */
    public static CustomerClassLoader getDefaultSameCustomClassLoader(ClassLoader parent) {
        if (customClassLoader == null) {
            try {
                synchronized (CustomerClassLoader.class) {
                    if (customClassLoader == null) {
                        customClassLoader = new CustomerClassLoader(parent);
                    }
                }
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
        return customClassLoader;
    }
}


/**
 * Created by gaofla on 2018/3/14.
 */
public class ClassFileManager extends ForwardingJavaFileManager {

    /**
     * 编译存储的类包括子类
     */
    private List<JavaClassObject> javaClassObjectList;

    public ClassFileManager(StandardJavaFileManager
                                    standardManager) {
        super(standardManager);
        this.javaClassObjectList = new ArrayList<JavaClassObject>();
    }

    public JavaClassObject getMainJavaClassObject() {
        if (this.javaClassObjectList != null && this.javaClassObjectList.size() > 0) {
            int size = this.javaClassObjectList.size();
            return this.javaClassObjectList.get((size - 1));
        }
        return null;
    }

    public List<JavaClassObject> getInnerClassJavaClassObject() {
        if (this.javaClassObjectList != null && this.javaClassObjectList.size() > 0) {
            int size = this.javaClassObjectList.size();
            if (size == 1) {
                return null;
            }
            return this.javaClassObjectList.subList(0, size - 1);
        }
        return null;
    }

    @Override
    public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
                                               String className, JavaFileObject.Kind kind, FileObject sibling)
            throws IOException {
        JavaClassObject jclassObject = new JavaClassObject(className, kind);
        this.javaClassObjectList.add(jclassObject);
        return jclassObject;
    }
}


/**
 * Created by gaofla on 2018/3/14.
 */
public class CharSequenceJavaFileObject  extends SimpleJavaFileObject {

    private CharSequence content;


    public CharSequenceJavaFileObject(String className,
                                      CharSequence content) {
        super(URI.create("string:///" + className.replace('.', '/')
                + Kind.SOURCE.extension), Kind.SOURCE);
        this.content = content;
    }

    @Override
    public CharSequence getCharContent(
            boolean ignoreEncodingErrors) {
        return content;
    }
}


/**
 * Created by gaofla on 2018/3/14.
 */
public class JavaClassObject extends SimpleJavaFileObject {

    protected final ByteArrayOutputStream bos =
            new ByteArrayOutputStream();


    public JavaClassObject(String name, Kind kind) {
        super(URI.create("string:///" + name.replace('.', '/')
                + kind.extension), kind);
    }


    public byte[] getBytes() {
        return bos.toByteArray();
    }

    @Override
    public OutputStream openOutputStream() throws IOException {
        return bos;

    }
}


/**
 * Created by gaofla on 2018/3/14.
 */
public class Test {
    public static void main(String args[]) throws Exception {
        String javaCode = "import com.classloader.compile.Base;\n" +
                "public class T implements Base {\n" +
                "    @Override\n" +
                "    public void say() {\n" +
                "        System.out.println(\"hello\");\n" +
                "    }\n" +
                "}";

        String version = "1";

        Class<?> clazz = ClassEngine.getInstance().compileAndLoadClass("T", javaCode, version, false);
        Base base1 = (Base) clazz.newInstance();
        base1.say();

        javaCode = "import com.classloader.compile.Base;\n" +
                "public class T implements Base {\n" +
                "    @Override\n" +
                "    public void say() {\n" +
                "        System.out.println(\"hello aaa\");\n" +
                "    }\n" +
                "}";
        version = "2";

        clazz = ClassEngine.getInstance().compileAndLoadClass("T", javaCode, version, false);
        Base base2 = (Base) clazz.newInstance();
        base2.say();

    }
}
      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值