Mavenのプラグインに一度の開発プロセスを覚えておいてください

要件の説明

、コードを提供するために、スキャンジャーパッケージ以降、繰り返し実行操作を減らし、実行時にロジックを実行したくないスクリプトの、それぞれの希望:(方法など、注釈付きの名前、など)の情報のスキャンタイプを開発ジャーコード変更パッケージは、報告された情報に対応することができます。

私たちのプロジェクトは、Mavenを充填することにより行われるが、私たちは私たちのプロジェクトのサン様々なMavenのライフサイクル管理におけるいくつかの操作を実行できるように、Mavenは、プラグイン機構を提供しています。

私はただの需要にMavenのプラグインを書くことを学ぶのMavenプラグイン機構を満たすために、この時間を必要とし、開発中に避けるために他人を助けることを望んで、ここに記録し、ピットの多くを踏ん。

Mavenのプラグインの記事

モジョプロジェクト

コンセプト

モジョは、Mavenの昔ながらのJavaオブジェクトです。プラグインは、単一または複数の関連モジョであるそれぞれのMavenモジョは、分散統一、ターゲット(実行ゴール)で実行されます。モジョは、単純なJavaクラスが含まれています。プラグインの一般的な類似モジョ抽象親クラスの複数カプセル化するために使用されてもよいです。

Mavenのプロジェクトの作成

名前:一般的に、我々はプラグインの名前を所有することになり-maven-plugin、お勧めできませんmaven—plugin後者は、公式プラグインの名前のMavenのチームを維持するために維持されているので、このネーミングを使用商標のApache Mavenを侵害します。

作成通常のMavenプロジェクトをすることができますし、我々はいくつかの変更を加える必要があるのpom.xml:

レビュー:のpom.xmlは抽象クラスインターフェースの一部が含まれ、この依存関係モジョ、Mavenの-プラグインAPIに依存関係を追加する必要があります。

<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-plugin-api</artifactId>
  <version>2.0</version>
</dependency>

通常のpom.xmlファイルには重要な違いは、それがパッケージ化されています方法です。

<packaging>maven-plugin</packaging>

Mavenプロジェクトを書きます

モジョはその後、我々はモジョの出力を印刷するために使用される最初のクラスを作成し、単純なJavaクラスです。

モジョAbstractMojoクラスは抽象クラスを拡張し、コンテンツモジョ特定の操作を定義するために使用されるexecute()メソッドを実装する必要があり、我々は彼らのニーズに応じて達成することができ、あなた自身を記述する必要があります。

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
/**
 * @goal hello
 */
public class HelloMojo extends AbstractMojo {

    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("Hello Mojo");
    }

}
@Mojo(name = "hello")
public class HelloMojo extends AbstractMojo {

    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("Hello Mojo");
    }

}

Mavenのモジョにする方法、これはそれがない通常のJavaクラスであることを知っていますか?ここでは、モジョ・ルックアップ・メカニズムについて話をする必要がある、とソースを扱うには、プラグイン・ツールは、クラスとしてモジョに@goalコメントを@MojoクラスのJavadocのアノテーションを使用するか、または含まれています。

@Mojoコメントを使用して、我々は、新しいパッケージを導入する必要があります。

<dependency>
  <groupId>org.apache.maven.plugin-tools</groupId>
  <artifactId>maven-plugin-annotations</artifactId>
  <version>3.1</version>
</dependency>

カスタムプラグインを実行

そして、他の同様のプラグインは、我々は、プラグインpom.xmlファイルを導入する必要があります。

<build>
    <plugins>
        <plugin>
            <groupId>XXX</groupId>
            <artifactId>XXX</artifactId>
            <version>XXX</version>
        </plugin>
    </plugins>
</build>

我々はまた、Mavenプラグインを設定することによって指定することができますが、ライフサイクルのどの段階で実行するだけでなく、いくつかのコマンドによって割り当てることができます。

例えば、私はあなたがコンパイル時に実行する必要があり、これを必要とする、あなたは、着信クラス名をスキャンする必要があります。

    <build>
        <plugins>
            <plugin>
                <groupId>com.sankuai</groupId>
                <artifactId>athena-nr-maven-plugin</artifactId>
                <version>0.0.2</version>
                <executions>
                    <execution>
                        <!-- 配置compile执行 -->
                        <phase>compile</phase>
                        <goals>
                            <!-- 配置执行目标 -->
                            <goal>reporter</goal>
                        </goals>
                        <configuration>
                            <!-- className是要传入的值 -->
                            <className>XXX</className>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
