程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java--自定義Class並且在內存中編譯,加載,實例化,java--class

Java--自定義Class並且在內存中編譯,加載,實例化,java--class

編輯:JAVA綜合教程

Java--自定義Class並且在內存中編譯,加載,實例化,java--class


本文的目的:

使用者在程序運行期間,可以動態的寫Java Class,不需要生成任何.Class文件就可以完全在內存中編譯,加載,實例化。

 

1、需要用到的組件介紹

1)JavaCompiler:用於編譯Java Code。

2)CharSequenceJavaFileObject:用於保存Java Code,提供方法給JavaCompiler獲取String形式的Java Code。

3)ClassFileManager:用於JavaCompiler將編譯好後的Class文件保存在指定對象中。

4)JavaClassObject:ClassFileManager告訴JavaCompiler需要將Class文件保存在JavaClassObject中,但是由JavaClassObject來決定最終以byte流來保存數據。

5)DynamicClassLoader:自定義類加載器,用於加載最後的二進制Class

 

2、源碼展現:

CharSequenceJavaFileObject.java

package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.net.URI;

/**
 * 用於將java源碼保存在content屬性中
 */
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {

    /**
     * 保存java code
     */
    private String content;


    /**
     * 調用父類構造器,並設置content
     * @param className
     * @param content
     */
    public CharSequenceJavaFileObject(String className, String content){
        super(URI.create("string:///" + className.replace('.', '/')
                + Kind.SOURCE.extension), Kind.SOURCE);
        this.content = content;
    }

    /**
     * 實現getCharContent,使得JavaCompiler可以從content獲取java源碼
     * @param ignoreEncodingErrors
     * @return
     */
    @Override
    public String getCharContent(boolean ignoreEncodingErrors) {
        return content;
    }
}

 

ClassFileManager.java

package com.ths.platform.framework.dynamic;

import java.io.IOException;

import javax.tools.*;

/**
 * 類文件管理器
 * 用於JavaCompiler將編譯好後的class,保存到jclassObject中
 */
public class ClassFileManager extends ForwardingJavaFileManager {

    /**
     * 保存編譯後Class文件的對象
     */
    private JavaClassObject jclassObject;

    /**
     * 調用父類構造器
     * @param standardManager
     */
    public ClassFileManager(StandardJavaFileManager standardManager) {
        super(standardManager);
    }

    /**
     * 將JavaFileObject對象的引用交給JavaCompiler,讓它將編譯好後的Class文件裝載進來
     * @param location
     * @param className
     * @param kind
     * @param sibling
     * @return
     * @throws IOException
     */
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
            throws IOException {
        if (jclassObject == null)
            jclassObject = new JavaClassObject(className, kind);
        return jclassObject;
    }

    public JavaClassObject getJavaClassObject() {
        return jclassObject;
    }
}

 

JavaClassObject.java

package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
 * 將輸出流交給JavaCompiler,最後JavaCompiler將編譯後的class文件寫入輸出流中
 */
public class JavaClassObject extends SimpleJavaFileObject {

    /**
     * 定義一個輸出流,用於裝載JavaCompiler編譯後的Class文件
     */
    protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    /**
     * 調用父類構造器
     * @param name
     * @param kind
     */
    public JavaClassObject(String name, Kind kind) {
        super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
    }

    /**
     * 獲取輸出流為byte[]數組
     * @return
     */
    public byte[] getBytes() {
        return bos.toByteArray();
    }

    /**
     * 重寫openOutputStream,將我們的輸出流交給JavaCompiler,讓它將編譯好的Class裝載進來
     * @return
     * @throws IOException
     */
    @Override
    public OutputStream openOutputStream() throws IOException {
        return bos;
    }

    /**
     * 重寫finalize方法,在對象被回收時關閉輸出流
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        bos.close();
    }
}

 

DynamicEngine.java(職責:使用JavaCompiler編譯Class,並且使用DynamicClassLoader加載Class)

package com.ths.platform.framework.dynamic;
 
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
 

/**
 * 在Java SE6中最好的方法是使用StandardJavaFileManager類。
 * 這個類可以很好地控制輸入、輸出,並且可以通過DiagnosticListener得到診斷信息,
 * 而DiagnosticCollector類就是listener的實現。
 * 使用StandardJavaFileManager需要兩步。
 * 首先建立一個DiagnosticCollector實例以及通過JavaCompiler的getStandardFileManager()方法得到一個StandardFileManager對象。
 * 最後通過CompilationTask中的call方法編譯源程序。
 */
public class DynamicEngine {
    //單例
    private static DynamicEngine ourInstance = new DynamicEngine();
 
