再接再厉,让插件真的像个正儿八经的插件

插件中的代码不可能都是手写的,本文将再接再厉,让插件可以依赖自己专属的3方库,当插件与业务代码依赖同一个名称的3方库时不会出现冲突情况。
注:本文将测试名为commons-lang3第3方库分别在插件与业务代码共存不同版本的case

插件目录说明

classes:插件核心代码
lib:插件所依赖的3方库
.
├── alioo-container-plugin-x1-1.0-SNAPSHOT-fat
│   ├── classes
│   │   └── com
│   │       └── lzc
│   │           └── alioo
│   │               └── container
│   │                   └── plugin
│   │                       └── x1
│   │                           ├── HelloOneModel.class
│   │                           └── HelloOneService.class
│   └── lib
│       ├── commons-lang3-3.10.jar
│       └── fastjson-1.2.83.jar

为了实现这个插件目录结构,尝试过一些maven自带的插件比如maven-assembly-plugin 和 maven-shade-plugin ,发现第1个需要额外的配置文件,第2个不能满足需求,它是直接打到一个jar中的
故自己实现了一个maven plugin的编写工作

自定义maven plugin

groupId:com.alioo.maven
artifactId:alioo-boot-maven-plugin

将插件绑定到package阶段,当执行mvn package时会自动触发此插件的运行

package com.alioo.maven;

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Build;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

@Mojo(name = "run", defaultPhase = LifecyclePhase.PACKAGE)
public class FatJarMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;


    public void execute() throws MojoExecutionException {
        try {
            getLog().info("Fat JAR(@alioo-boot-maven-plugin) project:" + project);
            getLog().info("Fat JAR(@alioo-boot-maven-plugin) project.getBuild():" + toString(project.getBuild()));
            getLog().info("Fat JAR(@alioo-boot-maven-plugin) project.getDependencies():" + project.getDependencies());

            String buildDirectory = project.getBuild().getDirectory();

            //创建plugin.properties

            File classesDir = new File(buildDirectory, "/classes");
            File libDir = new File(buildDirectory, "/lib");

            if (!libDir.exists()) {
                libDir.mkdirs();
            }

            for (Object artifactObj : project.getDependencyArtifacts()) {
                Artifact artifact = (Artifact) artifactObj;
                if (artifact.getScope() != null && artifact.getScope().equals("provided")) {
                    continue;
                }
                File sourceFile = new File(artifact.getFile().getAbsolutePath());
                //如果依赖的jar包是system类型,则直接将依赖的jar包添加到fatJar中的lib目录下
                getLog().info("Fat JAR(@alioo-boot-maven-plugin) dependencyArtifactPath =>" + sourceFile.getAbsolutePath());
                FileUtils.copyFile(sourceFile, new File(libDir, sourceFile.getName()));
            }

            // Prepare output jar file name
            File fatJar = new File(buildDirectory, project.getBuild().getFinalName() + "-fat.jar");
            JarOutputStream jos = new JarOutputStream(new FileOutputStream(fatJar));

            // 创建 classes 目录条目
            addDir(jos, "classes/");
            // 创建 lib 目录条目
            addDir(jos, "lib/");


            // Add project classes
            addClasses(jos, classesDir, "");

            for (File dependencyJar : libDir.listFiles()) {
                addJar(jos, dependencyJar);
            }
            jos.close();
        } catch (Exception e) {
            throw new MojoExecutionException("Error assembling fat jar", e);
        }
    }

    private void addClasses(JarOutputStream jos, File source, String prefix) throws IOException {
        if (source.isDirectory()) {
            for (File file : FileUtils.listFiles(source, null, true)) {
                String entryName = prefix + source.toURI().relativize(file.toURI()).getPath();
                if (file.isFile()) {
                    jos.putNextEntry(new JarEntry("classes/" + entryName));
                    FileUtils.copyFile(file, jos);
                    jos.closeEntry();
                }
            }
        }
    }

    private void addJar(JarOutputStream jos, File jarFile) throws IOException {
        JarEntry jarEntry = new JarEntry("lib/" + jarFile.getName());
        jos.putNextEntry(jarEntry);
        FileUtils.copyFile(jarFile, jos);
        jos.closeEntry();
    }

    private static void addDir(JarOutputStream jos, String dir) throws IOException {
        JarEntry libDirEntry = new JarEntry(dir);
        jos.putNextEntry(libDirEntry);
        jos.closeEntry();
    }

    public static String toString(Build build) {
        //采用json的风格,输出入参对象的各个属性
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"directory\":\"").append(build.getDirectory()).append("\",");
        sb.append("\"sourceDirectory\":\"").append(build.getSourceDirectory()).append("\",");
        sb.append("\"testSourceDirectory\":\"").append(build.getTestSourceDirectory()).append("\",");
        sb.append("\"outputDirectory\":\"").append(build.getOutputDirectory()).append("\",");
        sb.append("\"testOutputDirectory\":\"").append(build.getTestOutputDirectory()).append("\",");
        sb.append("\"extensions\":\"").append(build.getExtensions()).append("\",");
        sb.append("\"defaultGoal\":\"").append(build.getDefaultGoal()).append("\",");
        sb.append("\"resources\":\"").append(build.getResources()).append("\",");
        sb.append("\"testResources\":\"").append(build.getTestResources()).append("\",");
        sb.append("\"finalName\":\"").append(build.getFinalName()).append("\",");
        sb.append("\"filters\":\"").append(build.getFilters()).append("\"");
        sb.append("}");

        return sb.toString();


    }
}
<?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>com.alioo.maven</groupId>
    <artifactId>alioo-boot-maven-plugin</artifactId>
    <version>1.1-SNAPSHOT</version>
    <packaging>maven-plugin</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.8.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.6.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-project</artifactId>
            <version>3.0-alpha-1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.16.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>descriptor</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

