微信图片20210624201118.png

思路

  • plugin-face模块定义插件的接口
  • plugin-applicaion模块是应用程序,插件的运行处,采用spring boot作为框架
  • plugin-ly-initializer 一个具体实现的插件

部署运行的目录架构图

|-- plugin
    |--  plugin-ly-initializer.jar
    |-- 插件2.jar
|-- plugin-applicaion.jar

微信图片20210624202605.png

plugin-applicaion加载插件的思路, 读取具体实现插件的MANIFEST.MF文件的自定义字段

package ly.project.plugin.application.plugin;

import lombok.extern.slf4j.Slf4j;
import ly.project.face.plugin.PluginInitializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

@Component
@Slf4j
public class PluginRun {
    //插件的存放路径
    @Value("${plugin.path}")
    private String pluginPath;

    //插件
    private Set<PluginInitializer> pluginInitializerSet = new HashSet<>();

    //插件运行
    public boolean run() throws FileNotFoundException {

        log.info("开始加载插件,插件的加载路径为:{}", pluginPath);
        //读取插件
        File file = ResourceUtils.getFile(pluginPath);
        log.info("是否为目录{}", file.isDirectory());
        log.info("文件的绝对路径:{}", file.getAbsolutePath());
        return Optional.ofNullable(file.listFiles()).map(pluginJars -> {

            //初始化插件
            for (File pluginJar : pluginJars) {
                if (pluginJar.getName().endsWith(".jar")) {
                    try {
                        //获取启动类
                        JarFile jarFile = new JarFile(pluginJar);
                        Manifest mf = jarFile.getManifest();
                        Attributes mainAttributes = mf.getMainAttributes();
                        String startClass = mainAttributes.getValue("Ly-Plugin-Class");
                        if (startClass == null || startClass.equals("")) {
                            //manifest中没有自定义的Ly-Plugin-Class
                            continue;
                        }

                        //创建jar包中的插件,并且添加到pluginInitializerSet里面去
                        URL[] url = new URL[1];
                        url[0] = pluginJar.toURI().toURL();
                        log.info("打印具体的插件类:{}", startClass);
                        //重点!!!!在spring boot中,类加载一定要基于spring的类加载,不然会找不到类
                        URLClassLoader urlClassLoader = new URLClassLoader(url, SpringFactoriesLoader.class.getClassLoader());
                        Class<?> aClass = urlClassLoader.loadClass(startClass);
                        Object plugin = aClass.newInstance();
                        pluginInitializerSet.add((PluginInitializer) plugin);

                    } catch (IOException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                        e.printStackTrace();
                        log.error("错误信息:{}", e.getMessage());
                    }
                }

            }


            //运行所有的插件
            log.info("来了喔来了喔,插件来正在运行了喔");
            pluginInitializerSet.forEach(pluginInitializer -> {
                pluginInitializer.onInitialize();
            });

            return true;
        }).orElse(false);
    }
}

maven父子模块

父模块

  • 父pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>ly.project</groupId>
    <artifactId>myJavaTest</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>thread</module>
        <module>plugin-ly-initializer</module>
        <module>plugin-application</module>
        <module>plugin-face</module>
    </modules>

    <!--    子模块相关依赖指定和dependencyManagement比较,parent必须一致,dependencyManagement可以特殊-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>

    <!--    utf-8编码-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!--    dependencyManagement 标签,统一子模块公共依赖的版本,如果子模块有相同的依赖,并且没有指定具体版本,则采用该dependencyManagement定义-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>ly.project</groupId>
                <artifactId>plugin-face</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

plugin-ly-initializer子模块

pom.xml文件;主要是把自定义键值对打包到MANIFEST.MF



<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>myJavaTest</artifactId>
<groupId>ly.project</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>plugin-ly-initializer</artifactId>

<dependencies>
    <dependency>
        <groupId>ly.project</groupId>
        <artifactId>plugin-face</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <!--                    //追加一下MANIFEST.MF文件的自定义键值对-->
                <archive>
                    <!--使用manifestFile属性配置自定义的参数文件所在的-->
                    <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

</project>

微信图片20210624203544.png

参考链接

总结一些由于对java不熟,发生的异常问题

问题描述: 按照这两篇参考链接,编写完之后我能够在idea运行,编译、打包、运行;但是如果打包成jar,用命令行 java -jar xxx运行的时候,会抛出ClassNotFoundException这个异常;
关于类加载器 这个链接给了我思路

关键是在于 类加载器,一开始我是使用

new URLClassLoader(url);

一直报错,plugin-applicaion采用的是spring boot,spring boot 有自己特有的类加载,改成这样就好了

new URLClassLoader(url, SpringFactoriesLoader.class.getClassLoader());

要基于spring boot的类加载就好了


标题:java开发笔记之插件化开发&类加载器
作者:liangyu
地址:HTTPS://ly.laughingzhu.cn/articles/2021/06/24/1624537518696.html