【曹工雜談】說說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了。這又是為啥呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM