说说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了。这又是为啥呢?

【曹工杂谈】说说Maven框架和插件的契约的更多相关文章

  1. 【曹工杂谈】Maven源码调试工程搭建

    Maven源码调试工程搭建 思路 我们前面的文章<[曹工杂谈]Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗>分析了Maven大体的执行阶段,主要包括三个阶段: 启动类阶段,负责 ...

  2. 【曹工杂谈】Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗

    Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗 前奏 我们上篇文章,跟大家说了下,怎么调试maven插件的代码,注意,是插件的代码.插件,是要让主框架来执行的,主框架是谁呢,就是maven ...

  3. 【曹工杂谈】Maven底层容器Plexus Container的前世今生,一代芳华终落幕

    Maven底层容器Plexus Container的前世今生,一代芳华终落幕 前言 说实话,我非常地纠结,大家平时只是用Maven,对于内部的实现其实也不关心,我现在非要拉着大家给大家讲.这就有个问题 ...

  4. 【曹工杂谈】Maven IOC容器的下半场:Google Guice

    Maven容器的下半场:Guice 前言 在前面的文章里,Maven底层容器Plexus Container的前世今生,一代芳华终落幕,我们提到,在Plexus Container退任后,取而代之的底 ...

  5. 【曹工杂谈】Maven IOC 容器-- Guice内部有什么

    Google Guice容器内部有什么 前言 Maven系列,好几天没写了,主要是这几天被Google Guice卡住了,本来是可以随便带过Guice,讲讲guice的用法就够了(这个已经讲了,在前面 ...

  6. 【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题

    背景 去年写了一篇"[曹工杂谈]Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱",结果最近还真就用上了. 不是我用上,是组内一位同事,他也是这样:有个服务往数据库in ...

  7. 【曹工杂谈】详解Maven插件调试方法

    前言 今年的更新频率简直是降至冰点了,一方面平时加班相对多一些了,下班只想玩手机:另一方面,好像进了大厂后,学习动力也很低了,总之就,很懒散,博客的话,今年都才只更新了不到5篇. 现在慢慢有一点状态, ...

  8. 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种

    前言 今天新年第一天,给大家拜个年,祝大家新的一年里,技术突突突,头发长长长! 咱们搞技术的,比较直接,那就开始吧.我给大家看看我demo工程的效果(代码下边会给大家的): 技术栈是mybatis/m ...

  9. 曹工杂谈:一例简单的Jar包冲突解决示例

    Jar包冲突的相关文章: 了不得,我可能发现了Jar 包冲突的秘密   一.前言 jar包冲突分多种,简单理解来说,就是同package且同名的类在多个jar包内出现,如果两个jar包在同一个clas ...

随机推荐

  1. CC攻击和C2的区别

    [一]背景 今天被旁边姐姐问C2.CC是什么,虽然平时老看到这个词,身边也有自己写C2工具的大佬.但好像突然被问到有点懵,不知道怎么回答. [二]内容 CC ( Challenge Collapsar ...

  2. MarkDown语法(Typora软件为例)

    Hello    !我又来了 这篇文章主要给大家讲一下MarkDown的一些基础语法,MarkDown语法是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML( ...

  3. 2020Android高级开发面试题以及答案整理,持续更新中~

    本篇收录了一些大厂面试中经常会遇到的经典面试题,并且我做好了整理分类.虽然今年的金九银十已经过去了,但是可以为明年的金三银四做准备啊,相信每一个跳槽季都有很多的前端开发者蠢蠢欲动,通过对本篇知识的整理 ...

  4. jdbc如何注册数据库驱动Driver的?

    1. 先看看原生jdbc执行sql的步骤 // 在程序启动的时候需要注册一次mysql驱动,必须引入 mysql-connnector-java 的包 Class.forName("com. ...

  5. 关于shell脚本——echo、for语句、while语句、until语句

    目录 一.echo 1.1.echo命令用法 1.2.echo截取字符 二.for语句 2.1.实例 创建用户名文件 创建脚本文件 运行脚本 三.while语句 3.1.实例 创建脚本文件 运行脚本 ...

  6. 理解SpingAOP

    目录 什么是AOP? AOP术语 通知(Advice) 连接点(Join point) 切点(Pointcut) 连接点和切点的区别 切面(Aspect) 引入(Introduction) 织入(We ...

  7. SpringBoot自定义请求参数转换器

    需求 我们可能对接客户的系统的时候,虽然Spring为我们提供的很多方便的转换器,但是遇到还是可能遇到需要自定义请求参数转换器的情况. 日期转换器 SpringBoot默认是没有配置日期转换器的我们可 ...

  8. vue同一个页面可以有多个router-view

    参考:https://blog.csdn.net/u011615787/article/details/80075240 参考:https://router.vuejs.org/zh/guide/es ...

  9. spring security 入门级全篇代码

    CustomAccessDecisionManager 类 ---------------------------------------------------------------------- ...

  10. Redis5.0 配置文件中文参考

    Redis 5.0 配置文件#是否在后台执行,yes:后台运行:no:不是后台运行daemonize yes#是否开启保护模式,默认开启.要是配置里没有指定bind和密码.开启该参数后,redis只会 ...