当前位置:网站首页>Customize classloader and implement hot deployment - use loadclass
Customize classloader and implement hot deployment - use loadclass
2022-04-23 12:56:00 【Xiao an】
Requirements are as follows :
- Can be added at the front end 、 modify java Code , It can deploy and run the code without restarting the service .
- amount to : Can be realized java Hot deployment of code .
The code is as follows :
- ClassLoaderTest: Customize classloader Test class
- ClazzCache: Customize ClassLoader Cache class
- CustomCompiler: Custom compiler
- CustomClassLoader: Customize ClassLoader
- CustomClassLoaderParam: Customize ClassLoader Packaging ( Do not directly use the methods in this class , Should be used CustomClassLoader To achieve the function )
- TestCode: Written by the analog front end java Code
- TestCodeUpdate: Written by the analog front end java Code
ClassLoaderTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@SpringBootTest(classes = ApiApplication.class)
public class ClassLoaderTest {
@Test
public void classLoaderTest() throws Exception {
// New operation - First load , Should be approved findClass obtain
ClazzCache testCode = CustomClassLoaderParam.findClass("TestCode", 1L);
Object invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
System.err.println(invoke);
// Second load , Should get through cache
testCode = CustomClassLoaderParam.findClass("TestCode", 1L);
invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
System.err.println(invoke);
// New operation - First load , Should be approved findClass obtain
testCode = CustomClassLoaderParam.findClass("TestCodeUpdate", 1L);
invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
System.err.println(invoke);
// update operation - New will be created ClassLoader, new ClassLoader No load , It will be reloaded , adopt findClass obtain
ClazzCache testCodeUpdate = CustomClassLoaderParam.findClass("TestCode", 2L);
Object invokeUpdate = testCodeUpdate.getMethod().invoke(testCodeUpdate.getObj(), new EnterpriseMessage());
System.err.println(invokeUpdate);
// Once again , Due to the creation of a new ClassLoader, Should have passed findClass obtain , But cache processing is done , You should get... From the cache
testCode = CustomClassLoaderParam.findClass("TestCodeUpdate", 1L);
invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
System.err.println(invoke);
}
}