升级业务插件,采用alioo-boot-maven-plugin进行打包

<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>com.lzc.alioo.container.plugin</groupId>
    <artifactId>alioo-container-plugin-x1</artifactId>
    <version>1.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>alioo-container-plugin-x1</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alioo.maven</groupId>
            <artifactId>alioo-boot-maven-plugin</artifactId>
            <version>1.1-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.alioo.maven</groupId>
                <artifactId>alioo-boot-maven-plugin</artifactId>
                <version>1.1-SNAPSHOT</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>


</project>

升级业务容器

新增JarExtractor,自动完成业务插件解压动作

package com.lzc.alioo.container;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class JarExtractor {

    public static void main(String[] args) {
        // test case
        String jarFilePath = "/Users/mac/alioo-plugin/alioo-container-plugin-x1-1.0-SNAPSHOT-fat.jar"; // 替换为你的JAR文件路径
        try {
            extractJar(jarFilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String extractJar(String jarFilePath) throws IOException {
        String destDir = jarFilePath.substring(0, jarFilePath.lastIndexOf("."));
        extractJar(jarFilePath, destDir);
        return destDir;
    }

    public static void extractJar(String jarFilePath, String destDir) throws IOException {
        File dir = new File(destDir);
        if (dir.exists()) {
            dir.delete();
        }
        dir.mkdirs();

        try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath))) {
            JarEntry entry;

            while ((entry = jarIn.getNextJarEntry()) != null) {
                File file = new File(destDir, entry.getName());

                if (entry.isDirectory()) {
                    file.mkdirs();
                } else {
                    File parent = file.getParentFile();
                    if (parent != null) {
                        parent.mkdirs();
                    }

                    try (FileOutputStream out = new FileOutputStream(file)) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = jarIn.read(buffer)) > 0) {
                            out.write(buffer, 0, len);
                        }
                    }
                }

                jarIn.closeEntry();
            }
        }
    }
}

升级PluginClassLoader,支持识别这种新的业务插件结构

package com.lzc.alioo.container;


import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Slf4j
public class PluginClassLoader extends URLClassLoader {
    private static String pluginDir = null;

    private PluginClassLoader(String module, URL[] urlPath) {
        super(module, urlPath, ClassLoader.getSystemClassLoader());

    }

