作者 钟来

优化插件

... ... @@ -4,6 +4,8 @@ import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory;
import com.zhonglai.luhui.device.protocol.factory.plugins.InitPlugins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
... ... @@ -15,10 +17,11 @@ import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
... ... @@ -26,12 +29,13 @@ import java.util.jar.JarFile;
* 自定义的类加载器
*/
public class PluginsClassLoader extends URLClassLoader {
private static final Logger log = LoggerFactory.getLogger(PluginsClassLoader.class);
/**
* 存放类
*/
private static Map<String,PluginsClassLoader> jarMap = new HashMap<>();
private static ConcurrentHashMap<String,PluginsClassLoader> jarMap = new ConcurrentHashMap<>();
private static Map<String,Class<?>> classMap = new HashMap<>();
private static ConcurrentHashMap<String,Class<?>> classMap = new ConcurrentHashMap<>();
private PluginsClassLoader(URL[] urls) {
super(urls,PluginsClassLoader.class.getClassLoader());
... ... @@ -41,16 +45,30 @@ public class PluginsClassLoader extends URLClassLoader {
* 卸载jar
* @throws IOException
*/
private static void unloadJar(String... filePaths) throws IOException {
public static void unloadJar(String... filePaths) throws IOException {
for (String filePath:filePaths)
{
String key = InitPlugins.toJarPath(filePath);
if(jarMap.containsKey(key))
{
PluginsClassLoader pluginsClassLoader = jarMap.get(key);
jarMap.remove(key);
pluginsClassLoader.close();
// 移除并关闭 ClassLoader
PluginsClassLoader pluginsClassLoader = jarMap.remove(key);
if (pluginsClassLoader != null) {
try {
pluginsClassLoader.close();
} catch (IOException e) {
log.error("Failed to close ClassLoader for JAR: " + key, e);
}
}
// 移除 classMap 中的类
Iterator<Map.Entry<String, Class<?>>> it = classMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Class<?>> entry = it.next();
if (entry.getValue().getClassLoader() == pluginsClassLoader) {
it.remove();
}
}
File file = new File(key);
if(file.exists())
{
... ... @@ -61,6 +79,7 @@ public class PluginsClassLoader extends URLClassLoader {
}
/**
* 更新jar
* @throws IOException
... ... @@ -70,11 +89,13 @@ public class PluginsClassLoader extends URLClassLoader {
for (File file:filePaths)
{
String filePath = file.getAbsolutePath();
System.out.println("绝对路径:"+filePath);
unloadJar(filePath);
String key = InitPlugins.toJarPath(filePath);
FileCopyUtils.copy(new File(filePath),new File(key));
copyFile(filePath,key);
PluginsClassLoader pluginsClassLoader = new PluginsClassLoader(new URL[]{new URL("file:"+key)});
jarMap.put(key,pluginsClassLoader);
... ... @@ -82,40 +103,31 @@ public class PluginsClassLoader extends URLClassLoader {
laodJar(new File(key),pluginsClassLoader);
}
} catch (IOException e) {
throw new RuntimeException(e);
log.error("更新jar异常",e);
}
}
private static void laodJar(File jarfile,ClassLoader classLoader)
{
JarFile jarFile = null;
try {
jarFile = new JarFile(jarfile);
private static void laodJar(File jarfile, ClassLoader classLoader) {
try (JarFile jarFile = new JarFile(jarfile)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String entryName = jarEntry.getName();
if (entryName != null && entryName.endsWith(".class")) {
entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
Class<?> clas = classLoader.loadClass(entryName);
System.out.println(ProtocolParserFactory.class.isAssignableFrom(clas));;
if(SpringUtils.isSpringBean())
{
if (SpringUtils.isSpringBean()) {
Object object = loadBean(clas);
}
classMap.put(entryName,clas);
classMap.put(entryName, clas);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException | ClassNotFoundException e) {
log.error("加载jar:"+jarfile.getAbsolutePath()+" 失败",e);
}
}
private static <T> T loadBean(Class<T> clas)
... ... @@ -181,4 +193,34 @@ public class PluginsClassLoader extends URLClassLoader {
}
return null;
}
public static void copyFile(String sourcePathStr, String targetPathStr) {
Path sourcePath = Paths.get(sourcePathStr);
Path targetPath = Paths.get(targetPathStr);
// 检查源文件是否存在
if (!Files.exists(sourcePath)) {
log.error("Source file does not exist: " + sourcePath.toString());
return;
}
// 检查目标文件所在目录是否存在,如果不存在则创建
Path targetDir = targetPath.getParent();
if (!Files.exists(targetDir)) {
try {
Files.createDirectories(targetDir);
} catch (IOException e) {
log.error("Failed to create target directory: " + targetDir.toString(),e);
return;
}
}
// 执行文件复制
try {
Files.copy(sourcePath, targetPath);
System.out.println("File copied successfully from " + sourcePath.toString() + " to " + targetPath.toString());
} catch (IOException e) {
throw new RuntimeException("Failed to copy file from " + sourcePath.toString() + " to " + targetPath.toString(), e);
}
}
}
... ...
package com.zhonglai.luhui.device.protocol.factory.plugins;
import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory;
import com.zhonglai.luhui.device.protocol.factory.config.DeviceCach;
import com.zhonglai.luhui.device.protocol.factory.config.PluginsClassLoader;
import com.zhonglai.luhui.device.protocol.factory.dto.ParserDeviceHostDto;
import net.jodah.expiringmap.ExpirationListener;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
... ... @@ -9,17 +16,29 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class FileChangeListener {
private static Logger log = LoggerFactory.getLogger(FileChangeListener.class);
private WatchService watchService;
private Thread watcherThread;
private volatile boolean running = true;
private static Integer time = 5;
private static ExpiringMap<String, Boolean> upFileMap = ExpiringMap.builder().maxSize(100).expiration(time, TimeUnit.SECONDS)
.variableExpiration()
.asyncExpirationListener((ExpirationListener<String, Boolean>) (s, b) -> log.info("超时清除>>>>>>>:{} ",s))
.expirationPolicy(ExpirationPolicy.CREATED).build();
public void startFileWatcher() throws IOException {
//初始化插件
InitPlugins.init();
... ... @@ -33,8 +52,7 @@ public class FileChangeListener {
}
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) throws IOException {
public void onApplicationEvent() throws IOException {
startFileWatcher();
}
... ... @@ -46,7 +64,7 @@ public class FileChangeListener {
// PS:Path必须是目录,不能是文件;
// StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件
Path path = Paths.get(InitPlugins.getPluginsPath());
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
}
private void startThread()
... ... @@ -59,14 +77,18 @@ public class FileChangeListener {
private void watchFileChanges() {
while (running) {
try {
//记录变化的文件
// 获取目录的变化:
// take()是一个阻塞方法,会等待监视器发出的信号才返回。
// 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。
// 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个;
WatchKey key = watchService.take();
//记录变化的文件
// 处理文件变化事件:
// key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println(event.kind()+"事件触发"+": " + event.context());
// event.kind():事件类型
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
//事件可能lost or discarded
... ... @@ -74,10 +96,28 @@ public class FileChangeListener {
}
// 返回触发事件的文件或目录的路径(相对路径)
Path fileName = (Path) event.context();
System.out.println("文件更新: " + fileName);
PluginsClassLoader.uploadJar(new File(key.watchable().toString()+"/"+fileName));
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(fileName);
File file = fullPath.toFile();
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY)
{
if(file.exists() && file.length()>0)
{
if(!upFileMap.containsKey(fullPath.toString()))
{
PluginsClassLoader.uploadJar(file);
upFileMap.put(fullPath.toString(),true);
}
}
}
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE)
{
PluginsClassLoader.unloadJar(file.getAbsolutePath());
}
}
// 每次调用WatchService的take()或poll()方法时需要通过本方法重置
if (!key.reset()) {
break;
... ... @@ -104,7 +144,8 @@ public class FileChangeListener {
// 关闭WatchService
try {
if (watchService != null) {
watchService.close();
watchService.
close();
}
} catch (IOException e) {
e.printStackTrace();
... ...
... ... @@ -83,36 +83,10 @@ public class InitPlugins {
public static void main(String[] args) throws MalformedURLException {
File[] files = getJarFiles(getPluginsPath());
PluginsClassLoader.uploadJar(files);
// String jarFilePath = "E:\\work\\idea\\Luhui\\lh-modules\\lh-device-protocol-parser\\lh-device-xinjie\\target";
// File[] files = getJarFiles(jarFilePath);
// loaderJar(files);
// try {
// ProtocolParserFactory protocolParserFactory = getJarClass(ProtocolParserFactory.class,"com.zhonglai.luhui.device.protocol.xinjie.analysis.ProtocolParserServiceImpl");
// Topic topic = protocolParserFactory.analysisTopic("/13/jiulin/476210165B365166812345678Userdata/Json/476210165B365166812345678/pub_data");
// System.out.println(topic);
// } catch (InstantiationException e) {
// throw new RuntimeException(e);
// } catch (IllegalAccessException e) {
// throw new RuntimeException(e);
// }
}
// public static <T> T getJarClass(Class<T> clazz, String className) {
// try {
// Class<?> loadedClass = Class.forName(className);
// System.out.println("接口的类加载器:"+clazz.getClassLoader());
// System.out.println("实现类的类加载器:"+loadedClass.getClassLoader());
// if (clazz.isAssignableFrom(loadedClass)) {
// return clazz.cast(loadedClass.getDeclaredConstructor().newInstance());
// }
// } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
// // Handle exceptions here
// }
// return null;
// }
public static String toJarPath(String filePath)
{
return filePath.replace(pluginsPath,pluginsJarPath);
... ...