ClazzCache.java
import lombok.*;
import java.io.Serializable;
import java.lang.reflect.Method;
/** * Customize classloader Cache class information */
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClazzCache implements Serializable {
/** * Instance object */
private Object obj;
/** * Method */
private Method method;
/** * Last use time */
private Long lastUsedTime = System.currentTimeMillis();
}
CustomCompiler.java
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.io.*;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.*;
/** * Custom compiler */
public class CustomCompiler {
private static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/** * take java Code compiled as class Bytecode and return byte Array */
public static Map<String, byte[]> compiler(String className, String sourceCode) throws Exception {
try (
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager);
Writer writer = new StringWriter()
) {
JavaFileObject javaFileObject = manager.makeStringSource(className, sourceCode);
JavaCompiler.CompilationTask task = compiler.getTask(writer, manager, null, null, null, Arrays.asList(javaFileObject));
if (task.call()) {
return manager.getClassBytes();
} else {
throw new Exception(" Compile error :" + writer.toString());
}
}
}
/** * Memory Java File manager */
static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
final Map<String, List<JavaFileObject>> classObjectPackageMap = new HashMap<>();
MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
public Map<String, byte[]> getClassBytes() {
return new HashMap(this.classBytes);
}
@Override
public void flush() {
}
@Override
public void close() {
classBytes.clear();
}
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
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);
}
};
}
}
}
}
CustomClassLoader.java
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
/** * Customize classloader */
@Slf4j
public class CustomClassLoader extends ClassLoader {
/** * Deposit java Bytecode corresponding to the file ,findClass Deposited before ,defineClass Then remove */
public static Map<String, byte[]> CLASS_BYTES_TEMP = new ConcurrentHashMap();
/** * MyClassLoader The singleton pattern :DCL The way */
private static volatile CustomClassLoader instance;
private CustomClassLoader() {
}
public static CustomClassLoader getInstance() {
if (null == instance) {
synchronized (CustomClassLoader.class) {
if (null == instance) {
instance = new CustomClassLoader();
}
}
}
return instance;
}
/** * Reset classloader, Return to new classLoader */
public synchronized static void createNewClassLoader() {
instance = new CustomClassLoader();
}
/** * compile java The file is bytecode data , And stored in the cache , For subsequent use */
public void compiler(String name, byte[] byteClazz) {
CLASS_BYTES_TEMP.put(name, byteClazz);
}
/** * rewrite findClass, Customize ClassLoader */
@Override
public Class<?> findClass(String packageName) {
try {
log.info(" loadClass [{}] from findClass !", packageName);
byte[] byteClazz;
// Get... From local cache byte Bytecode information
byteClazz = CLASS_BYTES_TEMP.get(packageName);
if (byteClazz != null && byteClazz.length > 10) {
return defineClass(packageName, byteClazz, 0, byteClazz.length);
}
return null;
} finally {
// Remove bytecode information from local cache
CLASS_BYTES_TEMP.remove(packageName);
}
}
}
CustomClassLoaderParam.java
import com.fintell.dp3.biz.entity.EnterpriseMessage;
import com.fintell.dp3.common.SpringContextHolder;
import com.fintell.dp3.common.redis.RedisClient;
import com.fintell.dp3.common.redis.RedisContact;
import com.fintell.dp3.extend.VariableBuiltInExtend;
import com.fintell.tools.report.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** * Customize classloader Packaging * 1. Lock by class name , Load class only once * 2. Judge whether there is in the local cache , Whether the timestamp cache of class exists , If it does not exist, the table name is "add operation" * 3. Judge whether there is in the local cache , Class timestamp And To get whether the timestamp of the class is consistent , If it is inconsistent, it indicates that it is an update operation , You need to create a new ClassLoader And reload the class information * 4. Judge whether there is in the local cache , Whether class information already exists , If exist , Then return directly , If it does not exist , Then use ClassLoader.loadClass() Loading , And stored in the cache */
@Slf4j
public class CustomClassLoaderParam {
private static RedisClient redisClient = SpringContextHolder.getBean(RedisClient.class);
/** * Package path of extension class , Use VariableBuiltInExtend The package path */
public static final String basePackage = VariableBuiltInExtend.class.getPackage().getName() + ".";
/** * Class information cache */
private static Map<String, ClazzCache> cacheClazz = new ConcurrentHashMap<>();
/** * Used to record the corresponding time of the currently loaded class * key : name * value : Time stamp of loading class */
private static Map<String, Long> updateClazzs = new ConcurrentHashMap<>();
/** * rewrite findClass, Do cache processing * * @param className : Class name -> Variable name * @param timestampCurrent : The latest timestamp of the variable */
public static ClazzCache findClass(String className, Long timestampCurrent) throws Exception {
// Lock by class name
synchronized (className) {
Long timeStampLastModify = updateClazzs.get(className);
// 1. If it's not in the cache , This indicates that it is a new operation , Then reload class
if (timeStampLastModify == null) {
cacheClazz.remove(className);
}
// 2. If it exists in the cache , And the time is inconsistent , It is described as update operation , Use the new classloader And reload class file
if (timeStampLastModify != null && timestampCurrent != timeStampLastModify) {
cacheClazz.remove(className);
CustomClassLoader.createNewClassLoader();
}
// 1> From the cache
ClazzCache cache = cacheClazz.get(className);
if (null != cache) {
// 2> If cache exists , Then update the last use time , And return directly
cache.setLastUsedTime(System.currentTimeMillis());
log.info(" loadClass [{}] from cache !", className);
return cache;
}
// 1. If getting from cache fails , Then it means that it needs to be reloaded class file ,findClass Compile before java The data is a bytecode array
CustomClassLoader.getInstance().compiler(basePackage + className, getClazzCode(className));
// 2. Use the new ClassLoader Reload class
Class aClass = CustomClassLoader.getInstance().loadClass(basePackage + className);
// 3. Put the data in the local cache
Method method = aClass.getDeclaredMethod("invoke", EnterpriseMessage.class);
method.setAccessible(true);
ClazzCache clazzCache = ClazzCache.builder().obj(aClass.newInstance()).method(method).build();
cacheClazz.put(className, clazzCache);
// 4. Update cache time
updateClazzs.put(className, timestampCurrent);
// 5. Return cache class information
return clazzCache;
}
}
/** * Get bytecode information */
private static byte[] getClazzCode(String className) throws Exception{
byte[] byteClazz;
// 1. from redis Get bytecode
byteClazz = (byte[]) redisClient.getObj(RedisContact.getJavaCodeKey(className));
// 1.1 If from redis Get bytecode information , Then load class
if (byteClazz != null && byteClazz.length > 10) {
return byteClazz;
}
// 2. Get the source code from the database and compile it
String sourceCode = getSourceCode(className);
if (sourceCode == null || StringUtils.isEmpty(sourceCode)) {
throw new Exception(" Custom variable does not exist or variable logic does not exist !");
}
// 3. Compile the source code
Map<String, byte[]> compiler = CustomCompiler.compiler(className, sourceCode);
byteClazz = compiler.get(basePackage + className);
// 4. Put into cache
redisClient.set(RedisContact.getJavaCodeKey(className), byteClazz);
return byteClazz;
}
/** * Simulate obtaining the source code from the database */
private static String getSourceCode(String className) throws Exception {
return FileUtil.readFile(new File("D:\\cls\\" + className + ".java"), "utf-8");
}
}
TestCode.java
package com.fintell.dp3.extend;
import com.fintell.dp3.biz.entity.EnterpriseMessage;
public class TestCode {
public Object invoke(EnterpriseMessage enterpriseMessage) throws Exception{
Object defaultVal = 0;
try {
System.err.println(" add ");
return defaultVal;
} catch (NullPointerException ne) {
return defaultVal;
} catch (Exception e) {
throw e;
}
}
}
TestCodeUpdate.java
package com.fintell.dp3.extend;
import com.fintell.dp3.biz.entity.EnterpriseMessage;
public class TestCodeUpdate {
public Object invoke(EnterpriseMessage enterpriseMessage) throws Exception{
Object defaultVal = 0;
try {
System.err.println(" update ");
return defaultVal;
} catch (NullPointerException ne) {
return defaultVal;
} catch (Exception e) {
throw e;
}
}
}
版权声明
本文为[Xiao an]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230615438178.html
边栏推荐
- CVPR 2022 & ntire 2022 | the first transformer for hyperspectral image reconstruction
- XinChaCha Trust SSL Organization Validated
- STM32控制步进电机(ULN2003+28byj)
- 22. 括号生成
- World Book Day: I'd like to recommend these books
- SSL certificate refund instructions
- Importerror after tensorflow installation: DLL load failed: the specified module cannot be found, and the domestic installation is slow
- STM32工程移植:不同型号芯片工程之间的移植:ZE到C8
- 21 天学习MongoDB笔记
- CGC: contractual graph clustering for community detection and tracking
猜你喜欢

