说说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. 构建后端第6篇之---java 多态的本质 父类引用 指向子类实现

    张艳涛写于2021-2-20 今天来个破例了,不用英文写了,今天在家里电脑写的工具不行,简单的说 主题是:java多态的原理与实现 结论是:java的多态 Father father= new Son ...

  2. Discuz 7.x/6.x 全局变量防御绕过导致代码执行

    地址 http://192.168.49.2:8080/viewthread.php?tid=13&extra=page%3D1 安装成功后,找一个已存在的帖子,向其发送数据包,并在Cooki ...

  3. php预定义常量运用

    当前url 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 当前文件路径 dirname(__FILE__)header(" ...

  4. 让自己写的电子笔记连文带图全平台兼容(MarkDown图片显示兼容)

    目录 一.工具使用 语言使用:MarkDown 简介 使用原因 使用方法 软件使用:Typora 简介 环境设置搭建 1)搭建图床 2)配置PicGo 3)配置typora 4)测试 图片上传测试 平 ...

  5. 并发编程——认识java里的线程

    本文系作者 chaoCode原创,转载请私信并在文章开头附带作者和原文地址链接. 违者,作者保留追究权利. 前言 并发编程在我们日常开发中是时时刻刻都有在用的,只不过大部分的代码底层已经帮我们去做了一 ...

  6. linux中的dhcp

    目录 一.DHCP服务 二.DHCP的租约过程 三.使用DHCP动态配置主机地址 四.安装DHCP服务器 一.DHCP服务 ① DHCP (Dynamic HostConfiguration Prot ...

  7. 利用MySQL原数据信息批量转换指定库数据表生成Hive建表语句

    1.写出文件工具类 package ccc.utile; import java.io.*; /** * @author ccc * @version 1.0.0 * @ClassName Write ...

  8. gRPC学习之一:在CentOS7部署和设置GO

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. git连接远程GitHub仓库详细总结 for HTTPS协议

    简单唠叨几句哈.之前工作的时候,由于是在大厂,代码提交.版本管理都是多人协作的,所以公司当时用的git + gerrit来实现代码管理的.当时入职时并不懂git的使用,虽然有大神写好配置git的文档, ...

  10. 010 FPGA千兆网UDP通信【转载】

    一.以太网帧格式 图8‑12以太网帧格式 表8‑5以太网帧格式说明 类别 字节数 说明 前导码(Preamble) 8 连续 7 个 8'h55 加 1 个 8'hd5,表示一个帧的开始,用于双方设备 ...