    public static PluginClassLoader init(String module, File pluginFile) {
        URL[] urlPath = new URL[0];
        try {
            pluginDir = JarExtractor.extractJar(pluginFile.getAbsolutePath());
            urlPath = new URL[]{pluginFile.toURI().toURL()};
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        PluginClassLoader instance = new PluginClassLoader(module, urlPath);
        System.out.println("found alioo plugin by pluginPath:" + pluginFile.getAbsolutePath());
        return instance;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        //对于import的类优先使用lzcClassLoader进行统一加载

        clazz = loadClassData(name);
        if (clazz != null) {
            log.info("Loaded By " + this.getName() + "(" + this + ") name: " + name);
        }

        return clazz;

    }

    public Class<?> loadClassData(String className) {
        Class clazz = findLoadedClass(className);
        if (clazz != null) {
            return clazz;
        }
        try {
            return super.loadClass(className);
        } catch (ClassNotFoundException e) {
        }


        return null;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = name.replace('.', '/').concat(".class");

        //find class from classes
        {
            Path classPath = Paths.get(pluginDir, "classes", name.replace('.', '/') + ".class");
            try {
                if (Files.exists(classPath)) {
                    byte[] classData = Files.readAllBytes(classPath);
                    Class<?> cls = defineClass(name, classData, 0, classData.length);
//                    cache.put(name, cls);
                    return cls;
                }
            } catch (IOException e) {
                throw new ClassNotFoundException("Class " + name + " not found by " + this.getName());
            }


            //find class from lib
            {
                File libDir = new File(pluginDir, "lib");
                if (libDir.exists()) {
                    for (File file : libDir.listFiles(f -> f.getName().endsWith(".jar"))) {
                        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) {
                            ZipEntry entry;
                            while ((entry = zis.getNextEntry()) != null) {
                                if (entry.getName().equals(path)) {
                                    byte[] classData = zis.readAllBytes();
                                    Class<?> cls = defineClass(name, classData, 0, classData.length);
//                                   cache.put(name, cls);
                                    return cls;
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }

        }
        throw new ClassNotFoundException("Class " + name + " not found by " + this.getName());
    }


    public List<String> getExportedClass() {
        List<String> list = new ArrayList<>();
        String startPathStr = pluginDir + "/classes";
        Path startPath = Paths.get(startPathStr);

        try {
            Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    if (file.toString().endsWith(".class")) {
                        list.add(file.toString().substring(startPathStr.length() + 1));
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        return list;
    }

}

完整代码库

业务插件
https://github.com/lzc-alioo/alioo-container-plugin-x1
注: alioo-container-plugin-x2 与alioo-container-plugin-x1相同故未上传

业务插件打包工具
https://github.com/lzc-alioo/alioo-boot-maven-plugin

业务容器
https://github.com/lzc-alioo/alioo-container

测试工程
https://github.com/lzc-alioo/classloadertest

### 回答1: 对于这个问题,我可以给你提供一些建议。在Matlab中,可以使用switch-case语句来实现控制结构,其格式为:switch(expression),case1:statement1; case2:statement2; ……; otherwise:statementX;end。其中expression可以是一个变量或者表达式,case1到caseN是可能的取值,statement1到statementN分别是每一种可能情况下要执行的命令,otherwise是可选的,表示在所有case都不匹配的情况下应该执行的命令。 ### 回答2: switch-case语句是MATLAB中一种条件选择结构,用于根据不同的条件执行不同的代码块。以下是一个使用MATLAB编写的switch-case语句的示例: ```matlab score = input('请输入你的分数:'); % 获取用户输入的分数 switch score case 90 disp('成绩优秀,你是学霸!'); case 80 disp('成绩良好,继续努力!'); case 70 disp('成绩中等,还有进步空间!'); case 60 disp('成绩及格,继续加油!'); otherwise disp('成绩不及格,再接再厉!'); end ``` 在这个例子中,首先通过`input`函数获取用户输入的分数,然后使用switch-case语句根据不同的分数执行不同的代码块。根据输入的分数,分别进入不同的case分支并输出相应的信息。如果输入的分数没有匹配到任何一个case分支,将执行otherwise分支的代码块。 通过这种方式,我们可以根据不同的条件执行不同的操作,提高程序的灵活性和可读性。 ### 回答3: switch-case语句是一种条件判断语句,根据不同的条件执行不同的代码块。在MATLAB中,我们也可以使用switch-case语句来实现相同的功能。 switch语句由关键字switch和多个case语句组成,每个case语句都有一个常量或者表达式作为条件。当某个case的条件与switch中的表达式匹配时,该case中的代码块将被执行。同时,我们还可以为switch语句定义一个默认的执行代码块,即当没有任何case匹配时,执行默认的代码块。 以下是一个使用MATLAB编写的简单的switch-case语句示例: ```matlab % 假设我们定义一个变量x x = 3; % 使用switch-case语句进行条件判断 switch x case 1 disp('x等于1'); case 2 disp('x等于2'); case 3 disp('x等于3'); otherwise disp('x不等于1、2和3'); end ``` 在上述示例中,我们定义了一个变量x,并使用switch-case语句进行条件判断。根据x的值,对应的case中的代码块将被执行。在这个例子中,由于x的值为3,因此执行输出语句`disp('x等于3');`,即输出"x等于3"。如果x的值是1或2,则分别执行相应的case中的代码块。如果x的值不等于1、2和3,则执行默认的代码块,即输出"x不等于1、2和3"。 通过使用switch-case语句,我们可以根据不同的条件执行不同的代码块,提高了MATLAB程序的灵活性和可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值