作者 钟来

优化插件

@@ -4,6 +4,8 @@ import com.ruoyi.common.utils.StringUtils; @@ -4,6 +4,8 @@ import com.ruoyi.common.utils.StringUtils;
4 import com.ruoyi.common.utils.spring.SpringUtils; 4 import com.ruoyi.common.utils.spring.SpringUtils;
5 import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory; 5 import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory;
6 import com.zhonglai.luhui.device.protocol.factory.plugins.InitPlugins; 6 import com.zhonglai.luhui.device.protocol.factory.plugins.InitPlugins;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
7 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
8 import org.springframework.stereotype.Service; 10 import org.springframework.stereotype.Service;
9 import org.springframework.util.FileCopyUtils; 11 import org.springframework.util.FileCopyUtils;
@@ -15,10 +17,11 @@ import java.lang.reflect.Modifier; @@ -15,10 +17,11 @@ import java.lang.reflect.Modifier;
15 import java.net.MalformedURLException; 17 import java.net.MalformedURLException;
16 import java.net.URL; 18 import java.net.URL;
17 import java.net.URLClassLoader; 19 import java.net.URLClassLoader;
18 -import java.util.Enumeration;  
19 -import java.util.HashMap;  
20 -import java.util.List;  
21 -import java.util.Map; 20 +import java.nio.file.Files;
  21 +import java.nio.file.Path;
  22 +import java.nio.file.Paths;
  23 +import java.util.*;
  24 +import java.util.concurrent.ConcurrentHashMap;