    public static DynamicEngine getInstance() {
        return ourInstance;
    }
    private URLClassLoader parentClassLoader;
    private String classpath;
    private DynamicEngine() {
        //獲取類加載器
        this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        
        //創建classpath
        this.buildClassPath();
    }
    
   
    /**
     * @MethodName    : 創建classpath
     */
    private void buildClassPath() {
        this.classpath = null;
        StringBuilder sb = new StringBuilder();
        for (URL url : this.parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator);
        }
        this.classpath = sb.toString();
    }
    
    /**
     * @MethodName    : 編譯java代碼到Object
     * @Description    : TODO
     * @param fullClassName   類名
     * @param javaCode  類代碼
     * @return Object
     * @throws Exception
     */
    public Object javaCodeToObject(String fullClassName, String javaCode) throws Exception {
        long start = System.currentTimeMillis(); //記錄開始編譯時間
        Object instance = null;
        //獲取系統編譯器
        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");
        options.add("-classpath");
        options.add(this.classpath);
 
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        // 編譯源程序
        boolean success = task.call();
 
        if (success) {
            //如果編譯成功,用類加載器加載該類
            JavaClassObject jco = fileManager.getJavaClassObject();
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
            instance = clazz.newInstance();
        } else {
            //如果想得到具體的編譯錯誤,可以對Diagnostics進行掃描
            String error = "";
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                error += compilePrint(diagnostic);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("javaCodeToObject use:"+(end-start)+"ms");
        return instance;
    }
 
    /**
     * @MethodName    : compilePrint
     * @Description    : 輸出編譯錯誤信息
     * @param diagnostic
     * @return
     */
    private String compilePrint(Diagnostic diagnostic) {
        System.out.println("Code:" + diagnostic.getCode());
        System.out.println("Kind:" + diagnostic.getKind());
        System.out.println("Position:" + diagnostic.getPosition());
        System.out.println("Start Position:" + diagnostic.getStartPosition());
        System.out.println("End Position:" + diagnostic.getEndPosition());
        System.out.println("Source:" + diagnostic.getSource());
        System.out.println("Message:" + diagnostic.getMessage(null));
        System.out.println("LineNumber:" + diagnostic.getLineNumber());
        System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
        StringBuffer res = new StringBuffer();
        res.append("Code:[" + diagnostic.getCode() + "]\n");
        res.append("Kind:[" + diagnostic.getKind() + "]\n");
        res.append("Position:[" + diagnostic.getPosition() + "]\n");
        res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
        res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
        res.append("Source:[" + diagnostic.getSource() + "]\n");
        res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
        res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
        res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
        return res.toString();
    }
}

 

DynamicClassLoader.java

package com.ths.platform.framework.dynamic;

import java.net.URLClassLoader;
import java.net.URL;
 
/**
 * 自定義類加載器
 */
public class DynamicClassLoader extends URLClassLoader {
    public DynamicClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }
 
    public Class findClassByClassName(String className) throws ClassNotFoundException {
        return this.findClass(className);
    }
 
    public Class loadClass(String fullName, JavaClassObject jco) {
        byte[] classData = jco.getBytes();
        return this.defineClass(fullName, classData, 0, classData.length);
    }
}

 

DynaCompTest.java(測試類,從myclass文件中讀出源碼並在內存中編譯)

package com.ths.platform.framework.dynamic;

import sun.misc.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class DynaCompTest
{
    public static void main(String[] args) throws Exception {
        String fullName = "com.seeyon.proxy.MyClass";
        File file = new File("/Users/yangyu/Downloads/myclass");
        InputStream in = new FileInputStream(file);
        byte[] bytes = IOUtils.readFully(in, -1, false);
        String src = new String(bytes);
        in.close();
 
        System.out.println(src);
        DynamicEngine de = DynamicEngine.getInstance();
        Object instance =  de.javaCodeToObject(fullName,src.toString());
        System.out.println(instance);
    }
}

 

/Users/yangyu/Downloads/myclass文件(這裡使用文件,實際也可以在程序中直接拼湊String)

package com.seeyon.proxy;

public class MyClass {

    public String say(String str){
        return "hello"+str;
    }
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved