因为技术都是为业务服务的,所有的功能开发出来都需要有他的用途;

所以先说下需求是啥样子。

业务流程

这个sql查出来的结果需要经过java或者js或者字典或者三者随意组合,输出修改后的结果集。

Java脚本

js脚本

字典就不展示了。。。可以随意实现

结果预览

 这些就是基本的业务流程了,其实也不难。

想要实现这个流程,动态加载js不是问题,很简单,自行百度,5分钟搞定。

Java动态加载就没那么容易了。

首先我想到的就是反射。

大方向是对的,没问题,就用反射来实现,

于是写了一版。

public class ClassUtil {

	private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);
	private static JavaCompiler compiler;
	static{
		compiler = ToolProvider.getSystemJavaCompiler();
	}
	/**
	 * 获取java文件路径
	 * @param file
	 * @return
	 */
	private static String getFilePath(String file){
		int last1 = file.lastIndexOf('/');
		int last2 = file.lastIndexOf('\\');
		return file.substring(0, last1>last2?last1:last2)+File.separatorChar;
	}
	/**
	 * 编译java文件
	 * @param ops 编译参数
	 * @param files 编译文件
	 */
	private static void javac(List<String> ops, String... files){
		StandardJavaFileManager manager = null;
		try{
			manager = compiler.getStandardFileManager(null, null, null);
			Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(files);
			JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, ops, null, it);
			task.call();
			if(logger.isDebugEnabled()){
				for (String file:files){
					logger.debug("Compile Java File:" + file);
				}
			}
		}
		catch(Exception e){
			logger.error(e.getMessage());
		}
		finally{
			if(manager!=null){
				try {
					manager.close();
				}
				catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	/**
	 * 生成java文件
	 * @param file 文件名
	 * @param source java代码
	 * @throws Exception
	 */
	private static void writeJavaFile(String file,String source)throws Exception{
		if(logger.isDebugEnabled()){
			logger.debug("Write Java Source Code to:"+file);
		}
		BufferedWriter bw = null;
		try{
			File dir = new File(getFilePath(file));
			if(!dir.exists()){
				dir.mkdirs();
			}
			bw = new BufferedWriter(new FileWriter(file));
			bw.write(source);
			bw.flush();
		}
		catch(Exception e){
			throw e;
		}
		finally{
			if(bw!=null){
				bw.close();
			}
		}
	}
	/**
	 * 加载类
	 * @param name 类名
	 * @return
	 */
	private static Class<?> load(String name){
		Class<?> cls = null;
		ClassLoader classLoader = null;
		try{
			classLoader = ClassUtil.class.getClassLoader();
			cls = classLoader.loadClass(name);
			if(logger.isDebugEnabled()){
				logger.debug("Load Class["+name+"] by "+classLoader);
			}
		}
		catch(Exception e){
			logger.error(e.getMessage());
		}
		return cls;
	}
	/**
	 * 编译代码并加载类
	 * @param filePath java代码路径
	 * @param source java代码
	 * @param clsName 类名
	 * @param ops 编译参数
	 * @return
	 */
	public static Class<?> loadClass(String filePath,String source,String clsName,List<String> ops){
		try {
			logger.info("filePath:"+filePath);
			writeJavaFile( filePath,source);
			javac(ops,filePath);
			return load(clsName);
		}
		catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}
	/**
	 * 调用类方法
	 * @param cls 类
	 * @param methodName 方法名
	 * @param paramsCls 方法参数类型
	 * @param params 方法参数
	 * @return
	 */
	public static Object invoke(Class<?> cls,String methodName,Class<?>[] paramsCls,Object[] params){
		Object result = null;
		try {
			Method method = cls.getDeclaredMethod(methodName, paramsCls);
			Object obj = cls.newInstance();
			result = method.invoke(obj, params);
		}
		catch (Exception e) {
			logger.error(e.getMessage());
		}
		return result;
	}


	/**
	 * 执行JS函数,参数和返回值都是String类型
	 * @param javaStr 脚本
	 * @param className 类名
	 * @param methodName 方法名
	 * @param parameter 参数
	 * @return
	 */
	public Object convertByJava(String javaStr, String className,String methodName, Object parameter) {
		StringBuilder sb = new StringBuilder();
		sb.append(javaStr);
		//设置编译参数
		ArrayList<String> ops = new ArrayList<String>();
		ops.add("-Xlint:unchecked");
		//编译代码,返回class
		Class<?> cls = ClassUtil.loadClass(System.getProperty("user.dir")+"\\target\\classes\\"+className+".java", sb.toString(), className, ops);
		//执行方法
		Object result = ClassUtil.invoke(cls, methodName, new Class[]{List.class}, new Object[]{parameter});
		//输出结果
		logger.info("parameter:" + JSONObject.toJSONString(parameter));
		logger.info("result:" + result);
		return result;
	}

}

大致的流程是:先生成.java文件---再编译为.class文件---然后loadClass---最后invoke

本地跑main方法,这种写法是没问题的,但是如果要是用idea启动springboot项目,这样就会出现问题。参考这个解决。

警告: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassD_Hello_xzy_Word的博客-CSDN博客说明主要参考了这位大佬的文章:https://www.cnblogs.com/xxjcai/p/java_compiler.html不过我是在使用IDEA的时候遇到的问题,所以我这里介绍一下在IDEA中的解决方案。错误信息.\src\main\java\com\xiao\designpattern\dynamicproxy\TimeProxy.java:6: 警告: Can't i...https://blog.csdn.net/qq_39514033/article/details/103999277

这样就解决了。但是新的问题来了,这时如果对java文件进行修改,会发现修改无效,不管怎么改,都不会重新加载重新编译的.class文件。

        借此机会,研究了一下java类的加载机制,发现上面方法写的是双亲委派方式,每次加载.class文件的时候,发现字节码已经在jvm内存里存在了,上面那种写法是不行的。

        所以如果不想违背双亲委派,就使用classLoader的loadclass方法,如果想违背双亲委派,就自定义classLoader重写findclass方法,改造后的方法如下:

 自定义classLoader extends ClassLoader 重写findClass方法

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

public class MyClassLoader extends ClassLoader {
	private String rootDir;

	public MyClassLoader(String rootDir) {
		this.rootDir = rootDir;
	}

	@Override
	protected Class<?> findClass(String className){
		Class clazz = this.findLoadedClass(className);
		FileChannel fileChannel = null;
		WritableByteChannel outChannel = null;
		if (null == clazz) {
			try {
				String classFile = getClassFile(className);
				FileInputStream fis = new FileInputStream(classFile);
				fileChannel = fis.getChannel();
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				outChannel = Channels.newChannel(baos);
				ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
				while (true) {
					int i = fileChannel.read(buffer);
					if (i == 0 || i == -1) {
						break;
					}
					buffer.flip();
					outChannel.write(buffer);
					buffer.clear();
				}

				byte[] bytes = baos.toByteArray();
				clazz = defineClass(className, bytes, 0, bytes.length);


			} catch (Exception e) {
				// ...
			}finally {
				// ...
			}
		}
		return clazz;
	}

	/**
	 * 类文件的完全路径
	 */
	private String getClassFile(String className) {
		return rootDir + "\\" + className.replace('.', '\\') + ".class";
	}
}

这样完美解决本地idea启动springboot项目,热加载.java文件。

然后信心满满的将项目打成jar包,发布到测试环境进行测试。新的问题出现了!!!!!

jar包方式运行后,根本无法识别  import com.alibaba.fastjson.JSONObject; 这种引用。

为什呢,因为这个.java文件没有办法用到springboot打承德jar包中的任何依赖,就是单纯的java文件,只能用jdk的基本引用比如java.util什么的。

现在就得研究如何让动态编译的java文件能引用到springboot包中的依赖。

于是我就研究了一下springboot jarinjar。

在IDE中运行springboot 程序 类加载器是JDK自带的,类加载器已经完成了依赖jar的加载,所以编译是没问题的,但是springboot 用的是springboot自己写的LaunchedURLClassLoader。经过查阅资料后,从JavaFileManager入手,因为Java编译器是通过JavaFileManager来加载相关依赖类的。 重写JavaFileManager,使用到了springboot的 jarFile 来读取嵌套jar。

-------------------------------------------------------正确写法分割线------------------------------------------------

下面的才是正确写法!!!!!!!!!!!!!!

springboot的 jarFile 来读取嵌套jar

import com.sun.tools.javac.file.BaseFileObject;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import org.springframework.boot.loader.jar.JarFile;
import org.springframework.boot.system.ApplicationHome;

import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.io.*;
import java.lang.reflect.Constructor;
import java.net.*;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.stream.Collectors;

/**
 * 把一段Java字符串变成类
 *
 */
public class MemoryClassLoader extends URLClassLoader {


	private Map<String, byte[]> classBytes = new ConcurrentHashMap<>();


	/**
	 * 单利默认的
	 */
	private static final MemoryClassLoader defaultLoader = new MemoryClassLoader();

	public MemoryClassLoader() {
		super(new URL[0], MemoryClassLoader.class.getClassLoader());
	}

	/**
	 * 获取默认的类加载器
	 *
	 * @return 类加载器对象
	 */
	public static MemoryClassLoader getInstrance() {
		return defaultLoader;
	}

	/**
	 * 注册Java 字符串到内存类加载器中
	 *
	 * @param className 类名字
	 * @param javaStr   Java字符串
	 */
	public void registerJava(String className, String javaStr) {
		try {
			this.classBytes.putAll(compile(className, javaStr));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * 自定义Java文件管理器
	 *
	 * @param var1
	 * @param var2
	 * @param var3
	 * @return
	 */
	public static SpringJavaFileManager getStandardFileManager(DiagnosticListener<? super JavaFileObject> var1, Locale var2, Charset var3) {
		Context var4 = new Context();
		var4.put(Locale.class, var2);
		if (var1 != null) {
			var4.put(DiagnosticListener.class, var1);
		}

		PrintWriter var5 = var3 == null ? new PrintWriter(System.err, true) : new PrintWriter(new OutputStreamWriter(System.err, var3), true);
		var4.put(Log.outKey, var5);
		return new SpringJavaFileManager(var4, true, var3);
	}

	/**
	 * 编译Java代码
	 *
	 * @param className 类名字
	 * @param javaStr   Java代码
	 * @return class 二进制
	 */
	private static Map<String, byte[]> compile(String className, String javaStr) {
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager stdManager = getStandardFileManager(null, null, null);
		try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
			JavaFileObject javaFileObject = manager.makeStringSource(className, javaStr);
			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;
	}


	@Override
	public 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);
	}

	/**
	 * 开放findClass 给外部使用
	 *
	 * @param name classname
	 * @return class对象
	 */
	public Class<?> getClass(String name) throws ClassNotFoundException {
		return this.findClass(name);
	}

	/**
	 * 获取jar包所在路径
	 *
	 * @return jar包所在路径
	 */
	public static String getPath() {
		ApplicationHome home = new ApplicationHome(MemoryJavaFileManager.class);
		String path = home.getSource().getPath();
		return path;
	}

	/**
	 * 判断是否jar模式运行
	 *
	 * @return
	 */
	public static boolean isJar() {
		return getPath().endsWith(".jar");
	}

}


/**
 * 内存Java文件管理器
 * 用于加载springboot boot info lib 下面的依赖资源
 */
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {

	// compiled classes in bytes:
	final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

	final Map<String, List<JavaFileObject>> classObjectPackageMap = new HashMap<>();

	private JavacFileManager javaFileManager;

	/**
	 * key 包名 value javaobj 主要给jdk编译class的时候找依赖class用
	 */
	public final static Map<String, List<JavaFileObject>> CLASS_OBJECT_PACKAGE_MAP = new HashMap<>();

	private static final Object lock = new Object();

	private static boolean isInit = false;


	public void init() {
		try {
			String jarBaseFile = MemoryClassLoader.getPath();
			JarFile jarFile = new JarFile(new File(jarBaseFile));
			List<JarEntry> entries = jarFile.stream().filter(jarEntry -> {
				return jarEntry.getName().endsWith(".jar");
			}).collect(Collectors.toList());
			JarFile libTempJarFile = null;
			List<JavaFileObject> onePackgeJavaFiles = null;
			String packgeName = null;
			for (JarEntry entry : entries) {
				libTempJarFile = jarFile.getNestedJarFile(jarFile.getEntry(entry.getName()));
				if (libTempJarFile.getName().contains("tools.jar")) {
					continue;
				}
				Enumeration<JarEntry> tempEntriesEnum = libTempJarFile.entries();
				while (tempEntriesEnum.hasMoreElements()) {
					JarEntry jarEntry = tempEntriesEnum.nextElement();
					String classPath = jarEntry.getName().replace("/", ".");
					if (!classPath.endsWith(".class") || jarEntry.getName().lastIndexOf("/") == -1) {
						continue;
					} else {
						packgeName = classPath.substring(0, jarEntry.getName().lastIndexOf("/"));
						onePackgeJavaFiles = CLASS_OBJECT_PACKAGE_MAP.containsKey(packgeName) ? CLASS_OBJECT_PACKAGE_MAP.get(packgeName) : new ArrayList<>();
						onePackgeJavaFiles.add(new MemorySpringBootInfoJavaClassObject(jarEntry.getName().replace("/", ".").replace(".class", ""),
								new URL(libTempJarFile.getUrl(), jarEntry.getName()), javaFileManager));
						CLASS_OBJECT_PACKAGE_MAP.put(packgeName, onePackgeJavaFiles);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		isInit = true;

	}


	MemoryJavaFileManager(JavaFileManager fileManager) {
		super(fileManager);
		this.javaFileManager = (JavacFileManager) fileManager;
	}

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

	@Override
	public void flush() throws IOException {
	}

	@Override
	public void close() throws IOException {
		classBytes.clear();
	}


	public List<JavaFileObject> getLibJarsOptions(String packgeName) {
		synchronized (lock) {
			if (!isInit) {
				init();
			}
		}
		return CLASS_OBJECT_PACKAGE_MAP.get(packgeName);
	}

	@Override
	public Iterable<JavaFileObject> list(Location location,
	                                     String packageName,
	                                     Set<Kind> kinds,
	                                     boolean recurse)
			throws IOException {


		if ("CLASS_PATH".equals(location.getName()) && MemoryClassLoader.isJar()) {
			List<JavaFileObject> result = getLibJarsOptions(packageName);
			if (result != null) {
				return result;
			}
		}

		Iterable<JavaFileObject> it = super.list(location, packageName, kinds, recurse);

		if (kinds.contains(Kind.CLASS)) {
			final List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
			if (javaFileObjectList != null) {
				if (it != null) {
					for (JavaFileObject javaFileObject : it) {
						javaFileObjectList.add(javaFileObject);
					}
				}
				return javaFileObjectList;
			} else {
				return it;
			}
		} else {
			return it;
		}
	}

	@Override
	public String inferBinaryName(Location location, JavaFileObject file) {
		if (file instanceof MemoryInputJavaClassObject) {
			return ((MemoryInputJavaClassObject) file).inferBinaryName();
		}
		return super.inferBinaryName(location, file);
	}

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

	JavaFileObject makeStringSource(String className, final String code) {
		String classPath = className.replace('.', '/') + Kind.SOURCE.extension;

		return new SimpleJavaFileObject(URI.create("string:///" + classPath), Kind.SOURCE) {
			@Override
			public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
				return CharBuffer.wrap(code);
			}
		};
	}

	void makeBinaryClass(String className, final byte[] bs) {
		JavaFileObject javaFileObject = new MemoryInputJavaClassObject(className, bs);

		String packageName = "";
		int pos = className.lastIndexOf('.');
		if (pos > 0) {
			packageName = className.substring(0, pos);
		}
		List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
		if (javaFileObjectList == null) {
			javaFileObjectList = new LinkedList<>();
			javaFileObjectList.add(javaFileObject);

			classObjectPackageMap.put(packageName, javaFileObjectList);
		} else {
			javaFileObjectList.add(javaFileObject);
		}
	}

	class MemoryInputJavaClassObject extends SimpleJavaFileObject {
		final String className;
		final byte[] bs;

		MemoryInputJavaClassObject(String className, byte[] bs) {
			super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
			this.className = className;
			this.bs = bs;
		}

		@Override
		public InputStream openInputStream() {
			return new ByteArrayInputStream(bs);
		}

		public String inferBinaryName() {
			return className;
		}
	}


	class MemoryOutputJavaClassObject extends SimpleJavaFileObject {
		final String className;

		MemoryOutputJavaClassObject(String className) {
			super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
			this.className = className;
		}

		@Override
		public OutputStream openOutputStream() {
			return new FilterOutputStream(new ByteArrayOutputStream()) {
				@Override
				public void close() throws IOException {
					out.close();
					ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
					byte[] bs = bos.toByteArray();
					classBytes.put(className, bs);
					makeBinaryClass(className, bs);
				}
			};
		}
	}
}

/**
 * 用来读取springboot的class
 */
class MemorySpringBootInfoJavaClassObject extends BaseFileObject {
	private final String className;
	private URL url;

	MemorySpringBootInfoJavaClassObject(String className, URL url, JavacFileManager javacFileManager) {
		super(javacFileManager);
		this.className = className;
		this.url = url;
	}

	@Override
	public Kind getKind() {
		return JavaFileObject.Kind.valueOf("CLASS");
	}

	@Override
	public URI toUri() {
		try {
			return url.toURI();
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public String getName() {
		return className;
	}

	@Override
	public InputStream openInputStream() {
		try {
			return url.openStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

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

	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
		return null;
	}

	@Override
	public Writer openWriter() throws IOException {
		return null;
	}

	@Override
	public long getLastModified() {
		return 0;
	}

	@Override
	public boolean delete() {
		return false;
	}

	public String inferBinaryName() {
		return className;
	}

	@Override
	public String getShortName() {
		return className.substring(className.lastIndexOf("."));
	}

	@Override
	protected String inferBinaryName(Iterable<? extends File> iterable) {
		return className;
	}


	@Override
	public boolean equals(Object o) {
		return false;
	}

	@Override
	public int hashCode() {
		return 0;
	}


	@Override
	public boolean isNameCompatible(String simpleName, Kind kind) {
		return false;
	}
}

/**
 * java 文件管理器 主要用来 重新定义class loader
 */
class SpringJavaFileManager extends JavacFileManager {


	public SpringJavaFileManager(Context context, boolean b, Charset charset) {
		super(context, b, charset);
	}


	@Override
	public ClassLoader getClassLoader(Location location) {
		nullCheck(location);
		Iterable var2 = this.getLocation(location);
		if (var2 == null) {
			return null;
		} else {
			ListBuffer var3 = new ListBuffer();
			Iterator var4 = var2.iterator();

			while (var4.hasNext()) {
				File var5 = (File) var4.next();

				try {
					var3.append(var5.toURI().toURL());
				} catch (MalformedURLException var7) {
					throw new AssertionError(var7);
				}
			}
			return this.getClassLoader((URL[]) var3.toArray(new URL[var3.size()]));
		}
	}

	@Override
	protected ClassLoader getClassLoader(URL[] var1) {
		ClassLoader var2 = this.getClass().getClassLoader();
		try {
			Class loaderClass = Class.forName("org.springframework.boot.loader.LaunchedURLClassLoader");
			Class[] var4 = new Class[]{URL[].class, ClassLoader.class};
			Constructor var5 = loaderClass.getConstructor(var4);
			return (ClassLoader) var5.newInstance(var1, var2);
		} catch (Throwable var6) {
		}
		return new URLClassLoader(var1, var2);
	}


}

搞定。 

最后说一点,服务器上跑jar包的时候需要加上 -Xbootclasspath/a:$toolspath/tools.jar  这个启动参数来支持,因为Java编译器是通过JavaFileManager来加载相关依赖类的,而JavaFileManager来自tools.jar,所以不加会无法编译哦。

示例:nohup java -jar -Xbootclasspath/a:$toolspath/tools.jar:$toolspath/rt.jar:$toolspath/jce.jar XXXX.jar

资源传送门:https://download.csdn.net/download/YouShouRenSheng/85649938icon-default.png?t=M4ADhttps://download.csdn.net/download/YouShouRenSheng/85649938

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