22 import java.util.jar.JarEntry; 25 import java.util.jar.JarEntry;
23 import java.util.jar.JarFile; 26 import java.util.jar.JarFile;
24 27
@@ -26,12 +29,13 @@ import java.util.jar.JarFile; @@ -26,12 +29,13 @@ import java.util.jar.JarFile;
26 * 自定义的类加载器 29 * 自定义的类加载器
27 */ 30 */
28 public class PluginsClassLoader extends URLClassLoader { 31 public class PluginsClassLoader extends URLClassLoader {
  32 + private static final Logger log = LoggerFactory.getLogger(PluginsClassLoader.class);
29 /** 33 /**
30 * 存放类 34 * 存放类
31 */ 35 */
32 - private static Map<String,PluginsClassLoader> jarMap = new HashMap<>(); 36 + private static ConcurrentHashMap<String,PluginsClassLoader> jarMap = new ConcurrentHashMap<>();
33 37
34 - private static Map<String,Class<?>> classMap = new HashMap<>(); 38 + private static ConcurrentHashMap<String,Class<?>> classMap = new ConcurrentHashMap<>();
35 39
36 private PluginsClassLoader(URL[] urls) { 40 private PluginsClassLoader(URL[] urls) {
37 super(urls,PluginsClassLoader.class.getClassLoader()); 41 super(urls,PluginsClassLoader.class.getClassLoader());
@@ -41,16 +45,30 @@ public class PluginsClassLoader extends URLClassLoader { @@ -41,16 +45,30 @@ public class PluginsClassLoader extends URLClassLoader {
41 * 卸载jar 45 * 卸载jar
42 * @throws IOException 46 * @throws IOException
43 */ 47 */
44 - private static void unloadJar(String... filePaths) throws IOException { 48 + public static void unloadJar(String... filePaths) throws IOException {
45 for (String filePath:filePaths) 49 for (String filePath:filePaths)
46 { 50 {
47 String key = InitPlugins.toJarPath(filePath); 51 String key = InitPlugins.toJarPath(filePath);
48 - if(jarMap.containsKey(key))  
49 - {  
50 - PluginsClassLoader pluginsClassLoader = jarMap.get(key);  
51 - jarMap.remove(key);  
52 - pluginsClassLoader.close(); 52 +
  53 + // 移除并关闭 ClassLoader
  54 + PluginsClassLoader pluginsClassLoader = jarMap.remove(key);
  55 + if (pluginsClassLoader != null) {
  56 + try {
  57 + pluginsClassLoader.close();
  58 + } catch (IOException e) {
  59 + log.error("Failed to close ClassLoader for JAR: " + key, e);
  60 + }
53 } 61 }
  62 +
  63 + // 移除 classMap 中的类
  64 + Iterator<Map.Entry<String, Class<?>>> it = classMap.entrySet().iterator();
  65 + while (it.hasNext()) {
  66 + Map.Entry<String, Class<?>> entry = it.next();
  67 + if (entry.getValue().getClassLoader() == pluginsClassLoader) {
  68 + it.remove();
  69 + }
  70 + }
  71 +
54 File file = new File(key); 72 File file = new File(key);
55 if(file.exists()) 73 if(file.exists())
56 { 74 {
@@ -61,6 +79,7 @@ public class PluginsClassLoader extends URLClassLoader { @@ -61,6 +79,7 @@ public class PluginsClassLoader extends URLClassLoader {
61 79
62 } 80 }
63 81
  82 +
64 /** 83 /**
65 * 更新jar 84 * 更新jar
66 * @throws IOException 85 * @throws IOException
@@ -70,11 +89,13 @@ public class PluginsClassLoader extends URLClassLoader { @@ -70,11 +89,13 @@ public class PluginsClassLoader extends URLClassLoader {
70 for (File file:filePaths) 89 for (File file:filePaths)
71 { 90 {
72 String filePath = file.getAbsolutePath(); 91 String filePath = file.getAbsolutePath();
  92 + System.out.println("绝对路径:"+filePath);
  93 +
73 unloadJar(filePath); 94 unloadJar(filePath);
74 95
75 String key = InitPlugins.toJarPath(filePath); 96 String key = InitPlugins.toJarPath(filePath);
76 97
77 - FileCopyUtils.copy(new File(filePath),new File(key)); 98 + copyFile(filePath,key);
78 99
79 PluginsClassLoader pluginsClassLoader = new PluginsClassLoader(new URL[]{new URL("file:"+key)}); 100 PluginsClassLoader pluginsClassLoader = new PluginsClassLoader(new URL[]{new URL("file:"+key)});
80 jarMap.put(key,pluginsClassLoader); 101 jarMap.put(key,pluginsClassLoader);
@@ -82,40 +103,31 @@ public class PluginsClassLoader extends URLClassLoader { @@ -82,40 +103,31 @@ public class PluginsClassLoader extends URLClassLoader {
82 laodJar(new File(key),pluginsClassLoader); 103 laodJar(new File(key),pluginsClassLoader);
83 } 104 }
84 } catch (IOException e) { 105 } catch (IOException e) {
85 - throw new RuntimeException(e); 106 + log.error("更新jar异常",e);
86 } 107 }
87 108
88 } 109 }
89 110
90 111
91 112
92 - private static void laodJar(File jarfile,ClassLoader classLoader)  
93 - {  
94 - JarFile jarFile = null;  
95 - try {  
96 - jarFile = new JarFile(jarfile); 113 + private static void laodJar(File jarfile, ClassLoader classLoader) {
  114 + try (JarFile jarFile = new JarFile(jarfile)) {
97 Enumeration<JarEntry> entries = jarFile.entries(); 115 Enumeration<JarEntry> entries = jarFile.entries();
98 - while (entries.hasMoreElements())  
99 - { 116 + while (entries.hasMoreElements()) {
100 JarEntry jarEntry = entries.nextElement(); 117 JarEntry jarEntry = entries.nextElement();
101 String entryName = jarEntry.getName(); 118 String entryName = jarEntry.getName();
102 if (entryName != null && entryName.endsWith(".class")) { 119 if (entryName != null && entryName.endsWith(".class")) {
103 entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf(".")); 120 entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
104 Class<?> clas = classLoader.loadClass(entryName); 121 Class<?> clas = classLoader.loadClass(entryName);
105 - System.out.println(ProtocolParserFactory.class.isAssignableFrom(clas));;  
106 - if(SpringUtils.isSpringBean())  
107 - { 122 + if (SpringUtils.isSpringBean()) {
108 Object object = loadBean(clas); 123 Object object = loadBean(clas);
109 } 124 }
110 - classMap.put(entryName,clas); 125 + classMap.put(entryName, clas);
111 } 126 }
112 } 127 }
113 - } catch (IOException e) {  
114 - throw new RuntimeException(e);  
115 - } catch (ClassNotFoundException e) {  
116 - throw new RuntimeException(e); 128 + } catch (IOException | ClassNotFoundException e) {
  129 + log.error("加载jar:"+jarfile.getAbsolutePath()+" 失败",e);
117 } 130 }
118 -  
119 } 131 }
120 132
121 private static <T> T loadBean(Class<T> clas) 133 private static <T> T loadBean(Class<T> clas)
@@ -181,4 +193,34 @@ public class PluginsClassLoader extends URLClassLoader { @@ -181,4 +193,34 @@ public class PluginsClassLoader extends URLClassLoader {
181 } 193 }
182 return null; 194 return null;
183 } 195 }
  196 +
  197 + public static void copyFile(String sourcePathStr, String targetPathStr) {
  198 + Path sourcePath = Paths.get(sourcePathStr);
  199 + Path targetPath = Paths.get(targetPathStr);
  200 +
  201 + // 检查源文件是否存在
  202 + if (!Files.exists(sourcePath)) {
  203 + log.error("Source file does not exist: " + sourcePath.toString());
  204 + return;
  205 + }
  206 +
  207 + // 检查目标文件所在目录是否存在,如果不存在则创建
  208 + Path targetDir = targetPath.getParent();
  209 + if (!Files.exists(targetDir)) {
  210 + try {
  211 + Files.createDirectories(targetDir);
  212 + } catch (IOException e) {
  213 + log.error("Failed to create target directory: " + targetDir.toString(),e);
  214 + return;
  215 + }
  216 + }
  217 +
  218 + // 执行文件复制
  219 + try {
  220 + Files.copy(sourcePath, targetPath);
  221 + System.out.println("File copied successfully from " + sourcePath.toString() + " to " + targetPath.toString());
  222 + } catch (IOException e) {
  223 + throw new RuntimeException("Failed to copy file from " + sourcePath.toString() + " to " + targetPath.toString(), e);
  224 + }
  225 + }
184 } 226 }
1 package com.zhonglai.luhui.device.protocol.factory.plugins; 1 package com.zhonglai.luhui.device.protocol.factory.plugins;
2 2
3 import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory; 3 import com.zhonglai.luhui.device.protocol.factory.analysis.ProtocolParserFactory;
  4 +import com.zhonglai.luhui.device.protocol.factory.config.DeviceCach;
4 import com.zhonglai.luhui.device.protocol.factory.config.PluginsClassLoader; 5 import com.zhonglai.luhui.device.protocol.factory.config.PluginsClassLoader;
  6 +import com.zhonglai.luhui.device.protocol.factory.dto.ParserDeviceHostDto;
  7 +import net.jodah.expiringmap.ExpirationListener;
  8 +import net.jodah.expiringmap.ExpirationPolicy;
  9 +import net.jodah.expiringmap.ExpiringMap;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
5 import org.springframework.context.event.ContextRefreshedEvent; 12 import org.springframework.context.event.ContextRefreshedEvent;
6 import org.springframework.context.event.EventListener; 13 import org.springframework.context.event.EventListener;
7 import org.springframework.stereotype.Component; 14 import org.springframework.stereotype.Component;
@@ -9,17 +16,29 @@ import org.springframework.stereotype.Component; @@ -9,17 +16,29 @@ import org.springframework.stereotype.Component;
9 import javax.annotation.PostConstruct; 16 import javax.annotation.PostConstruct;
10 import javax.annotation.PreDestroy; 17 import javax.annotation.PreDestroy;
11 import java.io.File; 18 import java.io.File;
  19 +import java.io.FileFilter;
12 import java.io.IOException; 20 import java.io.IOException;
13 import java.nio.file.*; 21 import java.nio.file.*;
14 import java.util.ArrayList; 22 import java.util.ArrayList;
  23 +import java.util.HashSet;
15 import java.util.List; 24 import java.util.List;
  25 +import java.util.Set;
  26 +import java.util.concurrent.TimeUnit;
16 27
17 @Component 28 @Component
18 public class FileChangeListener { 29 public class FileChangeListener {
  30 + private static Logger log = LoggerFactory.getLogger(FileChangeListener.class);
19 private WatchService watchService; 31 private WatchService watchService;
20 private Thread watcherThread; 32 private Thread watcherThread;
21 private volatile boolean running = true; 33 private volatile boolean running = true;
22 34
  35 + private static Integer time = 5;
  36 +
  37 + private static ExpiringMap<String, Boolean> upFileMap = ExpiringMap.builder().maxSize(100).expiration(time, TimeUnit.SECONDS)
  38 + .variableExpiration()
  39 + .asyncExpirationListener((ExpirationListener<String, Boolean>) (s, b) -> log.info("超时清除>>>>>>>:{} ",s))
  40 + .expirationPolicy(ExpirationPolicy.CREATED).build();
  41 +
23 public void startFileWatcher() throws IOException { 42 public void startFileWatcher() throws IOException {
24 //初始化插件 43 //初始化插件
25 InitPlugins.init(); 44 InitPlugins.init();
@@ -33,8 +52,7 @@ public class FileChangeListener { @@ -33,8 +52,7 @@ public class FileChangeListener {
33 } 52 }
34 53
35 54
36 - @EventListener(ContextRefreshedEvent.class)  
37 - public void onApplicationEvent(ContextRefreshedEvent event) throws IOException { 55 + public void onApplicationEvent() throws IOException {
38 startFileWatcher(); 56 startFileWatcher();
39 } 57 }
40 58
@@ -46,7 +64,7 @@ public class FileChangeListener { @@ -46,7 +64,7 @@ public class FileChangeListener {
46 // PS:Path必须是目录,不能是文件; 64 // PS:Path必须是目录,不能是文件;
47 // StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件 65 // StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件
48 Path path = Paths.get(InitPlugins.getPluginsPath()); 66 Path path = Paths.get(InitPlugins.getPluginsPath());
49 - path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); 67 + path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
50 } 68 }
51 69
52 private void startThread() 70 private void startThread()
@@ -59,14 +77,18 @@ public class FileChangeListener { @@ -59,14 +77,18 @@ public class FileChangeListener {
59 private void watchFileChanges() { 77 private void watchFileChanges() {
60 while (running) { 78 while (running) {
61 try { 79 try {
  80 + //记录变化的文件
62 // 获取目录的变化: 81 // 获取目录的变化:
63 // take()是一个阻塞方法,会等待监视器发出的信号才返回。 82 // take()是一个阻塞方法,会等待监视器发出的信号才返回。
64 // 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。 83 // 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。
65 // 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个; 84 // 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个;
66 WatchKey key = watchService.take(); 85 WatchKey key = watchService.take();
  86 +
  87 + //记录变化的文件
67 // 处理文件变化事件: 88 // 处理文件变化事件:
68 // key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。 89 // key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。
69 for (WatchEvent<?> event : key.pollEvents()) { 90 for (WatchEvent<?> event : key.pollEvents()) {
  91 + System.out.println(event.kind()+"事件触发"+": " + event.context());
70 // event.kind():事件类型 92 // event.kind():事件类型
71 if (event.kind() == StandardWatchEventKinds.OVERFLOW) { 93 if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
72 //事件可能lost or discarded 94 //事件可能lost or discarded
@@ -74,10 +96,28 @@ public class FileChangeListener { @@ -74,10 +96,28 @@ public class FileChangeListener {
74 } 96 }
75 // 返回触发事件的文件或目录的路径(相对路径) 97 // 返回触发事件的文件或目录的路径(相对路径)
76 Path fileName = (Path) event.context(); 98 Path fileName = (Path) event.context();
77 - System.out.println("文件更新: " + fileName);  
78 - PluginsClassLoader.uploadJar(new File(key.watchable().toString()+"/"+fileName)); 99 + Path dir = (Path) key.watchable();
  100 + Path fullPath = dir.resolve(fileName);
  101 + File file = fullPath.toFile();
  102 +
  103 + if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY)
  104 + {
  105 + if(file.exists() && file.length()>0)
  106 + {
  107 + if(!upFileMap.containsKey(fullPath.toString()))
  108 + {
  109 + PluginsClassLoader.uploadJar(file);
  110 + upFileMap.put(fullPath.toString(),true);
  111 + }
  112 + }
  113 + }
  114 + if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE)
  115 + {
  116 + PluginsClassLoader.unloadJar(file.getAbsolutePath());
  117 + }
79 } 118 }
80 119
  120 +
81 // 每次调用WatchService的take()或poll()方法时需要通过本方法重置 121 // 每次调用WatchService的take()或poll()方法时需要通过本方法重置
82 if (!key.reset()) { 122 if (!key.reset()) {
83 break; 123 break;
@@ -104,7 +144,8 @@ public class FileChangeListener { @@ -104,7 +144,8 @@ public class FileChangeListener {
104 // 关闭WatchService 144 // 关闭WatchService
105 try { 145 try {
106 if (watchService != null) { 146 if (watchService != null) {
107 - watchService.close(); 147 + watchService.
  148 + close();
108 } 149 }
109 } catch (IOException e) { 150 } catch (IOException e) {
110 e.printStackTrace(); 151 e.printStackTrace();
@@ -83,36 +83,10 @@ public class InitPlugins { @@ -83,36 +83,10 @@ public class InitPlugins {
83 public static void main(String[] args) throws MalformedURLException { 83 public static void main(String[] args) throws MalformedURLException {
84 File[] files = getJarFiles(getPluginsPath()); 84 File[] files = getJarFiles(getPluginsPath());
85 PluginsClassLoader.uploadJar(files); 85 PluginsClassLoader.uploadJar(files);
86 -// String jarFilePath = "E:\\work\\idea\\Luhui\\lh-modules\\lh-device-protocol-parser\\lh-device-xinjie\\target";  
87 -// File[] files = getJarFiles(jarFilePath);  
88 -// loaderJar(files);  
89 -// try {  
90 -// ProtocolParserFactory protocolParserFactory = getJarClass(ProtocolParserFactory.class,"com.zhonglai.luhui.device.protocol.xinjie.analysis.ProtocolParserServiceImpl");  
91 -// Topic topic = protocolParserFactory.analysisTopic("/13/jiulin/476210165B365166812345678Userdata/Json/476210165B365166812345678/pub_data");  
92 -// System.out.println(topic);  
93 -// } catch (InstantiationException e) {  
94 -// throw new RuntimeException(e);  
95 -// } catch (IllegalAccessException e) {  
96 -// throw new RuntimeException(e);  
97 -// }  
98 86
99 } 87 }
100 88
101 89
102 -// public static <T> T getJarClass(Class<T> clazz, String className) {  
103 -// try {  
104 -// Class<?> loadedClass = Class.forName(className);  
105 -// System.out.println("接口的类加载器:"+clazz.getClassLoader());  
106 -// System.out.println("实现类的类加载器:"+loadedClass.getClassLoader());  
107 -// if (clazz.isAssignableFrom(loadedClass)) {  
108 -// return clazz.cast(loadedClass.getDeclaredConstructor().newInstance());  
109 -// }  
110 -// } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {  
111 -// // Handle exceptions here  
112 -// }  
113 -// return null;  
114 -// }  
115 -  
116 public static String toJarPath(String filePath) 90 public static String toJarPath(String filePath)
117 { 91 {
118 return filePath.replace(pluginsPath,pluginsJarPath); 92 return filePath.replace(pluginsPath,pluginsJarPath);