先放出代码,再说有几个坑的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285package com.yrxd.telemarketing.service; import org.apache.log4j.Logger; import org.springframework.boot.loader.LaunchedURLClassLoader; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.tools.*; import java.io.*; import java.net.*; import java.nio.CharBuffer; import java.util.*; import java.util.jar.JarEntry; /** */ public class CusCompiler { static Logger logger = Logger.getLogger(CusCompiler.class); static class CustomJavaFileObject implements JavaFileObject { private String binaryName; private URI uri; private String name; public String binaryName() { return binaryName; } public CustomJavaFileObject(String binaryName, URI uri) { this.uri = uri; this.binaryName = binaryName; name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath(); } @Override public Kind getKind() { return Kind.CLASS; } @Override public boolean isNameCompatible(String simpleName, Kind kind) { String baseName = simpleName + kind.extension; return kind.equals(getKind()) && (baseName.equals(getName()) || getName().endsWith("/" + baseName)); } @Override public NestingKind getNestingKind() { throw new UnsupportedOperationException(); } @Override public Modifier getAccessLevel() { throw new UnsupportedOperationException(); } @Override public URI toUri() { return uri; } @Override public String getName() { return name; } @Override public InputStream openInputStream() throws IOException { return uri.toURL().openStream(); } @Override public OutputStream openOutputStream() throws IOException { throw new UnsupportedOperationException(); } @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException(); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException(); } @Override public Writer openWriter() throws IOException { throw new UnsupportedOperationException(); } @Override public long getLastModified() { return 0; } @Override public boolean delete() { throw new UnsupportedOperationException(); } } static class MemoryInputJavaFileObject extends SimpleJavaFileObject { final String code; MemoryInputJavaFileObject(String name, String code) { super(URI.create(name.replaceAll("\.", "/") + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } @Override public CharBuffer getCharContent(boolean ignoreEncodingErrors) { return CharBuffer.wrap(code); } } static class MemoryOutputJavaFileObject extends SimpleJavaFileObject { final String name; Map<String, byte[]> class_out; MemoryOutputJavaFileObject(String name, Map<String, byte[]> out) { super(URI.create(name.replaceAll("\.", "/") + Kind.SOURCE.extension), Kind.CLASS); this.name = name; this.class_out = out; } @Override public OutputStream openOutputStream() { return new FilterOutputStream(new ByteArrayOutputStream()) { @Override public void close() throws IOException { out.close(); ByteArrayOutputStream bos = (ByteArrayOutputStream) out; class_out.put(name, bos.toByteArray()); } }; } } static class SpringBootJarFileManager implements JavaFileManager { private URLClassLoader classLoader; private StandardJavaFileManager standardJavaFileManager; final Map<String, byte[]> classBytes = new HashMap<>(); SpringBootJarFileManager(StandardJavaFileManager standardJavaFileManager, URLClassLoader systemLoader) { this.classLoader = new URLClassLoader(systemLoader.getURLs(), systemLoader); this.standardJavaFileManager = standardJavaFileManager; } @Override public ClassLoader getClassLoader(Location location) { return classLoader; } private List<JavaFileObject> find(String packageName) { List<JavaFileObject> result = new ArrayList<>(); String javaPackageName = packageName.replaceAll("\.", "/"); try { Enumeration<URL> urls = classLoader.findResources(javaPackageName); while (urls.hasMoreElements()) { URL ll = urls.nextElement(); String ext_form = ll.toExternalForm(); String jar = ext_form.substring(0, ext_form.lastIndexOf("!")); String pkg = ext_form.substring(ext_form.lastIndexOf("!") + 1); JarURLConnection conn = (JarURLConnection) ll.openConnection(); conn.connect(); Enumeration<JarEntry> jar_items = conn.getJarFile().entries(); while (jar_items.hasMoreElements()) { JarEntry item = jar_items.nextElement(); if (item.isDirectory() || (!item.getName().endsWith(".class"))) { continue; } if (item.getName().lastIndexOf("/") != (pkg.length() - 1)) { continue; } String name = item.getName(); URI uri = URI.create(jar + "!/" + name); String binaryName = name.replaceAll("/", "."); binaryName = binaryName.substring(0, binaryName.indexOf(JavaFileObject.Kind.CLASS.extension)); result.add(new CustomJavaFileObject(binaryName, uri)); } } } catch (Exception e) { e.printStackTrace(); } return result; } @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException { Iterable<JavaFileObject> ret = null; if (location == StandardLocation.PLATFORM_CLASS_PATH) { ret = standardJavaFileManager.list(location, packageName, kinds, recurse); } else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) { ret = find(packageName); if (ret == null || (!ret.iterator().hasNext())) { ret = standardJavaFileManager.list(location, packageName, kinds, recurse); } } else { ret = Collections.emptyList(); } return ret; } @Override public String inferBinaryName(Location location, JavaFileObject file) { String ret = ""; if (file instanceof CustomJavaFileObject) { ret = ((CustomJavaFileObject)file).binaryName; } else { ret = standardJavaFileManager.inferBinaryName(location, file); } return ret; } @Override public boolean isSameFile(FileObject a, FileObject b) { throw new UnsupportedOperationException(); } @Override public boolean handleOption(String current, Iterator<String> remaining) { return standardJavaFileManager.handleOption(current, remaining); } @Override public boolean hasLocation(Location location) { return location == StandardLocation.CLASS_PATH || location == StandardLocation.PLATFORM_CLASS_PATH; } @Override public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { throw new UnsupportedOperationException(); } @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { throw new UnsupportedOperationException(); } @Override public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { throw new UnsupportedOperationException(); } @Override public void flush() throws IOException { } @Override public void close() throws IOException { classBytes.clear(); } @Override public int isSupportedOption(String option) { return -1; } public Map<String, byte[]> getClassBytes() { return new HashMap<String, byte[]>(this.classBytes); } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { if (kind == JavaFileObject.Kind.CLASS) { return new MemoryOutputJavaFileObject(className, classBytes); } else { return standardJavaFileManager.getJavaFileForOutput(location, className, kind, sibling); } } } private static class MemoryClassLoader extends LaunchedURLClassLoader { Map<String, byte[]> classBytes = new HashMap<>(); public MemoryClassLoader(Map<String, byte[]> classBytes, ClassLoader classLoader) { super(new URL[0], classLoader); this.classBytes.putAll(classBytes); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("findClass: " + name); byte[] buf = classBytes.get(name); if (buf == null) { return super.findClass(name); } classBytes.remove(name); return defineClass(name, buf, 0, buf.length); } } public Class<?> loadClass(String name, Map<String, byte[]> classBytes) throws Exception { ClassLoader loader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> r = null; if (classBytes.containsKey(name)) { byte[] buf = classBytes.get(name); r = defineClass(name, buf, 0, buf.length); } else { r = systemClassLoader.loadClass(name); } return r; } }; return loader.loadClass(name); } private URLClassLoader systemClassLoader; public CusCompiler(URLClassLoader loader) { systemClassLoader = loader; } public Map<String, byte[]> compile(String className, String code) throws Exception { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null); SpringBootJarFileManager springBootJarFileManager = new SpringBootJarFileManager(stdManager, systemClassLoader); JavaFileObject javaFileObject = new MemoryInputJavaFileObject(className, code); List<String> options = new ArrayList<>(); //options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"), "-bootclasspath", System.getProperty("sun.boot.class.path"), "-extdirs", System.getProperty("java.ext.dirs"))); JavaCompiler.CompilationTask task = compiler.getTask(null, springBootJarFileManager, null, null, null, Arrays.asList(javaFileObject)); Boolean compileRet= task.call(); if (compileRet == null || (!compileRet.booleanValue())) { throw new RuntimeException("java filter compile error"); } for (String key : springBootJarFileManager.getClassBytes().keySet()) { logger.info("class: " + key + " len: " + Integer.valueOf(springBootJarFileManager.getClassBytes().get(key).length).toString()); } return springBootJarFileManager.getClassBytes(); } }
调用的时候 sample 如下:
1
2
3
4
5class_name = "com.yrxd.telemarketing.script." + class_name; //CusCompiler compiler = new CusCompiler((URLClassLoader)getClass().getClassLoader()); CusCompiler compiler = new CusCompiler((URLClassLoader)Thread.currentThread().getContextClassLoader()); Map<String, byte[]> results = compiler.compile(class_name, script); Class<?> clazz = compiler.loadClass(class_name, results);
踩坑的地方如下:
0. 启动 springboot 的时候,加上 -Xbootclasspath/a:/usr/local/java/lib/tools.jar
1. 由于 springboot 特殊的机制,它的启动 launcher 是可以从 fat jar 里面直接读取资源的,所以要充分利用这一点,编译的时候,利用 springboot 的loader 来查找资源,大胆的假设这个 loader 的类型为 URLClassLoader
2. 尽量不要直接用springboot 启动的 loader, loader 里面的资源读过一次之后,后面读不出来,这样导致的后果是: 在 springboot 的 main 函数里面,直接动态加载会有问题,需要新建一个loader,继承springboot 的loader
3. 注意适用这种方式后,系统会有多个 loader 存在,系统的的loader 是无法loader 这里动态加载的class, 由此可能引发一系列的异常,需要 case by case分析
4. 同一个classloader,对于相同的 classname, 不能 defineclass 调用多次,否则会出异常
5. 参考:
[http://atamur.blogspot.com/2009/10/using-built-in-javacompiler-with-custom.html](http://atamur.blogspot.com/2009/10/using-built-in-javacompiler-with-custom.html) 实现自定义的 filemanager
[https://github.com/michaelliao/compiler](https://github.com/michaelliao/compiler) 最开始找到的实现,不支持 springboot,通过把 springboot 解压后,也勉强可以用,但是在多线程的情况下,使用 mybatisplus 的lambdaQuery 会有问题
最后
以上就是酷酷楼房最近收集整理的关于springboot 2 下 java 代码 动态编译 动态加载 实现的全部内容,更多相关springboot内容请搜索靠谱客的其他文章。
发表评论 取消回复