SSM框架系列——Junit单元测试优化day2-3
![Packet capturing and sorting -- TCP protocol [8]](/img/ae/4957a997af725a1bf3f61cb24fc717.png)
Packet capturing and sorting -- TCP protocol [8]

实现一个盒子在父盒子中水平垂直居中的几种“姿势”

leetcode:437. 路径总和 III【dfs 选还是不选?】

World Book Day: I'd like to recommend these books

0基础可以考CPDA数据分析师证书吗

Van uploader upload picture implementation process, using native input to upload pictures

数据库中的日期时间类型

Labels and paths

SSM framework series - data source configuration day2-1
随机推荐
世界读书日:我想推荐这几本书
Unable to create servlet under SRC subfile of idea
NBIOT的AT指令
Object. The disorder of key value array after keys
Object.keys后key值数组乱序的问题
Analysis of InnoDB execution process in MySQL
Teach you to quickly develop a werewolf killing wechat applet (with source code)
Date time type in database
21 days learning mongodb notes
leetcode-791. Custom string sorting
How to click an object to play an animation
SSM框架系列——注解开发day2-2
Number of nodes of complete binary tree
STM32 is connected to the motor drive, the DuPont line supplies power, and then the back burning problem
Try the server for one month for free, and attach the tutorial
PHP generates JSON to process Chinese
mysql支持ip访问
Calculate the past date and days online, and calculate the number of live days
【每日一题】棋盘问题
使用Source Insight查看编辑源代码