Skip to content

Commit 5faebfb

Browse files
committed
newui: 实现在独立的 ClassLoader 中运行插件
1 parent 971cefd commit 5faebfb

7 files changed

Lines changed: 185 additions & 12 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.xwintop.xJavaFxTool;
2+
3+
public class AppException extends RuntimeException {
4+
5+
public AppException() {
6+
}
7+
8+
public AppException(String message) {
9+
super(message);
10+
}
11+
12+
public AppException(String message, Throwable cause) {
13+
super(message, cause);
14+
}
15+
16+
public AppException(Throwable cause) {
17+
super(cause);
18+
}
19+
}

src/main/java/com/xwintop/xJavaFxTool/newui/NewLauncherService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void loadPlugin(PluginJarInfo pluginJarInfo) {
5151
}
5252
}
5353

54-
Tab tab = PluginLoader.loadPluginAsTab(pluginJarInfo, tabPane);
54+
Tab tab = PluginLoader.loadIsolatedPluginAsTab(pluginJarInfo, tabPane);
5555
if (tab != null) {
5656
tab.setOnClosed(event -> this.jarInfoMap.remove(tab));
5757
jarInfoMap.put(tab, pluginJarInfo);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.xwintop.xJavaFxTool.plugin;
2+
3+
import java.io.File;
4+
import java.net.MalformedURLException;
5+
import java.net.URL;
6+
import java.net.URLClassLoader;
7+
8+
/**
9+
* 用于加载插件的 ClassLoader
10+
*/
11+
public class PluginClassLoader extends URLClassLoader {
12+
13+
private static URL fromFile(File file) {
14+
try {
15+
return file.toURI().toURL();
16+
} catch (MalformedURLException e) {
17+
throw new RuntimeException(e);
18+
}
19+
}
20+
21+
public PluginClassLoader(ClassLoader parent, File appFile) {
22+
super(new URL[]{fromFile(appFile)}, parent);
23+
}
24+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.xwintop.xJavaFxTool.plugin;
2+
3+
import com.xwintop.xJavaFxTool.AppException;
4+
import com.xwintop.xJavaFxTool.model.PluginJarInfo;
5+
import com.xwintop.xJavaFxTool.utils.Config;
6+
import com.xwintop.xcore.javafx.dialog.FxAlerts;
7+
import java.io.IOException;
8+
import java.net.URL;
9+
import java.util.ResourceBundle;
10+
import javafx.fxml.FXMLLoader;
11+
import org.apache.commons.lang3.StringUtils;
12+
13+
/**
14+
* 插件运行容器,在单独的 ClassLoader 中运行插件,防止不同插件依赖库冲突
15+
*/
16+
public class PluginContainer {
17+
18+
private final ClassLoader parentClassLoader;
19+
20+
private PluginClassLoader pluginClassLoader;
21+
22+
private final PluginJarInfo pluginJarInfo;
23+
24+
public PluginContainer(ClassLoader parentClassLoader, PluginJarInfo pluginJarInfo) {
25+
this.parentClassLoader = parentClassLoader;
26+
this.pluginJarInfo = pluginJarInfo;
27+
}
28+
29+
/**
30+
* 从 ClassLoader 中创建 FXMLLoader 对象
31+
*/
32+
public FXMLLoader createFXMLLoader() {
33+
try {
34+
pluginClassLoader = new PluginClassLoader(parentClassLoader, pluginJarInfo.getFile());
35+
FXMLLoader pluginFxmlLoader = (FXMLLoader)
36+
pluginClassLoader.loadClass("javafx.fxml.FXMLLoader").newInstance();
37+
38+
// 必须去掉前缀的 "/" 才能正确读取
39+
String fxmlPathFixed = StringUtils.removeStart(pluginJarInfo.getFxmlPath(), "/");
40+
41+
URL resource = pluginClassLoader.getResource(fxmlPathFixed);
42+
if (resource == null) {
43+
FxAlerts.error("加载失败", "无法在插件 " + pluginJarInfo.getFile().getName() +
44+
" 内找到所需资源 " + pluginJarInfo.getFxmlPath());
45+
return null;
46+
}
47+
48+
pluginFxmlLoader.setLocation(resource);
49+
50+
if (StringUtils.isNotEmpty(pluginJarInfo.getBundleName())) {
51+
ResourceBundle resourceBundle = ResourceBundle
52+
.getBundle(pluginJarInfo.getBundleName(), Config.defaultLocale, pluginClassLoader);
53+
pluginFxmlLoader.setResources(resourceBundle);
54+
}
55+
return pluginFxmlLoader;
56+
} catch (Exception e) {
57+
throw new AppException(e);
58+
}
59+
}
60+
61+
/**
62+
* 关闭 ClassLoader,不再加载新的类或资源。当其加载的所有对象都可以被回收时,ClassLoader 自身也可以被回收
63+
*/
64+
public void unload() {
65+
try {
66+
this.pluginClassLoader.close();
67+
} catch (IOException e) {
68+
throw new AppException(e);
69+
}
70+
}
71+
}

src/main/java/com/xwintop/xJavaFxTool/plugin/PluginLoader.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
@Slf4j
1818
public class PluginLoader {
1919

20+
/**
21+
* 以新窗口方式打开插件
22+
*/
2023
public static void loadPluginAsWindow(PluginJarInfo plugin) {
2124
try {
2225
FXMLLoader generatingCodeFXMLLoader = new FXMLLoader(PluginLoader.class.getResource(plugin.getFxmlPath()));
@@ -32,6 +35,9 @@ public static void loadPluginAsWindow(PluginJarInfo plugin) {
3235
}
3336
}
3437

38+
/**
39+
* 以新 Tab 方式打开插件
40+
*/
3541
public static Tab loadPluginAsTab(PluginJarInfo plugin, TabPane tabPane) {
3642
try {
3743
URL resource = PluginLoader.class.getResource(plugin.getFxmlPath());
@@ -72,4 +78,50 @@ public static Tab loadPluginAsTab(PluginJarInfo plugin, TabPane tabPane) {
7278
return null;
7379
}
7480

81+
/**
82+
* 以新 Tab 方式打开插件,但使用独立的 ClassLoader
83+
*/
84+
public static Tab loadIsolatedPluginAsTab(PluginJarInfo plugin, TabPane tabPane) {
85+
try {
86+
URL resource = PluginLoader.class.getResource(plugin.getFxmlPath());
87+
if (resource == null) {
88+
FxAlerts.error("加载插件失败", "无法读取资源文件 '" + plugin.getFxmlPath() + "'");
89+
return null;
90+
}
91+
92+
Tab tab = new Tab(plugin.getTitle());
93+
94+
if (StringUtils.isNotEmpty(plugin.getIconPath())) {
95+
ImageView imageView = new ImageView(new Image(plugin.getIconPath()));
96+
imageView.setFitHeight(18);
97+
imageView.setFitWidth(18);
98+
tab.setGraphic(imageView);
99+
}
100+
101+
PluginContainer pluginContainer = new PluginContainer(PluginLoader.class.getClassLoader(), plugin);
102+
FXMLLoader generatingCodeFXMLLoader = pluginContainer.createFXMLLoader();
103+
if (generatingCodeFXMLLoader == null) {
104+
return null;
105+
}
106+
107+
tab.setContent(generatingCodeFXMLLoader.load());
108+
tabPane.getTabs().add(tab);
109+
tabPane.getSelectionModel().select(tab);
110+
111+
tab.setOnCloseRequest(
112+
event -> {
113+
JavaFxViewUtil.setControllerOnCloseRequest(generatingCodeFXMLLoader.getController(), event);
114+
pluginContainer.unload();
115+
}
116+
);
117+
118+
return tab;
119+
} catch (Exception e) {
120+
log.error("加载插件失败", e);
121+
FxAlerts.error("插件加载失败", e);
122+
}
123+
124+
return null;
125+
}
126+
75127
}

src/main/java/com/xwintop/xJavaFxTool/plugin/PluginManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public PluginJarInfo getPlugin(String jarName) {
6060
.findFirst().orElse(null);
6161
}
6262

63+
public PluginJarInfo getPluginByFxmlPath(String fxmlPath) {
64+
return this.pluginList.stream()
65+
.filter(plugin -> Objects.equals(plugin.getFxmlPath(), fxmlPath))
66+
.findFirst().orElse(null);
67+
}
68+
6369
////////////////////////////////////////////////////////////// 插件列表
6470

6571
/**

src/main/java/com/xwintop/xJavaFxTool/services/IndexService.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.xwintop.xJavaFxTool.controller.IndexController;
55
import com.xwintop.xJavaFxTool.model.PluginJarInfo;
66
import com.xwintop.xJavaFxTool.plugin.PluginLoader;
7+
import com.xwintop.xJavaFxTool.plugin.PluginManager;
78
import com.xwintop.xJavaFxTool.utils.Config;
89
import com.xwintop.xJavaFxTool.utils.FxmlUtils;
910
import com.xwintop.xcore.javafx.dialog.FxAlerts;
@@ -27,6 +28,7 @@
2728

2829
@Setter
2930
public class IndexService {
31+
3032
private IndexController indexController;
3133

3234
public IndexService(IndexController indexController) {
@@ -77,7 +79,8 @@ public void addLogConsoleAction(ActionEvent event) {
7779
textArea.setFocusTraversable(true);
7880
ConsoleLogAppender.textAreaList.add(textArea);
7981
if (indexController.getSingleWindowBootCheckBox().isSelected()) {
80-
Stage newStage = JavaFxViewUtil.getNewStage(indexController.getBundle().getString("addLogConsole"), null, textArea);
82+
Stage newStage = JavaFxViewUtil
83+
.getNewStage(indexController.getBundle().getString("addLogConsole"), null, textArea);
8184
newStage.setOnCloseRequest(event1 -> {
8285
ConsoleLogAppender.textAreaList.remove(textArea);
8386
});
@@ -95,21 +98,19 @@ public void addLogConsoleAction(ActionEvent event) {
9598
}
9699

97100
/**
98-
* @Title: addContent
99-
* @Description: 添加Content内容
101+
* 添加Content内容
100102
*/
101-
public void addContent(String title, String url, String resourceBundleName, String iconPath) {
103+
public void addContent(String title, String fxmlPath, String resourceBundleName, String iconPath) {
102104

103-
PluginJarInfo plugin = new PluginJarInfo();
104-
plugin.setTitle(title);
105-
plugin.setFxmlPath(url);
106-
plugin.setBundleName(resourceBundleName);
107-
plugin.setIconPath(iconPath);
105+
PluginJarInfo pluginJarInfo = PluginManager.getInstance().getPluginByFxmlPath(fxmlPath);
106+
if (pluginJarInfo == null) {
107+
FxAlerts.error("打开失败", "没有找到指定的插件");
108+
}
108109

109110
if (indexController.getSingleWindowBootCheckBox().isSelected()) {
110-
PluginLoader.loadPluginAsWindow(plugin);
111+
PluginLoader.loadPluginAsWindow(pluginJarInfo);
111112
} else {
112-
PluginLoader.loadPluginAsTab(plugin, indexController.getTabPaneMain());
113+
PluginLoader.loadPluginAsTab(pluginJarInfo, indexController.getTabPaneMain());
113114
}
114115
}
115116

0 commit comments

Comments
 (0)