, IUtil> {
11 |
12 | /**
13 | * 扫描包进行注册
14 | */
15 | public UtilRegistry() {
16 | super(IUtil.class.getPackage().getName());
17 | instance = this;
18 | }
19 |
20 | private static UtilRegistry instance;
21 |
22 | public static UtilRegistry getInstance() {
23 | return instance;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/demo/util/DemoUtil.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.demo.util;
2 |
3 | import com.cm4j.demo.UtilRegistry;
4 |
5 | /**
6 | * @author yeas.fun
7 | * @since 2021/11/4
8 | */
9 | public class DemoUtil implements IUtil {
10 |
11 | public void hello() {
12 | System.out.println("hello world !");
13 | }
14 |
15 | public static DemoUtil getInstance() {
16 | return (DemoUtil) UtilRegistry.getInstance().get(DemoUtil.class);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/demo/util/IUtil.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.demo.util;
2 |
3 | import com.cm4j.registry.registered.IRegistered;
4 |
5 | /**
6 | * @author yeas.fun
7 | * @since 2021/1/8
8 | */
9 | public interface IUtil extends IRegistered {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/eval/JavaEvalUtil.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.eval;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.lang.reflect.Method;
6 | import java.lang.reflect.Modifier;
7 | import java.util.Map;
8 |
9 | import org.apache.commons.lang3.StringUtils;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import com.alibaba.fastjson.JSON;
14 | import com.cm4j.eval.compiler.DynamicCompiler;
15 | import com.google.common.base.Charsets;
16 | import com.google.common.io.Files;
17 |
18 | /**
19 | * Java动态代码执行
20 | *
21 | * 使用方法:
22 | * 直接写一个类,里面必须要有一个 public static 方法,就可以调用该方法
23 | */
24 | public class JavaEvalUtil {
25 |
26 | private static final Logger LOG = LoggerFactory.getLogger(JavaEvalUtil.class);
27 |
28 | private static void dump(String javaSource) throws IOException {
29 | String className = getClassName(javaSource);
30 |
31 | String basePath = new File("").getAbsolutePath();
32 | String finalPath = StringUtils.join(new String[]{basePath, "eval-output", className + ".java"}, File.separator);
33 |
34 | // 创建上级目录
35 | File file = new File(finalPath);
36 | Files.createParentDirs(file);
37 |
38 | LOG.error("java eval output:{}", file.getAbsolutePath());
39 | LOG.error("\n{}", javaSource);
40 | Files.write(javaSource.getBytes(Charsets.UTF_8), file);
41 | }
42 |
43 | /**
44 | * 编译并生成class
45 | *
46 | * @param sourceCode 源码内容
47 | * @return
48 | */
49 | private static Class> compile(String sourceCode) {
50 | String className = getClassName(sourceCode);
51 |
52 | DynamicCompiler dynamicCompiler = new DynamicCompiler(JavaEvalUtil.class.getClassLoader());
53 | dynamicCompiler.addSource(className, sourceCode);
54 |
55 | Map> build = dynamicCompiler.build();
56 | if (build.isEmpty()) {
57 | throw new RuntimeException("java eval compile error");
58 | }
59 | return build.values().iterator().next();
60 | }
61 |
62 | private static String getClassName(String sourceCode) {
63 | return StringUtils.trim(StringUtils.substringBetween(sourceCode, "public class ", "{"));
64 | }
65 |
66 | /**
67 | * 反射调用
68 | *
69 | * @param methtClass
70 | * @return
71 | * @throws Exception
72 | */
73 | private static Object call(Class> methtClass) throws Exception {
74 | Method[] declaredMethods = methtClass.getDeclaredMethods();
75 |
76 | for (Method declaredMethod : declaredMethods) {
77 | if (Modifier.isPublic(declaredMethod.getModifiers()) && Modifier.isStatic(declaredMethod.getModifiers())) {
78 | Object result = declaredMethod.invoke(null);
79 |
80 | LOG.error("JavaEval return: {}", JSON.toJSON(result));
81 | return result;
82 | }
83 | }
84 |
85 | throw new RuntimeException("NO method is [public static], cannot eval");
86 | }
87 |
88 | /**
89 | * 对外API,执行文件得结果
90 | *
91 | * @param content
92 | * @return
93 | * @throws Exception
94 | */
95 | public static Object eval(String content) throws Exception {
96 | // 类dump
97 | dump(content);
98 |
99 | // 编译生成内存二进制,并加载为class
100 | Class> compile = compile(content);
101 |
102 | // 反射调用class
103 | return call(compile);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/eval/compiler/CustomJavaFileObject.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.eval.compiler;
2 |
3 | /*-
4 | * #%L
5 | * compiler
6 | * %%
7 | * Copyright (C) 2017 - 2018 SkaLogs
8 | * %%
9 | * Licensed under the Apache License, Version 2.0 (the "License");
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | * #L%
21 | */
22 |
23 | import java.io.*;
24 | import java.net.URI;
25 |
26 | import javax.lang.model.element.Modifier;
27 | import javax.lang.model.element.NestingKind;
28 | import javax.tools.JavaFileObject;
29 |
30 | public class CustomJavaFileObject implements JavaFileObject {
31 | private final String binaryName;
32 | private final URI uri;
33 | private final String name;
34 |
35 | public CustomJavaFileObject(String binaryName, URI uri) {
36 | this.uri = uri;
37 | this.binaryName = binaryName;
38 | name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath(); // for FS based URI the path is not null, for JAR URI the scheme specific part is not null
39 | }
40 |
41 | public URI toUri() {
42 | return uri;
43 | }
44 |
45 | public InputStream openInputStream() throws IOException {
46 | return uri.toURL().openStream();
47 | }
48 |
49 | public OutputStream openOutputStream() {
50 | throw new UnsupportedOperationException();
51 | }
52 |
53 | public String getName() {
54 | return name;
55 | }
56 |
57 | public Reader openReader(boolean ignoreEncodingErrors) {
58 | throw new UnsupportedOperationException();
59 | }
60 |
61 | public CharSequence getCharContent(boolean ignoreEncodingErrors) {
62 | throw new UnsupportedOperationException();
63 | }
64 |
65 | public Writer openWriter() throws IOException {
66 | throw new UnsupportedOperationException();
67 | }
68 |
69 | public long getLastModified() {
70 | return 0;
71 | }
72 |
73 | public boolean delete() {
74 | throw new UnsupportedOperationException();
75 | }
76 |
77 | public Kind getKind() {
78 | return Kind.CLASS;
79 | }
80 |
81 | public boolean isNameCompatible(String simpleName, Kind kind) {
82 | String baseName = simpleName + kind.extension;
83 | return kind.equals(getKind())
84 | && (baseName.equals(getName())
85 | || getName().endsWith("/" + baseName));
86 | }
87 |
88 | public NestingKind getNestingKind() {
89 | throw new UnsupportedOperationException();
90 | }
91 |
92 | public Modifier getAccessLevel() {
93 | throw new UnsupportedOperationException();
94 | }
95 |
96 | public String binaryName() {
97 | return binaryName;
98 | }
99 |
100 |
101 | public String toString() {
102 | return this.getClass().getName() + "[" + this.toUri() + "]";
103 | }
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/eval/compiler/DynamicClassLoader.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.eval.compiler;
2 |
3 | /*-
4 | * #%L
5 | * compiler
6 | * %%
7 | * Copyright (C) 2017 - 2018 SkaLogs
8 | * %%
9 | * Licensed under the Apache License, Version 2.0 (the "License");
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | * #L%
21 | */
22 |
23 | import java.util.HashMap;
24 | import java.util.Map;
25 | import java.util.Map.Entry;
26 |
27 | public class DynamicClassLoader extends ClassLoader {
28 | private final Map byteCodes = new HashMap();
29 |
30 | public DynamicClassLoader(ClassLoader classLoader) {
31 | super(classLoader);
32 | }
33 |
34 | public void registerCompiledSource(MemoryByteCode byteCode) {
35 | byteCodes.put(byteCode.getClassName(), byteCode);
36 | }
37 |
38 | @Override
39 | protected Class> findClass(String name) throws ClassNotFoundException {
40 | MemoryByteCode byteCode = byteCodes.get(name);
41 | if (byteCode == null) {
42 | return super.findClass(name);
43 | }
44 |
45 | return super.defineClass(name, byteCode.getByteCode(), 0, byteCode.getByteCode().length);
46 | }
47 |
48 | public Map> getClasses() throws ClassNotFoundException {
49 | Map> classes = new HashMap>();
50 | for (MemoryByteCode byteCode : byteCodes.values()) {
51 | classes.put(byteCode.getClassName(), findClass(byteCode.getClassName()));
52 | }
53 | return classes;
54 | }
55 |
56 | public Map getByteCodes() {
57 | Map result = new HashMap(byteCodes.size());
58 | for (Entry entry : byteCodes.entrySet()) {
59 | result.put(entry.getKey(), entry.getValue().getByteCode());
60 | }
61 | return result;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/eval/compiler/DynamicCompiler.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.eval.compiler;
2 |
3 | import java.util.*;
4 |
5 | import javax.tools.*;
6 |
7 | public class DynamicCompiler {
8 | private final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
9 | private final StandardJavaFileManager standardFileManager;
10 | private final List options = new ArrayList();
11 | private final DynamicClassLoader dynamicClassLoader;
12 |
13 | private final Collection compilationUnits = new ArrayList();
14 | private final List> errors = new ArrayList>();
15 | private final List> warnings = new ArrayList>();
16 |
17 | public DynamicCompiler(ClassLoader classLoader) {
18 | if (javaCompiler == null) {
19 | throw new IllegalStateException(
20 | "Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(),"
21 | + " please confirm the application running in JDK not JRE.");
22 | }
23 | standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
24 |
25 | options.add("-Xlint:unchecked");
26 | dynamicClassLoader = new DynamicClassLoader(classLoader);
27 | }
28 |
29 | public void addSource(String className, String source) {
30 | addSource(new StringSource(className, source));
31 | }
32 |
33 | public void addSource(JavaFileObject javaFileObject) {
34 | compilationUnits.add(javaFileObject);
35 | }
36 |
37 | public Map> build() {
38 |
39 | errors.clear();
40 | warnings.clear();
41 |
42 | JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);
43 |
44 | DiagnosticCollector collector = new DiagnosticCollector();
45 | JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, collector, options, null,
46 | compilationUnits);
47 |
48 | try {
49 |
50 | if (!compilationUnits.isEmpty()) {
51 | boolean result = task.call();
52 |
53 | if (!result || collector.getDiagnostics().size() > 0) {
54 |
55 | for (Diagnostic extends JavaFileObject> diagnostic : collector.getDiagnostics()) {
56 | switch (diagnostic.getKind()) {
57 | case NOTE:
58 | case MANDATORY_WARNING:
59 | case WARNING:
60 | warnings.add(diagnostic);
61 | break;
62 | case OTHER:
63 | case ERROR:
64 | default:
65 | errors.add(diagnostic);
66 | break;
67 | }
68 |
69 | }
70 |
71 | if (!errors.isEmpty()) {
72 | throw new DynamicCompilerException("Compilation Error", errors);
73 | }
74 | }
75 | }
76 |
77 | Map> classes = dynamicClassLoader.getClasses();
78 | return classes;
79 | } catch (Throwable e) {
80 | throw new DynamicCompilerException(e, errors);
81 | } finally {
82 | compilationUnits.clear();
83 |
84 | }
85 |
86 | }
87 |
88 | public Map buildByteCodes() {
89 |
90 | errors.clear();
91 | warnings.clear();
92 |
93 | JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);
94 |
95 | DiagnosticCollector collector = new DiagnosticCollector();
96 | JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, collector, options, null,
97 | compilationUnits);
98 |
99 | try {
100 |
101 | if (!compilationUnits.isEmpty()) {
102 | boolean result = task.call();
103 |
104 | if (!result || collector.getDiagnostics().size() > 0) {
105 |
106 | for (Diagnostic extends JavaFileObject> diagnostic : collector.getDiagnostics()) {
107 | switch (diagnostic.getKind()) {
108 | case NOTE:
109 | case MANDATORY_WARNING:
110 | case WARNING:
111 | warnings.add(diagnostic);
112 | break;
113 | case OTHER:
114 | case ERROR:
115 | default:
116 | errors.add(diagnostic);
117 | break;
118 | }
119 |
120 | }
121 |
122 | if (!errors.isEmpty()) {
123 | throw new DynamicCompilerException("Compilation Error", errors);
124 | }
125 | }
126 | }
127 |
128 | return dynamicClassLoader.getByteCodes();
129 | } catch (ClassFormatError e) {
130 | throw new DynamicCompilerException(e, errors);
131 | } finally {
132 | compilationUnits.clear();
133 |
134 | }
135 |
136 | }
137 |
138 | private List diagnosticToString(List> diagnostics) {
139 |
140 | List diagnosticMessages = new ArrayList();
141 |
142 | for (Diagnostic extends JavaFileObject> diagnostic : diagnostics) {
143 | diagnosticMessages.add(
144 | "line: " + diagnostic.getLineNumber() + ", message: " + diagnostic.getMessage(Locale.US));
145 | }
146 |
147 | return diagnosticMessages;
148 |
149 | }
150 |
151 | public List getErrors() {
152 | return diagnosticToString(errors);
153 | }
154 |
155 | public List getWarnings() {
156 | return diagnosticToString(warnings);
157 | }
158 |
159 | public ClassLoader getClassLoader() {
160 | return dynamicClassLoader;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/cm4j-hotswap/src/main/java/com/cm4j/eval/compiler/DynamicCompilerException.java:
--------------------------------------------------------------------------------
1 | package com.cm4j.eval.compiler;
2 |
3 | /*-
4 | * #%L
5 | * compiler
6 | * %%
7 | * Copyright (C) 2017 - 2018 SkaLogs
8 | * %%
9 | * Licensed under the Apache License, Version 2.0 (the "License");
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | * #L%
21 | */
22 |
23 | import java.util.*;
24 |
25 | import javax.tools.Diagnostic;
26 | import javax.tools.JavaFileObject;
27 |
28 | public class DynamicCompilerException extends RuntimeException {
29 | private static final long serialVersionUID = 1L;
30 | private List> diagnostics;
31 |
32 | public DynamicCompilerException(String message, List> diagnostics) {
33 | super(message);
34 | this.diagnostics = diagnostics;
35 | }
36 |
37 | public DynamicCompilerException(Throwable cause, List> diagnostics) {
38 | super(cause);
39 | this.diagnostics = diagnostics;
40 | }
41 |
42 | private List