//执行目标
@Mojo(name="reporter" ,requiresDependencyResolution = ResolutionScope.COMPILE)
public class Reporter extends AbstractMojo{

        //传入参数
    @Parameter(property = "reporter.className")
    private String className;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        //执行逻辑
            ......
    }

}

Mavenのプラグインのデバッグ

メソッドのデバッグがたくさんありますが、IDEAを使用する比較的単純な必要性を選択してください。

ターミナルを使用しmvnDebug groupID:artifactID:version:goal、プラグインを開始するコマンドを、この時間は、ポート8000を開始します。

我々はまた、リモートIDEAと過去をつなぐ、Mavenのリモートデバッグを使用する必要があり、executeメソッドプラグインにブレークポイントでマークされました。

Mavenのプラグイン依存性(第一ホール)

当社のロジックは、クラスをロードした後、このような何かを行うには、完全なクラス名を通じて、完全なクラス名モジョの着信クラスです。

しかし、問題は、クラスを見つけることが、プラグインを実行すると、現在のクラスのロードにそれを投影しないことができ、なぜClassLoaderを現在のプロジェクトを、破ったことができませんか?

Mavenの達人参照クラスローディング機構に関するサイトの指示。

ローディング機構のMavenの4つのタイプがあります。

  1. システムクラスローダ
  2. コアクラスローダ
  3. プラグインのクラスローダ
  4. カスタムクラスローダ

我们关心的是第三种,plugin classloaders。这个类加载器从类加载的层次关系来看是继承与System classloader 和Core Classloader的,凭想当然的理解在插件goal执行的时候插件的classloader已经包含的project pom 中申明的依赖包。但是,plugin classloader说明中有这么一句话:

Please note that the plugin classloader does neither contain the dependencies of the current project nor its build output. Instead, plugins can query the project's compile, runtime and test class path from the MavenProject in combination with the mojo annotation requiresDependencyResolution from the Mojo API Specification. For instance, flagging a mojo with @requiresDependencyResolution runtime enables it to query the runtime class path of the current project from which it could create further classloaders.

翻译一下:

请注意,plugin classloader既不包含当前工程的dependencies,也不包含当前工程的输出目录。但是,如果你现在插件运行的时候想引用当前工程的编译(compile)、运行时(runtime)、测试(test)的classpath,可以通过MavenProject 这个组合在成员对象来调用,这个mojo对象需要有“@requiresDependencyResolution”这个annotation

参考该说明,我们知道,maven plugin 不能拿到当前工程的dependencies,也不能包含当前工程的输出目录,我们要向加载我们需要的类可以通过两个方法解决。

解决的第一个方法

在plugin配置的时候为plugin配置节点单独配置一个dependance

    <build>
        <plugins>
            <plugin>
                <groupId>com.sankuai</groupId>
                <artifactId>athena-nr-maven-plugin</artifactId>
                <version>0.0.2</version>
                <executions>
                    <execution>
                        <!-- 配置compile执行 -->
                        <phase>compile</phase>
                        <goals>
                            <!-- 配置执行目标 -->
                            <goal>reporter</goal>
                        </goals>
                        <configuration>
                            <!-- className是要传入的值 -->
                            <className>XXX</className>
                        </configuration>
                    </execution>
                </executions>
                <!-- 配置需要引入的依赖 -->
                <dependencies>
                            <dependency>
                            <groupId>org.apache.httpcomponents</groupId>
                            <artifactId>httpclient</artifactId>
                            <version>4.5.3</version>
                            </dependency>
                        </dependencies>
            </plugin>
        </plugins>
    </build>

但是这样一来,每次升级Jar包,我还要将依赖的配置升一下级,好烦。

解决的第二个方法

动态读取目标项目所依赖的classpath并根据这些classpath生成相应的url数组,以这个url数组作为参数得到的类加载器可以实现在maven插件中动态加载目标项目类及第三方引用包的目的。

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

在mojo类中引入MavenProject参数,这个参数是目标项目的抽象,仅仅引入这个参数不需要多余的操作即可。

