This is an automated email from the ASF dual-hosted git repository.
mahaitao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 12a2c1929 Task[#4379] Plugin upload hot load into Shenyu (#4392)
12a2c1929 is described below
commit 12a2c1929c46f0902701c60c8b02ca14b667c21d
Author: 杨文杰 <[email protected]>
AuthorDate: Mon Mar 20 15:22:28 2023 +0800
Task[#4379] Plugin upload hot load into Shenyu (#4392)
* add jar resource in plugin table
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* support shenyu upload plugin hot load in gateway
* merge master
* trigger ci
* trigger ci
* trigger ci
* trigger ci
* trigger ci
* trigger ci
* tigger ci
* tigger ci
* tigger ci
* tigger ci
* init tcp
* trigger ci
---------
Co-authored-by: xiaoyu <[email protected]>
---
.../src/main/resources/mappers/plugin-sqlmap.xml | 2 +
.../starter/gateway/ShenyuConfiguration.java | 12 +-
.../shenyu/web/handler/ShenyuWebHandler.java | 13 +-
.../shenyu/web/loader/ShenyuLoaderService.java | 42 +++++--
.../shenyu/web/loader/ShenyuPluginLoader.java | 136 ++++++++++++++++-----
.../shenyu/web/handler/ShenyuWebHandlerTest.java | 13 +-
6 files changed, 167 insertions(+), 51 deletions(-)
diff --git a/shenyu-admin/src/main/resources/mappers/plugin-sqlmap.xml
b/shenyu-admin/src/main/resources/mappers/plugin-sqlmap.xml
index 73068a4f3..62eabc19f 100644
--- a/shenyu-admin/src/main/resources/mappers/plugin-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/plugin-sqlmap.xml
@@ -26,6 +26,7 @@
<result column="config" jdbcType="VARCHAR" property="config"/>
<result column="role" jdbcType="VARCHAR" property="role"/>
<result column="sort" jdbcType="INTEGER" property="sort"/>
+ <result column="plugin_jar" property="pluginJar"/>
<result column="enabled" jdbcType="TINYINT" property="enabled"/>
</resultMap>
@@ -37,6 +38,7 @@
config,
role,
sort,
+ plugin_jar,
enabled
</sql>
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java
index a5fb0fa25..661da790f 100644
---
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java
@@ -54,6 +54,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.WebFilter;
@@ -77,21 +78,22 @@ public class ShenyuConfiguration {
* logger.
*/
private static final Logger LOG =
LoggerFactory.getLogger(ShenyuConfiguration.class);
-
+
/**
* Init ShenyuWebHandler.
*
- * @param plugins this plugins is All impl ShenyuPlugin.
- * @param config the config
+ * @param plugins this plugins is All impl ShenyuPlugin.
+ * @param config the config
+ * @param shenyuLoaderService theLoaderServer
* @return {@linkplain ShenyuWebHandler}
*/
@Bean("webHandler")
- public ShenyuWebHandler shenyuWebHandler(final
ObjectProvider<List<ShenyuPlugin>> plugins, final ShenyuConfig config) {
+ public ShenyuWebHandler shenyuWebHandler(final
ObjectProvider<List<ShenyuPlugin>> plugins, final ShenyuConfig config, @Lazy
final ShenyuLoaderService shenyuLoaderService) {
List<ShenyuPlugin> pluginList =
plugins.getIfAvailable(Collections::emptyList);
List<ShenyuPlugin> shenyuPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(ShenyuPlugin::getOrder)).collect(Collectors.toList());
shenyuPlugins.forEach(shenyuPlugin -> LOG.info("load plugin:[{}]
[{}]", shenyuPlugin.named(), shenyuPlugin.getClass().getName()));
- return new ShenyuWebHandler(shenyuPlugins, config);
+ return new ShenyuWebHandler(shenyuPlugins, shenyuLoaderService,
config);
}
/**
diff --git
a/shenyu-web/src/main/java/org/apache/shenyu/web/handler/ShenyuWebHandler.java
b/shenyu-web/src/main/java/org/apache/shenyu/web/handler/ShenyuWebHandler.java
index fa7fa3e1b..347706444 100644
---
a/shenyu-web/src/main/java/org/apache/shenyu/web/handler/ShenyuWebHandler.java
+++
b/shenyu-web/src/main/java/org/apache/shenyu/web/handler/ShenyuWebHandler.java
@@ -18,6 +18,7 @@
package org.apache.shenyu.web.handler;
import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.common.config.ShenyuConfig;
import org.apache.shenyu.common.dto.PluginData;
import org.apache.shenyu.common.enums.PluginHandlerEventEnum;
@@ -25,6 +26,7 @@ import org.apache.shenyu.plugin.api.ShenyuPlugin;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.base.cache.BaseDataCache;
import org.apache.shenyu.plugin.base.cache.PluginHandlerEvent;
+import org.apache.shenyu.web.loader.ShenyuLoaderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
@@ -36,6 +38,7 @@ import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -60,6 +63,8 @@ public final class ShenyuWebHandler implements WebHandler,
ApplicationListener<P
*/
private final List<ShenyuPlugin> sourcePlugins;
+ private ShenyuLoaderService shenyuLoaderService;
+
private final boolean scheduled;
private Scheduler scheduler;
@@ -68,11 +73,13 @@ public final class ShenyuWebHandler implements WebHandler,
ApplicationListener<P
* Instantiates a new shenyu web handler.
*
* @param plugins the plugins
+ * @param shenyuLoaderService shenyuLoaderService
* @param shenyuConfig plugins config
*/
- public ShenyuWebHandler(final List<ShenyuPlugin> plugins, final
ShenyuConfig shenyuConfig) {
+ public ShenyuWebHandler(final List<ShenyuPlugin> plugins, final
ShenyuLoaderService shenyuLoaderService, final ShenyuConfig shenyuConfig) {
this.sourcePlugins = new ArrayList<>(plugins);
this.plugins = new ArrayList<>(plugins);
+ this.shenyuLoaderService = shenyuLoaderService;
ShenyuConfig.Scheduler config = shenyuConfig.getScheduler();
this.scheduled = config.getEnabled();
if (scheduled) {
@@ -168,6 +175,10 @@ public final class ShenyuWebHandler implements WebHandler,
ApplicationListener<P
*/
private synchronized void onPluginEnabled(final PluginData pluginData) {
LOG.info("shenyu use plugin:[{}]", pluginData.getName());
+ if (StringUtils.isNoneBlank(pluginData.getPluginJar())) {
+ LOG.info("shenyu start load plugin [{}] from upload plugin jar",
pluginData.getName());
+
shenyuLoaderService.loadUploadedJarPlugins(Collections.singletonList(pluginData.getPluginJar()));
+ }
final List<ShenyuPlugin> enabledPlugins =
this.sourcePlugins.stream().filter(plugin ->
plugin.named().equals(pluginData.getName())
&& pluginData.getEnabled()).collect(Collectors.toList());
enabledPlugins.removeAll(this.plugins);
diff --git
a/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuLoaderService.java
b/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuLoaderService.java
index 3e67ec4da..4ace8b402 100644
---
a/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuLoaderService.java
+++
b/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuLoaderService.java
@@ -64,19 +64,43 @@ public class ShenyuLoaderService {
executor.scheduleAtFixedRate(this::loaderExtPlugins,
config.getScheduleDelay(), config.getScheduleTime(), TimeUnit.SECONDS);
}
}
-
+
private void loaderExtPlugins() {
try {
- List<ShenyuLoaderResult> results =
ShenyuPluginLoader.getInstance().loadExtendPlugins(shenyuConfig.getExtPlugin().getPath());
- if (CollectionUtils.isEmpty(results)) {
- return;
- }
- List<ShenyuPlugin> shenyuExtendPlugins =
results.stream().map(ShenyuLoaderResult::getShenyuPlugin).filter(Objects::nonNull).collect(Collectors.toList());
- webHandler.putExtPlugins(shenyuExtendPlugins);
- List<PluginDataHandler> handlers =
results.stream().map(ShenyuLoaderResult::getPluginDataHandler).filter(Objects::nonNull).collect(Collectors.toList());
- subscriber.putExtendPluginDataHandler(handlers);
+ List<ShenyuLoaderResult> extendPlugins =
ShenyuPluginLoader.getInstance().loadExtendPlugins(shenyuConfig.getExtPlugin().getPath());
+ loaderPlugins(extendPlugins);
+ } catch (Exception e) {
+ LOG.error("shenyu ext plugins load has error ", e);
+ }
+ }
+
+ /**
+ * loadUploadedJarPlugins.
+ *
+ * @param uploadedJarResources uploadedJarResources
+ */
+ public void loadUploadedJarPlugins(final List<String>
uploadedJarResources) {
+ try {
+ List<ShenyuLoaderResult> extendPlugins =
ShenyuPluginLoader.getInstance().loadUploadedJarPlugins(uploadedJarResources);
+ loaderPlugins(extendPlugins);
} catch (Exception e) {
LOG.error("shenyu ext plugins load has error ", e);
}
}
+
+ /**
+ * loaderPlugins.
+ *
+ * @param results results
+ */
+ private void loaderPlugins(final List<ShenyuLoaderResult> results) {
+ if (CollectionUtils.isEmpty(results)) {
+ return;
+ }
+ List<ShenyuPlugin> shenyuExtendPlugins =
results.stream().map(ShenyuLoaderResult::getShenyuPlugin).filter(Objects::nonNull).collect(Collectors.toList());
+ webHandler.putExtPlugins(shenyuExtendPlugins);
+ List<PluginDataHandler> handlers =
results.stream().map(ShenyuLoaderResult::getPluginDataHandler).filter(Objects::nonNull).collect(Collectors.toList());
+ subscriber.putExtendPluginDataHandler(handlers);
+ }
+
}
diff --git
a/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuPluginLoader.java
b/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuPluginLoader.java
index 676687081..0b254a809 100644
---
a/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuPluginLoader.java
+++
b/shenyu-web/src/main/java/org/apache/shenyu/web/loader/ShenyuPluginLoader.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.web.loader;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
+import org.apache.shenyu.common.exception.ShenyuException;
import org.apache.shenyu.plugin.api.ShenyuPlugin;
import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
@@ -30,6 +31,8 @@ import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -38,6 +41,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
@@ -50,34 +54,38 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
/**
* Shenyu Plugin loader.
*/
public final class ShenyuPluginLoader extends ClassLoader implements Closeable
{
-
+
private static final Logger LOG =
LoggerFactory.getLogger(ShenyuPluginLoader.class);
-
+
static {
registerAsParallelCapable();
}
-
+
private static volatile ShenyuPluginLoader pluginLoader;
-
+
private final ReentrantLock lock = new ReentrantLock();
-
+
private final List<PluginJar> jars = Lists.newArrayList();
-
+
private final Set<String> names = new HashSet<>();
-
+
private final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
-
+
+ private final Map<String, byte[]> uploadedJarClassByteArrayCache = new
ConcurrentHashMap<>();
+
private ShenyuPluginLoader() {
super(ShenyuPluginLoader.class.getClassLoader());
}
-
+
/**
* Get plugin loader instance.
*
@@ -93,13 +101,13 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
}
return pluginLoader;
}
-
+
/**
* Load extend plugins list.
*
* @param path the path
* @return the list
- * @throws IOException the io exception
+ * @throws IOException the io exception
*/
public List<ShenyuLoaderResult> loadExtendPlugins(final String path)
throws IOException {
File[] jarFiles =
ShenyuPluginPathBuilder.getPluginFile(path).listFiles(file ->
file.getName().endsWith(".jar"));
@@ -144,7 +152,63 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
});
return results;
}
-
+
+ /**
+ * loadUploadedJarResourcesList.
+ *
+ * @param loadUploadedJarResources loadUploadedJarResources
+ * @return the list
+ */
+ public List<ShenyuLoaderResult> loadUploadedJarPlugins(final List<String>
loadUploadedJarResources) {
+ List<byte[]> jarByteArrayList =
loadUploadedJarResources.stream().map(loadUploadedJarResourceStr ->
Base64.getDecoder().decode(loadUploadedJarResourceStr)).collect(Collectors.toList());
+ for (byte[] jarByteArray : jarByteArrayList) {
+ parserJar(jarByteArray);
+ }
+ List<ShenyuLoaderResult> results = new ArrayList<>();
+ names.forEach(className -> {
+ Object instance;
+ try {
+ instance = getOrCreateSpringBean(className);
+ if (Objects.nonNull(instance)) {
+ results.add(buildResult(instance));
+ LOG.info("The class successfully loaded into a
upload-Jar-plugin {} is registered as a spring bean", className);
+ }
+ } catch (ClassNotFoundException | IllegalAccessException |
InstantiationException e) {
+ LOG.warn("Registering upload-Jar-plugins succeeds spring bean
fails:{}", className);
+ }
+ });
+ return results;
+ }
+
+ /**
+ * parserJar.
+ *
+ * @param jarBytes jarBytes
+ */
+ private void parserJar(final byte[] jarBytes) {
+ try (JarInputStream jarInputStream = new JarInputStream(new
ByteArrayInputStream(jarBytes))) {
+ JarEntry jarEntry;
+ while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
+ String entryName = jarEntry.getName();
+ if (!jarEntry.isDirectory() && entryName.endsWith(".class") &&
!entryName.contains("$")) {
+ String className = jarEntry.getName().substring(0,
entryName.length() - 6).replaceAll("/", ".");
+ try (ByteArrayOutputStream buffer = new
ByteArrayOutputStream()) {
+ int data;
+ while ((data = jarInputStream.read()) != -1) {
+ buffer.write(data);
+ }
+ buffer.flush();
+ byte[] classByteArray = buffer.toByteArray();
+ names.add(className);
+ uploadedJarClassByteArrayCache.put(className,
classByteArray);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new ShenyuException("load jar classes find error");
+ }
+ }
+
@Override
protected Class<?> findClass(final String name) throws
ClassNotFoundException {
if (ability(name)) {
@@ -157,22 +221,30 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
synchronized (this) {
clazz = classCache.get(name);
if (clazz == null) {
- String path = classNameToPath(name);
- for (PluginJar each : jars) {
- ZipEntry entry = each.jarFile.getEntry(path);
- if (Objects.nonNull(entry)) {
- try {
- int index = name.lastIndexOf('.');
- if (index != -1) {
- String packageName = name.substring(0, index);
- definePackageInternal(packageName,
each.jarFile.getManifest());
+ // support base64Jar
+ if (uploadedJarClassByteArrayCache.containsKey(name)) {
+ byte[] currClazzByteArray =
uploadedJarClassByteArrayCache.remove(name);
+ clazz = defineClass(name, currClazzByteArray, 0,
currClazzByteArray.length);
+ classCache.put(name, clazz);
+ return clazz;
+ } else {
+ String path = classNameToPath(name);
+ for (PluginJar each : jars) {
+ ZipEntry entry = each.jarFile.getEntry(path);
+ if (Objects.nonNull(entry)) {
+ try {
+ int index = name.lastIndexOf('.');
+ if (index != -1) {
+ String packageName = name.substring(0,
index);
+ definePackageInternal(packageName,
each.jarFile.getManifest());
+ }
+ byte[] data =
ByteStreams.toByteArray(each.jarFile.getInputStream(entry));
+ clazz = defineClass(name, data, 0,
data.length);
+ classCache.put(name, clazz);
+ return clazz;
+ } catch (final IOException ex) {
+ LOG.error("Failed to load class {}.", name,
ex);
}
- byte[] data =
ByteStreams.toByteArray(each.jarFile.getInputStream(entry));
- clazz = defineClass(name, data, 0, data.length);
- classCache.put(name, clazz);
- return clazz;
- } catch (final IOException ex) {
- LOG.error("Failed to load class {}.", name, ex);
}
}
}
@@ -180,7 +252,7 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
}
throw new ClassNotFoundException(String.format("Class name is %s not
found.", name));
}
-
+
@Override
protected Enumeration<URL> findResources(final String name) throws
IOException {
if (ability(name)) {
@@ -198,7 +270,7 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
}
return Collections.enumeration(resources);
}
-
+
@Override
protected URL findResource(final String name) {
if (ability(name)) {
@@ -215,7 +287,7 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
}
return null;
}
-
+
@Override
public void close() {
for (PluginJar each : jars) {
@@ -226,7 +298,7 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
}
}
}
-
+
private <T> T getOrCreateSpringBean(final String className) throws
ClassNotFoundException, IllegalAccessException, InstantiationException {
if (SpringBeanUtils.getInstance().existBean(className)) {
return SpringBeanUtils.getInstance().getBeanByClassName(className);
@@ -259,7 +331,7 @@ public final class ShenyuPluginLoader extends ClassLoader
implements Closeable {
lock.unlock();
}
}
-
+
private ShenyuLoaderResult buildResult(final Object instance) {
ShenyuLoaderResult result = new ShenyuLoaderResult();
if (instance instanceof ShenyuPlugin) {
diff --git
a/shenyu-web/src/test/java/org/apache/shenyu/web/handler/ShenyuWebHandlerTest.java
b/shenyu-web/src/test/java/org/apache/shenyu/web/handler/ShenyuWebHandlerTest.java
index 1cfe2ba7d..bc720c905 100644
---
a/shenyu-web/src/test/java/org/apache/shenyu/web/handler/ShenyuWebHandlerTest.java
+++
b/shenyu-web/src/test/java/org/apache/shenyu/web/handler/ShenyuWebHandlerTest.java
@@ -26,6 +26,7 @@ import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.api.context.ShenyuContext;
import org.apache.shenyu.plugin.base.cache.BaseDataCache;
import org.apache.shenyu.plugin.base.cache.PluginHandlerEvent;
+import org.apache.shenyu.web.loader.ShenyuLoaderService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -57,7 +58,9 @@ public final class ShenyuWebHandlerTest {
@Mock
private ShenyuWebHandler shenyuWebHandler;
-
+
+ private ShenyuLoaderService shenyuLoaderService;
+
private final List<ShenyuPlugin> listPlugins = new ArrayList<>();
private final ShenyuPlugin plugin1 = new TestPlugin1();
@@ -68,7 +71,8 @@ public final class ShenyuWebHandlerTest {
public void setUp() {
listPlugins.add(plugin1);
listPlugins.add(plugin2);
- shenyuWebHandler = new ShenyuWebHandler(listPlugins, new
ShenyuConfig());
+ shenyuLoaderService = mock(ShenyuLoaderService.class);
+ shenyuWebHandler = new ShenyuWebHandler(listPlugins,
shenyuLoaderService, new ShenyuConfig());
}
@Test
@@ -80,6 +84,7 @@ public final class ShenyuWebHandlerTest {
exchange.getAttributes().put(Constants.PARAM_TRANSFORM, "{key:value}");
Mono<Void> handle = shenyuWebHandler.handle(exchange);
StepVerifier.create(handle).expectSubscription().verifyComplete();
+
}
@Test
@@ -96,11 +101,11 @@ public final class ShenyuWebHandlerTest {
.build());
ShenyuConfig shenyuConfig = new ShenyuConfig();
shenyuConfig.getScheduler().setEnabled(true);
- ShenyuWebHandler shenyuWebHandler1 = new ShenyuWebHandler(listPlugins,
shenyuConfig);
+ ShenyuWebHandler shenyuWebHandler1 = new ShenyuWebHandler(listPlugins,
shenyuLoaderService, shenyuConfig);
Mono<Void> handle = shenyuWebHandler1.handle(exchange);
assertNotNull(handle);
shenyuConfig.getScheduler().setType("elastic");
- ShenyuWebHandler shenyuWebHandler2 = new ShenyuWebHandler(listPlugins,
shenyuConfig);
+ ShenyuWebHandler shenyuWebHandler2 = new ShenyuWebHandler(listPlugins,
shenyuLoaderService, shenyuConfig);
Mono<Void> handle2 = shenyuWebHandler2.handle(exchange);
assertNotNull(handle2);
}