This is an automated email from the ASF dual-hosted git repository.

xiaoyu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git


The following commit(s) were added to refs/heads/main by this push:
     new 65bec05e0e Add loader ext blog (#1007)
65bec05e0e is described below

commit 65bec05e0e27c1082d1276e7baca63e84b065a80
Author: hql <[email protected]>
AuthorDate: Sun Feb 4 10:26:16 2024 +0800

    Add loader ext blog (#1007)
    
    * [DOC] add Loader SourceCode Analysis ExtLoader
    
    * [DOC] fix author title
    
    * [DOC] fix MARKDOWN
    
    * [DOC] fix markdown check
---
 .../Loader-SourceCode-Analysis-ExtLoader.md        | 300 +++++++++++++++++++++
 1 file changed, 300 insertions(+)

diff --git 
a/i18n/zh/docusaurus-plugin-content-blog/Loader-SourceCode-Analysis-ExtLoader.md
 
b/i18n/zh/docusaurus-plugin-content-blog/Loader-SourceCode-Analysis-ExtLoader.md
new file mode 100644
index 0000000000..430751e744
--- /dev/null
+++ 
b/i18n/zh/docusaurus-plugin-content-blog/Loader-SourceCode-Analysis-ExtLoader.md
@@ -0,0 +1,300 @@
+---
+title: 扩展插件加载逻辑
+author: hql0312
+author_title: hql0312 Coder
+author_url: https://github.com/hql0312
+tags: [plugin,ext,Apache ShenYu]
+---
+
+> 本文基于`shenyu-2.6.1`版本进行源码分析.
+
+# 正文
+
+Shenyu 提供了一个种机制来定制自己的插件或是修改已有的插件,在其内部通过extPlugin的配置实现,其需要满足以下两点:
+1. 实现接口 `ShenyuPlugin` 或是 `PluginDataHandler`
+2. 将实现的包打包后,放置于`shenyu.extPlugin.path`对应的路径下
+
+## 入口
+
+真正实现该逻辑的类是`ShenyuLoaderService`,接下来看下该类是如何处理
+
+```java
+    public ShenyuLoaderService(final ShenyuWebHandler webHandler, final 
CommonPluginDataSubscriber subscriber, final ShenyuConfig shenyuConfig) {
+        // 插件信息的信息订阅
+        this.subscriber = subscriber;
+        // Shenyu封装的WebHandler,包含了所有的插件逻辑
+        this.webHandler = webHandler;
+        // 配置信息
+        this.shenyuConfig = shenyuConfig;
+        // 扩展插件的配置信息,如路径,是否启用、开启多少线程来处理、检查加载的频率等信息
+        ExtPlugin config = shenyuConfig.getExtPlugin();
+        // 如果启用的,则创建定时任务来检查并加载
+        if (config.getEnabled()) {
+            // 创建一个指定线程名称的定时任务
+            ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(config.getThreads(), 
ShenyuThreadFactory.create("plugin-ext-loader", true));
+            // 创建固定频率执行的任务,默认在30s,每300s,执行一次
+            executor.scheduleAtFixedRate(() -> loadExtOrUploadPlugins(null), 
config.getScheduleDelay(), config.getScheduleTime(), TimeUnit.SECONDS);
+        }
+    }
+    
+```
+
+该类有以下几个属性:
+
+`webHandler`: 该类是shenyu 处理请求的入口,引用了所有的插件数据,在扩展插件加载后,需要进行更新
+
+`subscriber`: 该类是插件的订阅的入口,引用了所有插件的订阅处理类,在扩展配置加载后,也需要进行同步更新
+
+`executor`: 
在`ShenyuLoaderService`内部会创建一个定时任务,来定时扫描加载指定路径下的jar包,便于加载扩展的插件,实现动态发现
+默认会在启动30秒后,每300秒扫描一次
+
+同时这里可以通过 `shenyu.extPlugin.enabled`配置来决定是否要开启扩展插件功能的启用
+
+以上的配置可以在配置文件中进行调整:
+
+```yaml
+shenyu:
+  extPlugin:
+    path:   # 扩展插件的存储目录
+    enabled: true # 是否启用扩展功能
+    threads: 1 # 扫描加载的线程数
+    scheduleTime: 300 # 任务执行的频率
+    scheduleDelay: 30 # 任务启动后多久开始执行
+```
+
+接下来看下加载的逻辑:
+
+```java
+   public void loadExtOrUploadPlugins(final PluginData uploadedJarResource) {
+        try {
+            List<ShenyuLoaderResult> plugins = new ArrayList<>();
+            // 获取ShenyuPluginClassloader的持有对象
+            ShenyuPluginClassloaderHolder singleton = 
ShenyuPluginClassloaderHolder.getSingleton();
+            if (Objects.isNull(uploadedJarResource)) {
+                // 参数为空,则从扩展的目录,加载所有的jar包
+                // PluginJar:包含ShenyuPlugin接口、PluginDataHandler接口的数据
+                List<PluginJarParser.PluginJar> uploadPluginJars = 
ShenyuExtPathPluginJarLoader.loadExtendPlugins(shenyuConfig.getExtPlugin().getPath());
+                // 遍历所有的待加载插件
+                for (PluginJarParser.PluginJar extPath : uploadPluginJars) {
+                    LOG.info("shenyu extPlugin find new {} to load", 
extPath.getAbsolutePath());
+                    // 使用扩展插件的加载器来加载指定的插件,便于后续的加载和卸载
+                    ShenyuPluginClassLoader extPathClassLoader = 
singleton.createPluginClassLoader(extPath);
+                    // 使用ShenyuPluginClassLoader 进行加载
+                    // 主要逻辑是:判断是否实现ShenyuPlugin接口、PluginDataHandler接口 或是否标识 
@Component\@Service等注解,如果有,则注册为SpringBean
+                    // 构造 ShenyuLoaderResult对象
+                    
plugins.addAll(extPathClassLoader.loadUploadedJarPlugins());
+                }
+            } else {
+                // 加载指定jar,逻辑同加载全部
+                PluginJarParser.PluginJar pluginJar = 
PluginJarParser.parseJar(Base64.getDecoder().decode(uploadedJarResource.getPluginJar()));
+                LOG.info("shenyu upload plugin jar find new {} to load", 
pluginJar.getJarKey());
+                ShenyuPluginClassLoader uploadPluginClassLoader = 
singleton.createPluginClassLoader(pluginJar);
+                
plugins.addAll(uploadPluginClassLoader.loadUploadedJarPlugins());
+            }
+            // 将扩展的插件,加入到ShenyuWebHandler的插件列表,后续的请求则会经过加入的插件内容
+            loaderPlugins(plugins);
+        } catch (Exception e) {
+            LOG.error("shenyu plugins load has error ", e);
+        }
+    }
+```
+
+该方法处理的逻辑:
+1. 判断参数uploadedJarResource是否有值,如果没有,则会加载全部,否则加载指定资源jar包进行处理
+2. 从 `shenyu.extPlugin.path` 中获取到指定jar包,并封装成 PluginJar对象,该对象包含了jar包以下信息
+    - version: 版本信息
+    - groupId:包的groupId
+    - artifactId: 包的 artifactId
+    - absolutePath: 绝对路径
+    - clazzMap:class对应的字节码
+    - resourceMap:jar包的字节码
+3. 
通过`ShenyuPluginClassloaderHolder`创建对应的ClassLoader,对应的类是`ShenyuPluginClassLoader`,
 并进行加载对应的类
+    - 调用`ShenyuPluginClassLoader.loadUploadedJarPlugins` 加载对应的类并注册成Spring 
Bean,这样可以使用Spring容器来管理
+4. 调用`loaderPlugins`方法,将扩展的插件更新到 `webHandler` 以及 `subscriber`中
+
+## 插件注册
+
+对于提供的jar包里的内容,加载器只会处理指定接口类型的类,实现逻辑在 
`ShenyuPluginClassLoader.loadUploadedJarPlugins()` 方法
+
+```java
+public List<ShenyuLoaderResult> loadUploadedJarPlugins() {
+        List<ShenyuLoaderResult> results = new ArrayList<>();
+        // 所有的类映射关系
+        Set<String> names = pluginJar.getClazzMap().keySet();
+        // 遍历所有的类
+        names.forEach(className -> {
+            Object instance;
+            try {
+                // 尝试创建对象,如果可以,则加入到Spring容器中
+                instance = getOrCreateSpringBean(className);
+                if (Objects.nonNull(instance)) {
+                    // 构建ShenyuLoaderResult对象
+                    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, e);
+            }
+        });
+        return results;
+    }
+```
+
+该方法就是负责构建所有符合条件的对象,并封装成 `ShenyuLoaderResult`对象,该对象对于创建后对象,进行了封装,会在方法 
`buildResult()`中进行处理
+
+```java
+    private ShenyuLoaderResult buildResult(final Object instance) {
+        ShenyuLoaderResult result = new ShenyuLoaderResult();
+        // 创建的对象是否实现了ShenyuPlugin
+        if (instance instanceof ShenyuPlugin) {
+            result.setShenyuPlugin((ShenyuPlugin) instance);
+            // 创建的对象是否实现了PluginDataHandler
+        } else if (instance instanceof PluginDataHandler) {
+            result.setPluginDataHandler((PluginDataHandler) instance);
+        }
+        return result;
+    }
+```
+
+同时进入方法 `getOrCreateSpringBean()` 进一步分析
+
+```java
+    private <T> T getOrCreateSpringBean(final String className) throws 
ClassNotFoundException, IllegalAccessException, InstantiationException {
+        // 确认是否已经注册过了,如果有则不处理,直接返回
+        if (SpringBeanUtils.getInstance().existBean(className)) {
+            return SpringBeanUtils.getInstance().getBeanByClassName(className);
+        }
+        lock.lock();
+        try {
+            // Double check,
+            T inst = 
SpringBeanUtils.getInstance().getBeanByClassName(className);
+            if (Objects.isNull(inst)) {
+                // 使用 ShenyuPluginClassLoader 进行加载类
+                Class<?> clazz = Class.forName(className, false, this);
+                //Exclude ShenyuPlugin subclass and PluginDataHandler subclass
+                // without adding @Component @Service annotation
+                // 确认是否是 ShenyuPlugin 或是 PluginDataHandler的子类
+                boolean next = ShenyuPlugin.class.isAssignableFrom(clazz)
+                        || PluginDataHandler.class.isAssignableFrom(clazz);
+                if (!next) {
+                    // 如果不是,确认是否标识了 @Component 与 @Service 注解
+                    Annotation[] annotations = clazz.getAnnotations();
+                    next = Arrays.stream(annotations).anyMatch(e -> 
e.annotationType().equals(Component.class)
+                            || e.annotationType().equals(Service.class));
+                }
+                if (next) {
+                    // 如果符合以上内容,则注册Bean
+                    GenericBeanDefinition beanDefinition = new 
GenericBeanDefinition();
+                    beanDefinition.setBeanClassName(className);
+                    beanDefinition.setAutowireCandidate(true);
+                    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+                    // 注册bean
+                    String beanName = 
SpringBeanUtils.getInstance().registerBean(beanDefinition, this);
+                    // 创建对象
+                    inst = 
SpringBeanUtils.getInstance().getBeanByClassName(beanName);
+                }
+            }
+            return inst;
+        } finally {
+            lock.unlock();
+        }
+    }
+```
+
+逻辑大致如下:
+1. 判断是否实现了接口 `ShenyuPlugin` 或 `PluginDataHandler`, 如果没有,则是否标识了 `@Component` 或是 
`@Service`
+2. 如果符合1的条件,则将该对象注册到Spring 容器,并返回创建的对象
+
+## 同步
+
+在插件注册成功后,这时只是实例化了插件,但它还不会生效,因为它还未添加到 Shenyu的插件链中,同步逻辑由 `loaderPlugins()`方法实现
+
+```java
+    private void loaderPlugins(final List<ShenyuLoaderResult> results) {
+        if (CollectionUtils.isEmpty(results)) {
+            return;
+        }
+        // 获取所有实现了接口ShenyuPlugin的对象
+        List<ShenyuPlugin> shenyuExtendPlugins = 
results.stream().map(ShenyuLoaderResult::getShenyuPlugin).filter(Objects::nonNull).collect(Collectors.toList());
+        // 同步更新webHandler中plugins
+        webHandler.putExtPlugins(shenyuExtendPlugins);
+        // 获取所有实现了接口PluginDataHandler的对象
+        List<PluginDataHandler> handlers = 
results.stream().map(ShenyuLoaderResult::getPluginDataHandler).filter(Objects::nonNull).collect(Collectors.toList());
+        // 同步扩展的PluginDataHandler
+        subscriber.putExtendPluginDataHandler(handlers);
+
+    }
+```
+
+该方法的逻辑处理了两个数据:
+1. 将实现了 `ShenyuPlugin` 接口的数据,同步至 `webHandler`的plugins 列表
+
+```java
+    public void putExtPlugins(final List<ShenyuPlugin> extPlugins) {
+        if (CollectionUtils.isEmpty(extPlugins)) {
+            return;
+        }
+        // 过滤出新增的插件
+        final List<ShenyuPlugin> shenyuAddPlugins = extPlugins.stream()
+                .filter(e -> plugins.stream().noneMatch(plugin -> 
plugin.named().equals(e.named())))
+                .collect(Collectors.toList());
+        // 过滤出更新的插件,以名称和旧的相同来判断,则为更新
+        final List<ShenyuPlugin> shenyuUpdatePlugins = extPlugins.stream()
+                .filter(e -> plugins.stream().anyMatch(plugin -> 
plugin.named().equals(e.named())))
+                .collect(Collectors.toList());
+        // 如果没有数据,则跳过
+        if (CollectionUtils.isEmpty(shenyuAddPlugins) && 
CollectionUtils.isEmpty(shenyuUpdatePlugins)) {
+            return;
+        }
+        // 复制旧的数据
+        // copy new list
+        List<ShenyuPlugin> newPluginList = new ArrayList<>(plugins);
+        // 添加新的插件数据
+        // Add extend plugin from pluginData or shenyu ext-lib
+        this.sourcePlugins.addAll(shenyuAddPlugins);
+        // 添加新数据
+        if (CollectionUtils.isNotEmpty(shenyuAddPlugins)) {
+            shenyuAddPlugins.forEach(plugin -> LOG.info("shenyu auto add 
extends plugins:{}", plugin.named()));
+            newPluginList.addAll(shenyuAddPlugins);
+        }
+        // 修改更新的数据
+        if (CollectionUtils.isNotEmpty(shenyuUpdatePlugins)) {
+            shenyuUpdatePlugins.forEach(plugin -> LOG.info("shenyu auto update 
extends plugins:{}", plugin.named()));
+            for (ShenyuPlugin updatePlugin : shenyuUpdatePlugins) {
+                for (int i = 0; i < newPluginList.size(); i++) {
+                    if 
(newPluginList.get(i).named().equals(updatePlugin.named())) {
+                        newPluginList.set(i, updatePlugin);
+                    }
+                }
+                for (int i = 0; i < this.sourcePlugins.size(); i++) {
+                    if 
(this.sourcePlugins.get(i).named().equals(updatePlugin.named())) {
+                        this.sourcePlugins.set(i, updatePlugin);
+                    }
+                }
+            }
+        }
+        // 重新排序
+        plugins = sortPlugins(newPluginList);
+    }
+```
+
+2. 将实现了 `PluginDataHandler` 接口的数据,同步至 `subscriber` 的handlers 列表
+
+```java
+    public void putExtendPluginDataHandler(final List<PluginDataHandler> 
handlers) {
+        if (CollectionUtils.isEmpty(handlers)) {
+            return;
+        }
+        // 遍历所有数据
+        for (PluginDataHandler handler : handlers) {
+            String pluginNamed = handler.pluginNamed();
+            // 更新现有的PluginDataHandler列表
+            MapUtils.computeIfAbsent(handlerMap, pluginNamed, name -> {
+                LOG.info("shenyu auto add extends plugin data handler name is 
:{}", pluginNamed);
+                return handler;
+            });
+        }
+    }
+```
+
+至此,扩展插件的加载过程分析结束。

Reply via email to