动态编译和动态运行代码

代码来源于https://github.com/hxulin/dynamic-compile-samples.git

引入编译包

        <dependency>
            <groupId>com.itranswarp</groupId>
            <artifactId>compiler</artifactId>
            <version>1.0</version>
        </dependency>

添加被调用的类

package com.example.demo.dynamic;

public class IndexService {

    public void query(){
        System.out.println("query");
    }
}

添加测试类

package com.example.demo.dynamic;

import com.itranswarp.compiler.JavaStringCompiler;

import java.lang.reflect.Method;
import java.util.Map;

public class DynamicTest {


    public static final String code = "package com.example.demo.dynamic;\n" +
            "\n" +
            "public class UserService {\n" +
            "\n" +
            "    private IndexService service;\n" +
            "\n" +
            "    public void user(){\n" +
            "        service.query();\n" +
            "    }\n" +
            "\n" +
            "    public void setService(IndexService service){\n" +
            "        this.service = service;\n" +
            "    }\n" +
            "\n" +
            "}";

    public static void main(String[] args) throws Exception {
        JavaStringCompiler compiler = new JavaStringCompiler();
        Map<String, byte[]> compile = compiler.compile("UserService.java", code);
        Class<?> aClass = compiler.loadClass("com.example.demo.dynamic.UserService", compile);

        Method setService = aClass.getMethod("setService", IndexService.class);
        Object o = aClass.newInstance();
        setService.invoke(o,new IndexService());
        Method user = aClass.getMethod("user");
        user.invoke(o);


    }
}

使用jdk自带的比较复杂,所以使用已有的编译包

 

补充,在后续的测试中,将测试代码添加到一个SpringBoot项目中,在idea中通过main方法启动项目,暴露接口传入java代码,可以编译,但是将springboot打包成jar启动后,传入java代码,编译失败,找不到符号

目前有一种方法,已经测试成功可以运行,仅供测试用哈

1、创建一个springboot项目,添加上面的编译依赖,

项目主要用到的类

 

 2、dy包中的类

package com.example.demo.dy;

public class MyClassLoad extends ClassLoader {
    
     @Override
     protected Class<?> findClass(String name) throws ClassNotFoundException {
         MyJavaFileObject javaFileObject = MyJavaFileManager.fileObjects.get(name);
         if(javaFileObject != null){
             byte[] compileByte = javaFileObject.getCompileByte();
             return defineClass(name,compileByte,0,compileByte.length);
         }
         try {
             return ClassLoader.getSystemClassLoader().loadClass(name);
         }catch (Exception e){
             return super.findClass(name);
         }
     }

}
package com.example.demo.dy;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

public class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    
     public static Map<String, MyJavaFileObject> fileObjects = new ConcurrentHashMap<>();
    
    public MyJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
    }

    @Override
    public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
        JavaFileObject javaFileObject = fileObjects.get(className);
        if (javaFileObject == null){
            super.getJavaFileForInput(location,className,kind);
        }
        return javaFileObject;
    }

    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
        MyJavaFileObject javaFileObject = new MyJavaFileObject(className,kind);
        fileObjects.put(className,javaFileObject);
        return javaFileObject;
    }
}
package com.example.demo.dy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;

import javax.tools.SimpleJavaFileObject;

public class MyJavaFileObject extends SimpleJavaFileObject {
    
     private String source;
     private ByteArrayOutputStream outputStream;

     public MyJavaFileObject(String name,String source) {
         super(URI.create("String:///"+name),Kind.SOURCE);
         this.source = source;
     }

     public MyJavaFileObject(String name,Kind kind){
         super(URI.create("String:///"+name),kind);
         source = null;
     }

     @Override
     public OutputStream openOutputStream() throws IOException {
         outputStream = new ByteArrayOutputStream();
         return outputStream;
     }

     @Override
     public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
         if(source == null){
             throw new IllegalArgumentException("source == null");
         }
         return source;
     }

     public byte[] getCompileByte(){
         return outputStream.toByteArray();
     }
}    

3、controller包中的类(SendController没用)

package com.example.demo.controller;

public interface UserService {
    
    public String user();

}
package com.example.demo.controller;

import org.springframework.stereotype.Service;

@Service
public class IndexService {

    public String index() {
        return "indexService";
    }
}
package com.example.demo.controller;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.dy.MyClassLoad;
import com.example.demo.dy.MyJavaFileManager;
import com.example.demo.dy.MyJavaFileObject;

import com.itranswarp.compiler.JavaStringCompiler;

@RestController
public class IndexController {
    
    @Autowired
    private IndexService indexService;
    

    @GetMapping("index")
    public String index() {
        return "Success";
    }
    
