• 【曹工杂谈】说说Maven框架和插件的契约


    说说Maven框架和插件的契约

    前言

    Maven框架就像现在公司内的各种平台方,规定一些契约,然后想办法拉动业务方,一起在这个平台上去做生态共建。Maven也是这样,其实它就是一个插件执行的框架,Maven刚开始肯定不知道会有谁去贡献插件,插件如果写得五花八门的话,那对于平台方来说,可能就是一个灾难,所以,平台方就要负责定标准,要在我平台上写插件,必须怎么怎么样。

    Maven给插件就定了契约,这个契约,是通过api jar包的方式。每次发布Maven新版本,与之伴随的,都会有一个api jar包。

    如果有人要基于这个版本的api jar包来开发插件,就需要把这个插件引入到自己的插件工程中。然后根据api jar包中的契约接口,来实现自己的插件逻辑。

    比如,maven clean插件的工程代码中,就依赖了api jar包。如下:

    api jar包中的契约接口长啥样呢?

    public interface Mojo
    {
    	...
        void execute()
            throws MojoExecutionException, MojoFailureException;
    }
    

    核心方法就是这个,只要你实现这个接口就完事了。

    作为框架方,怎么去调用这个插件呢?简而言之,就是:

    1、找到插件的实现类jar包,然后构造一个该插件的类加载器,去加载这个jar包,然后找到对应的实现了契约接口的类,比如这里的CleanMojo

    2、加载了这个CleanMojo的class之后,当然是反射生成对象,然后强制转换为契约接口,然后调用契约接口就行。比如:

    Class cleanMojoClass = 插件的类加载器加载插件的jar包;
    Mojo cleanMojo = (Mojo)cleanMojoClass.newInstance();
    cleanMojo.execute();
    

    到此为止,我们的理论知识已经足够了,我们是不是可以show the code了?

    工程实践

    我们会模拟上面的过程,

    1. 建一个Maven module,用来存放插件api契约接口;
    2. 建一个Maven module,引入api,实现插件api,这样,我们的插件就算是实现好了;
    3. 接下来,把这两个工程编译一下,把jar包安装到本地仓库;
    4. 再新建一个工程,模拟Maven框架去加载插件,并执行插件。

    插件api工程

    直接用maven的archetype中的quickstart,新建一个module,里面很简单,就一个接口:

    然后执行mvn install,安装到本地仓库。

    插件实现工程

    在pom中,我们会引入api。

      <dependencies>
        <dependency>
          <groupId>org.example</groupId>
          <artifactId>my-plugin-api</artifactId>
          <version>1.0-SNAPSHOT</version>
        </dependency>
      </dependencies>
    
    

    代码也很简单,就一个实现类。

    然后执行mvn install,安装到本地仓库。

    主工程,模拟框架去调用插件

    主工程就是模拟我们的Maven框架,由于我们调用插件,肯定是通过api的方式,所以,pom中肯定是要引入api的。

      <dependencies>
        <dependency>
          <groupId>org.example</groupId>
          <artifactId>my-plugin-api</artifactId>
          <version>1.0-SNAPSHOT</version>
        </dependency>
      </dependencies>
    

    接下来,我们写了个测试类:

    public static void main( String[] args ) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        	// 1.1处
            URL urlForPluginApi = new URL("file:/C:\Users\Administrator\.m2\repository\org\example\my-plugin-api\1.0-SNAPSHOT\my-plugin-api-1.0-SNAPSHOT.jar");
            URL urlForPluginImpl = new URL("file:/C:\Users\Administrator\.m2\repository\org\example\my-plugin-implementation\1.0-SNAPSHOT\my-plugin-implementation-1.0-SNAPSHOT.jar");
            URL[] urls = {urlForPluginApi, urlForPluginImpl};
        
        	// 1.2
            URLClassLoader urlClassLoader = new URLClassLoader(urls,ClassLoader.getSystemClassLoader()){
                @Override
                public Class<?> loadClass(String name) throws ClassNotFoundException {
                    try{
                        // 保证:寻找类时,优先查找自己的classpath,找不到,再去交给parent classloader
                        Class<?> clazz = findClass(name);
                        return clazz;
                    }catch (ClassNotFoundException exception ){
                        return super.loadClass(name);
                    }
                }
            };
            
        	// 1.3
            Class<?> implClazzByPluginClassloader = urlClassLoader.loadClass("org.example.MyMojoImplementation");
        	// 1.4 
            MojoInterface mojoInterface = (MojoInterface) implClazzByPluginClassloader.newInstance();
        	// 1.5 
            mojoInterface.execute();
    
            System.out.println( "Hello World!" );
        }
    

    我先大概讲解一下上述代码:

    • 1.1处,构造了两个url,分别指向我本地仓库的两个文件,也就是api.jar和插件对应的实现的jar

    • 1.2处,使用1.1中的url,构造了一个classloader,这个classloader的parent classloader,我们传的是,系统的AppClassloader。

      同时,我们重写了这个classloader的行为,重写后的行为如下:遇到要加载的类时,自己优先加载,也就是会去自己的两个url里面找,看看能不能找到,如果找不到,就会进入异常,异常被我们捕获后,交给parent classloader去加载;

    • 1.3处,我们用新建的classloader,去加载了插件的实现类

    • 1.4处,利用1.3处加载的实现类的class,反射生成对象,强转为MojoInterface接口对象

    • 1.5处,多态方式执行插件逻辑

    大家不妨思考下,大家觉得,最终的执行结果是啥?我们的“hello world”能打印出来吗?

    这个代码,我们上传了gitee,大家可以拉下来看。

    https://gitee.com/ckl111/maven-3.8.1-source-learn

    我这边给大家展示下,执行结果:

    大家看看,这像话吗,明明我的插件代码里,是实现了接口的,怎么就不能向上转型呢?:

    public class MyMojoImplementation implements MojoInterface{
    
        @Override
        public void execute() {
            System.out.println("implementation execute business logic");
        }
    }
    

    这个。。。怎么说呢。。。这么跟你解释吧,我们加载MyMojoImplementation时,发现这个类吧,还实现了接口MojoInterface,那么,这个接口类也就需要加载,因为我们classloader进行了改写(优先由自己进行加载),因此,最终呢,MojoInterface也就和MyMojoImplementation一样,都是由插件类加载器去加载的。

    最终呢,在向上转型时,会出现下边这个情况,两边不匹配,就报错了。

            MojoInterface(框架中的这个类,是由框架的类加载器加载的) mojoInterface = (MojoInterface) implClazzByPluginClassloader.newInstance();(这个实现类实现的接口,是由插件类加载器加载的)
    
    

    课后题

    我们对代码进行了修改,改成了如下的样子,结果,就可以跑通我们的hello world了。这又是为啥呢?

  • 相关阅读:
    前端各种小细节
    图片等比例缩放
    微信小程序倒计时,购物车,向左滑删除 左拉删除
    微信小程序用户拒绝授权,重新调起授权
    微信小程序swiper切换卡内嵌滚动条不显示
    php面试总结
    tp5.0 学习(三):URL和路由
    pytorch 基础内容
    Grammarly for Chrome-语法、用词自动检查
    时空图神经网路:STGNNs
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/15245223.html
Copyright © 2020-2023  润新知