Jdk8 dynamically compiles Java source code into Class files
1.JDK version
2. Project introduction
Dynamic source code compilation requires a custom class loader. The JVM will determine whether it is the same class based on the class loader and the full class name. Therefore, during dynamic compilation and loading, the same class cannot be loaded twice with the same class loader unless Remove old classes from JVM level.
When the same class is loaded by different class loaders, the JVM will judge it as non-similar, so it cannot be directly instantiated and then forced to an instance of the same type.Need to implement dynamic replacement based on interfaces and abstract classes
1. Dependence
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-dynamic</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>2.7.4</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0_341</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}\lib\tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<finalName>dynamic-demo</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.4</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<!-- for tools.jar -->
<!-- <configuration>-->
<!-- <includeSystemScope>true</includeSystemScope>-->
<!-- </configuration>-->
</plugin>
</plugins>
</build>
</project>
2. Startup class
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author
* @date 2023-08-10 9:51
* @since 1.8
*/
@SpringBootApplication
public class DynamicApp {
public static void main(String[] args) {
SpringApplication.run(DynamicApp.class,args);
}
}
3. Configuration class (for testing dependency injection)
package com.example.config;
import org.springframework.stereotype.Component;
/**
* @author moon
* @date 2023-08-30 14:58
* @since 1.8
*/
@Component
public class KafkaConfig {
public void getConfig(){
System.out.println("kafka config");
}
}
4.Tools
1.Java source file reading class
package com.example.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* @author moon
* @date 2023-08-31 9:25
* @since 1.8
*/
public class FileUtil {
public static String readJson(String filePath){
if (org.springframework.util.StringUtils.hasLength(filePath)){
InputStream inputStream = null;
StringBuilder builder = new StringBuilder();
try {
int batchSize = 2048;
inputStream = new FileInputStream(filePath);
byte[] temp = new byte[batchSize];
int read;
while ((read = inputStream.read(temp)) != -1){
if (read < batchSize){
byte[] tail = Arrays.copyOf(temp,read);
builder.append(new String(tail));
} else {
builder.append(new String(temp));
}
}
return builder.toString();
} catch (IOException e) {
return "";
} finally {
if (null != inputStream){
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return "";
}
}
2.SpringBoot container instance management class
package com.example.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author moon
* @date 2023-08-31 11:18
* @since 1.8
*/
@Component
public class SpringBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private static ConfigurableApplicationContext context ;
/**
* 获取 Bean 工厂
*/
private static DefaultListableBeanFactory beanFactory ;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanUtil.applicationContext = applicationContext;
SpringBeanUtil.context= (ConfigurableApplicationContext) applicationContext;
SpringBeanUtil.beanFactory= (DefaultListableBeanFactory) context.getBeanFactory();
}
/**
* 替换 bean 并获取新的
* @param beanName
* @param clazz
* @return
*/
public static Object replace(String beanName,Class clazz){
//卸载
unregister(beanName);
//注册
register(beanName,clazz);
//获取
return getBean(beanName);
}
/**
* 注册 Bean
* @param clazz
*/
public static void register(String beanName,Class clazz){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition definition = builder.getBeanDefinition();
//为 definition 设置额外属性
definition.setScope("singleton");
//注册
beanFactory.registerBeanDefinition(beanName,definition);
}
/**
* 卸载 Bean
* @param beanName
*/
public static void unregister(String beanName){
if (beanFactory.containsBean(beanName)){
beanFactory.removeBeanDefinition(beanName);
}
}
/**
* 获取所有 Bean
* @return
*/
public static List<String> getBeans(){
String[] names = applicationContext.getBeanDefinitionNames();
List<String> beans = new ArrayList<>(names.length);
for (String name:names){
beans.add(applicationContext.getBean(name).getClass().getName());
}
return beans;
}
/**
* bean 是否存在
* @param name
* @return
*/
public static boolean isBeanExist(String name){
return applicationContext.containsBean(name);
}
/**
* 通过名称获取 Bean
* @param name
* @return
* @param <T>
* @throws BeansException
*/
public static <T> T getBean(String name) throws BeansException{
return (T) applicationContext.getBean(name);
}
/**
* 通过类型获取 Bean
* @param clazz
* @return
* @param <T>
* @throws BeansException
*/
public static <T> T getBean(Class<?> clazz) throws BeansException{
return (T) applicationContext.getBean(clazz);
}
/**
* 获取指定类型的 Bean 的名称
* @param className
* @return
* @throws BeansException
*/
public static List<String> getBeanName(String className) throws BeansException, ClassNotFoundException {
Class<?> clazz = Class.forName(className);
return Arrays.asList(applicationContext.getBeanNamesForType(clazz));
}
}
5.Test category
1.Abstract class
package com.example.service;
/**
* @author moon
* @date 2023-08-30 14:15
* @since 1.8
*/
public abstract class TestAbstract {
/**
* 抽象方法
* @param str
*/
public abstract void hand(String str);
}
2.Interface class
package com.example.service;
/**
* @author moon
* @date 2023-08-31 10:58
* @since 1.8
*/
public interface TestService {
/**
* 处理
* @param str
*/
void hand(String str);
}
3. Default abstract implementation
package com.example.service.impl;
import com.example.config.KafkaConfig;
import com.example.service.TestAbstract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author moon
* @date 2023-08-31 11:01
* @since 1.8
*/
@Component
public class TestAbstractImpl extends TestAbstract {
@Autowired
KafkaConfig config;
@Value("${my.ip}")
String ip;
@Override
public void hand(String str) {
config.getConfig();
System.out.println(str);
System.out.println(ip);
}
}
4.Default interface implementation
package com.example.service.impl;
import com.example.config.KafkaConfig;
import com.example.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author moon
* @date 2023-08-31 10:59
* @since 1.8
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
KafkaConfig config;
@Override
public void hand(String str) {
config.getConfig();
System.out.println("hand: " + this);
}
}
6.Interface class
1. Test interface
package com.example.controller;
import com.example.service.TestAbstract;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author moon
* @date 2023-08-30 17:27
* @since 1.8
*/
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 引入抽象类依赖
*/
@Resource(name = "testAbstractImpl")
TestAbstract testAbstract;
@GetMapping("/print")
public void print(String content){
testAbstract.hand("Hello : " + content);
}
}
2. Class overloading control interface
package com.example.controller;
import com.example.dynamic.MemoryClassLoader;
import com.example.service.TestAbstract;
import com.example.util.FileUtil;
import com.example.util.SpringBeanUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
/**
* @author moon
* @date 2023-08-30 14:10
* @since 1.8
*/
@RestController
@RequestMapping("/reload")
public class Reload extends ClassLoader{
@GetMapping("/re")
public void re(String name,String beanName) throws InstantiationException, IllegalAccessException, InvocationTargetException, MalformedURLException, NoSuchMethodException, ClassNotFoundException {
String className = "com.example.service.impl." + name;
String classPath = "C:\\Users\\administrator\\Desktop\\jar\\"+name+".java";
String javaStr = FileUtil.readJson(classPath);
/**
* 定义新的 MemoryClassLoader 同一个 MemoryClassLoader 不能两次加载同一个类
*/
MemoryClassLoader loader = new MemoryClassLoader();
loader.registerJava(className,javaStr);
Class clazz = loader.findClass(className);
TestAbstract handler = (TestAbstract) clazz.getDeclaredConstructor().newInstance();
// 将外部Jar包中的Bean注入到Spring容器中
Object obj = SpringBeanUtil.replace(beanName,clazz);
TestAbstract test = (TestAbstract) obj;
test.hand("sss");
System.out.println();
}
}
7. Dynamically compiled classes
1. Class loader
package com.example.dynamic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author moon
*/
@Slf4j
@Component
public class MemoryClassLoader extends URLClassLoader {
/**
* 缓存字节码
*/
private Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();
/**
* 构造
*/
public MemoryClassLoader() {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
}
/**
* 注册 Java 字符串到内存类加载器中
*
* @param className 类名字
* @param javaStr Java字符串
*/
public void registerJava(String className, String javaStr) {
try {
this.classBytesMap.putAll(compile(className, javaStr));
} catch (Exception e) {
log.error("register java class exception:");
}
}
/**
* 编译 Java 源码
*
* @param className 类名字
* @param javaStr Java代码
* @return class 二进制
*/
private Map<String, byte[]> compile(String className, String javaStr) {
//初始化编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//获取Java文件管理器
try (MemoryJavaFileManager manager = new MemoryJavaFileManager()) {
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 (Exception e) {
log.error("compile java str exception:",e);
}
return null;
}
/**
* 获取 Class
* @param name the name of the class
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytesMap.get(name);
if (buf == null) {
return super.findClass(name);
}
classBytesMap.remove(name);
return defineClass(name, buf, 0, buf.length);
}
/**
* 获取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");
}
}
2. Class Manager
package com.example.dynamic;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import org.springframework.boot.loader.jar.JarFile;
import javax.tools.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
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 文件管理器
* 用于加载 SpringBoot 下面的依赖资源
*
* @author moon
* @date 2023-08-10 9:58
* @since 1.8
*/
public class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/**
* 缓存字节码
*/
final Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();
/**
* 缓存文件对象
*/
final Map<String, List<JavaFileObject>> classObjectPackageMap = new ConcurrentHashMap<>();
/**
* 文件管理器
*/
private JavacFileManager javaFileManager;
/**
* 包名 / JavaFile(.java)
*/
public final static Map<String, List<JavaFileObject>> CLASS_OBJECT_PACKAGE_MAP = new ConcurrentHashMap<>();
/**
* 锁对象
*/
private static final Object lock = new Object();
/**
* 初始化标识
*/
private static boolean isInit = false;
/**
* 初始化
*/
public void init() {
try {
JarFile tempJarFile;
List<JavaFileObject> javaFiles;
String packageName,className;
//获取当前 Jar 包
String jarBaseFile = MemoryClassLoader.getPath();
//加载 Jar 包
JarFile jarFile = new JarFile(new File(jarBaseFile));
//取包自身文件
for (JarEntry entry:jarFile){
//SpringBoot repackage 打包 class 文件带一个 BOOT-INF/classes/ 之后才是包名
String name = entry.getName().replace("BOOT-INF/classes/","");
String classPath = name.replace("/", ".");
//如果不是 class 文件跳过
if (name.endsWith(".class")){
//取出包名
packageName = classPath.substring(0, name.lastIndexOf("/"));
//取类名
className = classPath.replace(".class", "");
//创建集合
javaFiles = Optional.ofNullable(CLASS_OBJECT_PACKAGE_MAP.get(packageName)).orElse(new ArrayList<>()) ;
//取 JavaFile
filterClass(packageName,className,jarFile.getUrl(),entry.getName(),javaFiles);
}
}
//遍历取内部 Jar 包
List<JarEntry> entries = jarFile.stream().filter(jarEntry -> {
return jarEntry.getName().endsWith(".jar");
}).collect(Collectors.toList());
// Jar File
for (JarEntry entry : entries) {
//取内部文件
tempJarFile = jarFile.getNestedJarFile(jarFile.getEntry(entry.getName()));
//跳过工具包 Jar
if (tempJarFile.getName().contains("tools.jar")) {
continue;
}
//遍历 Jar 文件
Enumeration<JarEntry> tempEntriesEnum = tempJarFile.entries();
while (tempEntriesEnum.hasMoreElements()) {
JarEntry jarEntry = tempEntriesEnum.nextElement();
String classPath = jarEntry.getName().replace("/", ".");
//如果不是 class 文件跳过
if (!classPath.endsWith(".class") || jarEntry.getName().lastIndexOf("/") == -1) {
continue;
} else {
//取出包名
packageName = classPath.substring(0, jarEntry.getName().lastIndexOf("/"));
//取类名
className = jarEntry.getName().replace("/", ".").replace(".class", "");
//创建集合
javaFiles = Optional.ofNullable(CLASS_OBJECT_PACKAGE_MAP.get(packageName)).orElse(new ArrayList<>()) ;
//取 JavaFile
filterClass(packageName,className,tempJarFile.getUrl(),jarEntry.getName(),javaFiles);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
isInit = true;
}
/**
* 取 class
* @param packageName
* @param className
* @param url
* @param entryName
* @param javaFiles
*/
private void filterClass(String packageName,String className,URL url,String entryName,List<JavaFileObject> javaFiles) throws MalformedURLException {
//取 JavaFile
javaFiles.add(new MemorySpringBootInfoJavaClassObject(className, new URL(url, entryName), javaFileManager));
//缓存 Package / JavaFile
CLASS_OBJECT_PACKAGE_MAP.put(packageName, javaFiles);
}
/**
* 构造
*/
MemoryJavaFileManager() {
super(getStandardFileManager(null, null, null));
this.javaFileManager = (JavacFileManager) fileManager;
}
/**
* 获取文件对象集合
* @param packageName
* @return
*/
public List<JavaFileObject> getLibJarsOptions(String packageName) {
synchronized (lock) {
if (!isInit) {
init();
}
}
return CLASS_OBJECT_PACKAGE_MAP.get(packageName);
}
@Override
public Iterable<JavaFileObject> list(Location location,
String packageName,
Set<JavaFileObject.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(JavaFileObject.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, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new MemoryOutputJavaClassObject(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
/**
* 设置源码
* @param className
* @param code
* @return
*/
JavaFileObject makeStringSource(String className, final String code) {
String classPath = className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension;
return new SimpleJavaFileObject(URI.create("string:///" + classPath), JavaFileObject.Kind.SOURCE) {
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
};
}
/**
* 设置字节码
* @param className
* @param bs
*/
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();
classBytesMap.put(className, bs);
makeBinaryClass(className, bs);
}
};
}
}
/**
* 获取编译结果
* @return
*/
public Map<String, byte[]> getClassBytes() {
return new HashMap<>(this.classBytesMap);
}
/**
* 刷新
* @throws IOException
*/
@Override
public void flush() throws IOException {
}
/**
* 关闭
* @throws IOException
*/
@Override
public void close() throws IOException {
classBytesMap.clear();
}
/**
* 自定义 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);
}
}
3. Class objects
package com.example.dynamic;
import com.sun.tools.javac.file.BaseFileObject;
import com.sun.tools.javac.file.JavacFileManager;
import javax.tools.JavaFileObject;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
/**
* 用来读取 spring boot 的 class
*
* @author moon
* @date 2023-08-10 9:57
* @since 1.8
*/
public class MemorySpringBootInfoJavaClassObject extends BaseFileObject {
private final String className;
private URL url;
public MemorySpringBootInfoJavaClassObject(String className, URL url, JavacFileManager javacFileManager) {
super(javacFileManager);
this.className = className;
this.url = url;
}
@Override
public JavaFileObject.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, JavaFileObject.Kind kind) {
return false;
}
}
4.Java file class
package com.example.dynamic;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Iterator;
/**
* Java 文件管理器
*
* @author moon
* @date 2023-08-10 9:53
* @since 1.8
*/
public class SpringJavaFileManager extends JavacFileManager {
/**
*
* @param context
* @param b
* @param charset
*/
public SpringJavaFileManager(Context context, boolean b, Charset charset) {
super(context, b, charset);
}
/**
* 重写类加载器
* @param location a location
* @return
*/
@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()]));
}
}
/**
* 获取 LaunchedURLClassLoader 加载器
*
* @param var1
* @return
*/
@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);
}
}
8.Configuration file
server:
port: 8082
my:
ip: 123.456.789.1
3. Test
Start the Java service (Xbootclasspath introduces tools.jar)
java -Xbootclasspath/a:C:\Progra~1\Java\jdk1.8.0_341\jre\lib\tools.jar -jar dynamic-demo.jar
1. Test class
1. Modification of the original test class
package com.example.service.impl;
import com.example.config.KafkaConfig;
import com.example.service.TestAbstract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author moon
* @date 2023-08-31 11:01
* @since 1.8
*/
@Component
public class TestAbstractImpl extends TestAbstract {
@Autowired
KafkaConfig config;
@Value("${my.ip}")
String ip;
@Override
public void hand(String str) {
config.getConfig();
System.out.println("How are you" + str);
System.out.println(ip);
}
}
2. Test
1. Direct printing of the original type
http://127.0.0.1:8082/test/print?content=lisi
2. Modification of original class
重载
http://127.0.0.1:8082/reload/re?name=TestAbstractImpl&beanName=testAbstractImpl
Call the test
http://127.0.0.1:8082/test/print?content=zhangsan
4.Jar decompilation record
1. IDEA installs the plug-in Java Decompiler
2. Find the plug-in package (the Jar package can be taken to other locations for use): C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2022.2\plugins\java-decompiler\lib
创建一个 SRC 目录
反编译命令
%JAVA_HOME_19%\java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true tools.jar src
The result is a tools.jar file. Change its extension to .zip and decompress it to see that it is actually a java file.