通过MavenProject参数,我们可以调用project的getCompileClasspathElements()拿到路径的String 列表。

通过这个列表,可以构建URL数组,构建自己的类加载器,该类加载器可以达到实现在maven插件中动态加载目标项目类及第三方引用包的目的。

自定义类加载器篇

首先来复习一下类加载器的一些知识

ClassLoader类加载器

主要的作用是将class文件加载到jvm虚拟机中。jvm启动的时候,并不是一次性加载所有的类,而是根据需要动态去加载类,主要分为隐式加载和显示加载。

隐式加载

程序代码中不通过调用ClassLoader来加载需要的类,而是通过JVM类自动加载需要的类到内存中。例如,当我们在类中继承或者引用某个类的时候,JVM在解析当前这个类的时,发现引用的类不在内存中,那么就会自动将这些类加载到内存中。

显式加载

代码中通过Class.forName()this.getClass.getClassLoader.LoadClass(),自定义类加载器中的findClass()方法等。

jvm自带的加载器

BootStrap ClassLoader

主要加载%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。可以通过System.getProperty("sun.boot.class.path")查看加载路径

Extention ClassLoader

主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。也可以通过System.out.println(System.getProperty("java.ext.dirs"))查看加载类文件的路径。

AppClassLoader

主要加载当前应用下的classpath路径下的类。之前我们在环境变量中配置的classpath就是指定AppClassLoader的类加载路径。

类加载器的继承关系

ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader,BoopStrap ClassLoder不在上图中,因为它是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。jvm加载的顺序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder

AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为null,BoopStrap ClassLoader为顶级加载器。

demo验证

package test;

public class Test {
    
    public static void main(String []args){
        
        System.out.println(Test.class.getClassLoader().toString());
        
        System.out.println(Test.class.getClassLoader().getParent().toString());
        
        System.out.println(Test.class.getClassLoader().getParent().getParent().toString());
    }
}

类加载机制

例如:当jvm要加载Test.class的时候

  1. 首先会到自定义加载器中查找,看是否已经加载过,如果已经加载过,则返回字节码。
  2. 如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。
  3. 如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。
  4. 如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。
  5. 如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下"sun.boot.class.path"查看是否有Test.class字节码,有则返回,没有通知下一层加载器ExtClassLoader到自己指定的类加载路径下java.ext.dirs查看。
  6. 依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。

类加载过程

loadClass > findLoadedClass > findClass

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加载器不为空,调用父加载器的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空则,调用Bootstrap Classloader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //父加载器没有找到,则调用findclass
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                //调用resolveClass()
                resolveClass(c);
            }
            return c;
        }
    }

自定义类加载器

/**
 * created by zhangzhiyuan in 2019/8/2
 */
public class EngineClassLoader extends URLClassLoader {
    public EngineClassLoader() {
        this(getSystemClassLoader());
    }

    public EngineClassLoader(ClassLoader parent) {
        super(new URL[] {}, parent);
    }

    public void addURL(URL... urls) {
        if (urls != null) {
            for (URL url : urls) {
                super.addURL(url);
            }
        }
    }

}

加载不到注解问题(第二个坑)

用了该加载器,确实可以通过全类名加载到指定的类,但是又出现了另一个问题。

调用该类是否有某注解的时候,显示没有,并且也无法拿到指定注解

boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

但是在打断点的时候,是显示有该注解的,为什么呢?

因为该注解是由我们的自定义类加载器获得的,和我们调用的类虽然是一个类,但不是一个类

一个类由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例。

无奈只能全程反射了……麻烦的一匹。

参考

https://blog.csdn.net/u012620150/article/details/78652624

https://blog.csdn.net/m0_37635806/article/details/86711423

https://blog.csdn.net/weixin_40318210/article/details/85055133

https://blog.csdn.net/iteye_10738/article/details/81794471

https://blog.csdn.net/imlsz/article/details/51013556

https://blog.csdn.net/tianlihu/article/details/83669738

https://stackoverflow.com/questions/9318935/get-project-build-directory-from-mavenproject

https://stackoverflow.com/questions/13462107/mavenproject-get-the-available-classes-for-use-on-my-plugin

https://stackoverflow.com/questions/35457401/maven-plugin-api-get-mavenproject-from-artifact

おすすめ

転載: www.cnblogs.com/coder-chi/p/11305498.html