目前我想在我的 Spring 应用程序中实现一个插件系统。这个想法是有一个主要的 spring 应用程序来监视文件夹中是否有新的 jar 文件。当我将新的 jar 文件放入文件夹中时,主应用程序应该自动提升 RestController 类以供使用,而无需停机。在插件 jar 中没有主类或类似的东西。
Java Spring 是否可以在运行时启动外部 RestController 类?
我从 tsarenkotxt 找到了一个关于这个主题的不错的 存储库。抽象类是插件初始化类的“接口”。线程类来自监视目录的主应用程序。
KR, 黑玫瑰01
package eu.arrowhead.plugin.types;
import eu.arrowhead.plugin.TargetModule;
import eu.arrowhead.plugin.TargetSystem;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.ResponseEntity;
import java.util.UUID;
/**
* Interface for Plugin Startclass
*/
public abstract class IPlugin {
protected final static Logger LOG = LogManager.getLogger(IPlugin.class);
private UUID PLUGIN_ID;
protected final static String PLUGIN_NAME = "Testplugin";
protected final static String PLUGIN_DESCRIPTION = "This is a Testplugin.";
protected final static String PLUGIN_VERSION = "1";
protected final static TargetSystem PLUGIN_TARGETSYSTEM = null;
protected final static TargetModule PLUGIN_TARGETMODULE = null;
/**
* Description of the plugin
* @return
*/
public static String getPluginDescription() {
return PLUGIN_DESCRIPTION;
}
/**
* Name of the plugin
* @return
*/
public static String getPluginName() {
return PLUGIN_NAME;
}
/**
* Version of the plugin
* @return
*/
public static String getPluginVersion() {
return PLUGIN_VERSION;
}
/**
* Target System of the plugin
* @return
*/
public static TargetSystem getPluginTargetSystem() {
return PLUGIN_TARGETSYSTEM;
}
/**
* Target Module of the plugin
* @return
*/
public static TargetModule getPluginTargetModule() {
return PLUGIN_TARGETMODULE;
}
public void setId(UUID id) {
this.PLUGIN_ID = id;
}
public UUID getId() {
return this.PLUGIN_ID;
}
public static ResponseEntity restTest() {
return null;
}
public void beforeStart() {
}
public void start() {
}
public void beforeStop() {
}
public void stop() {
}
}
@Component
public class PluginLoader extends Thread {
protected final static Logger logger = LogManager.getLogger(PluginLoader.class);
@Value("${sip.integrator.plugin.dir:./plugin}")
private String pluginDirectory;
@Autowired
private RequestMappingHandlerMapping handlerMapping;
private File dir;
private boolean isFirstStart = true;
public PluginLoader() {
}
/**
* Check if given plugin directory exists, is directory and readable. If path does not
* exists than it will create the given directory. Default: ./plugin
* @return
*/
private boolean createPluginDirectory() {
if (
Files.isDirectory(Path.of(this.pluginDirectory)) &&
Files.exists(Path.of(this.pluginDirectory)) &&
Files.isReadable(Path.of(this.pluginDirectory))
)
return true;
try {
Files.createDirectory(Path.of(this.pluginDirectory),
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-r--")));
return true;
} catch (IOException e) {
logger.error("Cannot create plugin path: " + this.pluginDirectory);
e.printStackTrace();
return false;
}
}
/**
* Create a dynamic rest endpoint
* @return
*/
private Object createRestHandler(String method) throws InstantiationException, IllegalAccessException {
return new ByteBuddy()
.subclass(Object.class)
.name("Initializer")
.annotateType(AnnotationDescription.Builder
.ofType(RestController.class)
.build()
)
.defineMethod(method, ResponseEntity.class, Modifier.PUBLIC)
.intercept(MethodDelegation.to(IPlugin.class))
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance();
}
public void run() {
if (this.isFirstStart) {
this.createPluginDirectory();
this.isFirstStart = false;
}
if (this.isInterrupted())
return;
try {
WatchService watcher = dir.toPath().getFileSystem().newWatchService();
WatchKey watckKey;
List<WatchEvent<?>> events;
dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_CREATE,/*StandardWatchEventKinds
.ENTRY_DELETE,*/
StandardWatchEventKinds.ENTRY_MODIFY);
watckKey = watcher.take();
while (!this.isInterrupted()) {
events = watckKey.pollEvents();
for (WatchEvent event : events) {
if (!event.context().toString().endsWith(".jar")) {
logger.error("JUMP");
continue;
}
File f = new File(dir.getAbsolutePath() + "\\" + event.context().toString());
URLClassLoader child = new URLClassLoader(
new URL[] {f.toURI().toURL()},
this.getClass().getClassLoader()
);
logger.error("FOUND");
JarInputStream jarStream;
try {
jarStream = new JarInputStream(f.toURL().openStream());
} catch (Exception e) {
e.printStackTrace();
logger.error("NOOOOO");
continue;
}
try {
Class<?> classToLoad = Class.forName("de.sip.plugin.Initializer", true, child);
handlerMapping
.registerMapping(
RequestMappingInfo.paths("/t/a")
.methods(RequestMethod.GET)
.produces(MediaType.ALL_VALUE)
.build(),
createRestHandler("restTest"),
classToLoad.getMethod("restTest")
);
} catch (Exception e) {
e.printStackTrace();
logger.error("Cannot load plugin");
child.close();
jarStream.close();
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}