    @PostMapping("compile")
    public String compile(@RequestBody String message) {
        JavaStringCompiler compiler = new JavaStringCompiler();
        try {
            Map<String, byte[]> compile = compiler.compile("UserServiceImpl.java", message,null);
            Class<?> loadClass = compiler.loadClass("com.example.demo.service.UserServiceImpl",compile);
            Object userService = loadClass.newInstance();
            Method method = loadClass.getMethod("setIndexService", IndexService.class);
            
            method.invoke(userService, indexService);
            
            if(userService instanceof UserService) {
                UserService service = (UserService)userService;
                return service.user();
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            
        }
        
    }
    
    @PostMapping("compile1")
    public String compile1(@RequestBody String message) throws IOException {
        JavaStringCompiler compiler = new JavaStringCompiler();
        List<String> options = new ArrayList<>();
        options.add("-classpath");
        options.add("./BOOT-INF/classes/");
        try {
            Map<String, byte[]> compile = compiler.compile("UserServiceImpl.java", message,options);
            Class<?> loadClass = compiler.loadClass("com.example.demo.controller.UserServiceImpl",compile);
            Object userService = loadClass.newInstance();
            Method method = loadClass.getMethod("setIndexService", IndexService.class);

            method.invoke(userService, indexService);

            if(userService instanceof UserService) {
                UserService service = (UserService)userService;
                return service.user();
            }
            return null;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {

        }

    }


    @PostMapping("compile3")
    public String compile3(@RequestBody String message) {
        JavaStringCompiler compiler = new JavaStringCompiler();
        List<String> options = new ArrayList<>();
        String classpath = this.getClass().getClassLoader().getResource("").getPath();
        options.add("-cp");
        options.add(classpath);
        try {
            Map<String, byte[]> compile = compiler.compile("HelloWorld.java", message,options);
            Class<?> loadClass = compiler.loadClass("HelloWorld",compile);

            Method print = loadClass.getMethod("print");
            print.invoke(loadClass.newInstance());

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {

        }
        return null;

    }
    
    @PostMapping("compile2")
    public String compile2(@RequestBody String message) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
        JavaFileManager fileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector,null,null));
        String current = this.getClass().getResource("").getPath();
        String classpath = this.getClass().getClassLoader().getResource("").getPath();
        List<String> options = new ArrayList<>();
//        options.add("-target");
//        options.add("1.8");
        System.out.println(classpath);
        System.out.println(current);
        options.add("-d");
        options.add(".");
        options.add("-cp");
        options.add(classpath);
        JavaFileObject javaFileObject = new MyJavaFileObject("UserServiceImpl.java",message);
        Boolean call = compiler.getTask(null, fileManager, collector, options, null, Arrays.asList(javaFileObject)).call();
        if(!call) {
            return "fail";
        }
        ClassLoader classLoader = new MyClassLoad();
        Class<?> loadClass = null;
        try {
             loadClass = classLoader.loadClass("com.example.demo.service.UserServiceImpl");
             Object userService = loadClass.newInstance();
             Method method = loadClass.getMethod("setIndexService", IndexService.class);
             method.invoke(userService, indexService);
             if(userService instanceof UserService) {
                    UserService service = (UserService)userService;
                    return service.user();
             }
             
        } catch (Exception e) {
            e.printStackTrace();
        }
   
        
        return null;
    }
}

4、之后将项目打包,放到一个目录中,然后解压到当前目录下

为啥要解压呢,主要是为了方便5中指定classpath(主要解决编译找不到符号的问题)

目录结构

 

 5、测试接口--主要测试compile1,这个接口中在编译的时候添加了-classpath的参数,这样的话,动态编译的时候能够找到符号引用

使用postman测试

package com.example.demo.controller;

import com.example.demo.controller.UserService;
import com.example.demo.controller.IndexService;

public class UserServiceImpl implements UserService {
    
    private IndexService indexService;

    public void setIndexService(IndexService indexService) {
        this.indexService = indexService;
    }
    
    @Override
    public String user() {
        return indexService.index();
    }

}

 

 完,目前还不知道有啥问题,待后续补充吧

6、还有一个

 

 就是上面的依赖的编译包,编译方法添加了个参数。

补充:

对compile2接口做调整

 

 

 

 添加了一个父类类加载器,不然在加载类的时候,类中关于其他类的引用加载不到

补充:查看类能否被卸载

 

 添加gc方法调用

虚拟机参数添加:

-XX:+TraceClassUnloading

然后查看日志打印,应该可有看到类的卸载信息

 补充:

为了方便jar包部署,添加了一个在jar包启动完成后,进行jar包解压的操作

 

 

 

 

posted @ 2019-12-18 22:08  默默行走  阅读(2839)  评论(2编辑  收藏  举报