解释器模式-自定义语言的实现

news2024/11/17 20:11:58

 有时,我们希望输入一串字符串,然后计算机能够按照预先定义的文法规则来对这个字符串进行解释,从而实现相应的功能。

例如,我们想实现简单的加减法接收器,只需输入一个表达式,它就能计算出表达式结果。比如输入字符串:“2+4-1+4-5”,计算机输出4。像这种用户自己定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言。此时可以使用解释器模式来实现自定义语言。

1 解释器模式

1.1 文法规则

在前面所提到的加法/减法解释器中,emigrants表达式都包含了3个语言单位,可以使用如下语法规则来定义:

expression ::= value | operation

operation ::= expression ‘+’ expression | expression ‘-’ expression

value ::= an integer // 一个整数值

::=

表示定义为

|

表示或

“{” 和 “}”

表示组合

*

表示出现0次或多次

语言单位

又称为语言构造成分,每一条语句所定义的字符串,如上例中的value和operation。

终结表达式

组成元素是最基本的语言单位,不能再进行分解。

非终结表达式

组成元素仍然可以是表达式,可以进一步分解。如expression 和 operation。

图 文法规则说明

​​​​​​​1.2 抽象语法树

除了使用文法规则来定义一个语言外,还可以通过一种被称为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观的表示语言的构成。每一棵抽象语法树对应一个语言实例。“2+4-1+4-5” 的抽象语法树表示为:

图 抽象语法树示意图

图中终结符表达式作为树的叶子节点,而非终结表达式作为非叶子节点,它们可将终结符表达式以及包含终结符和非终结符的子表达式作为其子节点。通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类。

1.3 解释器模式概述

用于描述如何使用对象语言构成一个简单的语言解释器。定义一个语言的文法,并建立一个解释器来解释语言中的句子。这里的“语言”是指使用规定格式和语法的代码。

图 解释器模式结构图

  1. AbstractExpression,抽象表达式,声明了抽象的解释操作,是所有终结符表达式和非终结符表达式的父类。
  2. TerminalExpression,终结符表达式,实现了与文法中的终结符相关联的解释操作,在句子中的每个终结符都是该类的一个实例,它们的实例可以通过非终结符表达式组成较为复杂的句子。
  3. NonterminalExpression,非终结符表达式,实现了文法中非终结符的解释操作。在非终结符表达式中可以包含终结符表达式,也可以包含非终结符表达式,因此其解释操作一般通过递归的方式来实现。
  4. Context,环境类,又称为上下文,用于存储解释器之外的一些全局信息。通常存储了需要解释的语句。

1.3.1 解释器模式实现简单的英文控制指令

需求描述:每个指令对应一个表达式,该表达式可以是简单表达式,也可以是复合表达式。两个表达式之间可以通过and连接,形成复合表达式。简单表达式组成如下表:

移动方向(direction)

上(up)、下(down)、左(left)、右(right)

移动方式(action)

移动(move)、快速移动(run)

移动距离(distance)

为一个正整数

表 简单表达式的组成

例如“up move 5”表示向上移动5个单位。“dow run 10 and left move 20”表示向下快速移动10个单位然后向左移动20个单位。

direction ::= ‘up’|’down’|’left’|’right’;

action :: = ‘move’|’run’;

distance ::= an integer; //一个正整数

expression ::= direction action distance | complexExpression

complexExpression ::= expression ‘and’ expression

// 简单的英语控制指令,例如 left move 10
public interface AbstractExpression {

    void interpret();

}

public class ActionExpression implements AbstractExpression{

    private final String action;

    public ActionExpression(String action) {
        this.action = action;
    }

    @Override
    public void interpret() {
        if (action == null) {
            throw new RuntimeException("解析失败:行为值为空");
        }
        String tempStr = action.toLowerCase();
        switch (tempStr) {
            case "move":
                System.out.print("移动");
                break;
            case "run":
                System.out.print("快速移动");
                break;
            default:
                throw new RuntimeException("解析失败:行为值不合法");
        }
    }

}

public class ComplexExpression implements AbstractExpression{

    private AbstractExpression leftExpress;
    private AbstractExpression rightExpress;

    public ComplexExpression(AbstractExpression leftExpress, AbstractExpression rightExpress) {
        this.leftExpress = leftExpress;
        this.rightExpress = rightExpress;
    }

    @Override
    public void interpret() {
        leftExpress.interpret();
        System.out.print(" 然后 ");
        rightExpress.interpret();
    }

}

public class DirectionExpression implements AbstractExpression{

    private final String direction;

    public DirectionExpression(String direction) {
        this.direction = direction;
    }

    @Override
    public void interpret() {
        if (direction == null) {
            throw new RuntimeException("解析失败:方向值为空");
        }
        String tempStr = direction.toLowerCase();
        switch (tempStr) {
            case "up":
                System.out.print("向上");
                break;
            case "down":
                System.out.print("向下");
                break;
            case "left":
                System.out.print("向左");
                break;
            case "right":
                System.out.print("向右");
                break;
            default: throw new RuntimeException("解析失败:方向值不合法");
        }
    }

}

public class DistanceExpression implements AbstractExpression{

    private final String value;

    public DistanceExpression(String value) {
        this.value = value;
    }

    @Override
    public void interpret() {
        if (value == null) {
            throw new RuntimeException("解析失败:距离为空");
        }
        System.out.print(Integer.valueOf(value) + "个单位");
    }

}

public class SimpleExpression implements AbstractExpression{

    private final AbstractExpression direction;
    private final AbstractExpression action;
    private final AbstractExpression distance;

    public SimpleExpression(AbstractExpression direction, AbstractExpression action, AbstractExpression distance) {
        this.direction = direction;
        this.action = action;
        this.distance = distance;
    }

    @Override
    public void interpret() {
        direction.interpret();
        action.interpret();
        distance.interpret();
    }

}

public class Client {

    private static final String[] expressArr = {"left move 12 and down run 5","down run 3","right run 1 and left move 12 and left run 4 and down move 12"};


    public static void main(String[] args) {
        for (String str : expressArr) {
            String[] strArr = str.split(" ");
            Stack<AbstractExpression> expressionStack = new Stack<>();
            for (int i = 0; i < strArr.length; i++) {
                if ("and".equals(strArr[i])) {
                    AbstractExpression leftExp = expressionStack.pop();
                    AbstractExpression rightExp = buildSimpleExp(strArr, i + 1);
                    expressionStack.push(new ComplexExpression(leftExp,rightExp));
                    i += 3;
                } else {
                    expressionStack.push(buildSimpleExp(strArr,i));
                    i += 2;
                }
            }
            expressionStack.pop().interpret();
            System.out.println();
        }
//        运行结果:
//        向左移动12个单位 然后 向下快速移动5个单位
//        向下快速移动3个单位
//        向右快速移动1个单位 然后 向左移动12个单位 然后 向左快速移动4个单位 然后 向下移动12个单位
    }

    private static AbstractExpression buildSimpleExp(String[] strArr, int start) {
        AbstractExpression directionExp = new DirectionExpression(strArr[start]);
        AbstractExpression actionExp = new ActionExpression(strArr[start + 1]);
        AbstractExpression distance = new DistanceExpression(strArr[start + 2]);
        return new SimpleExpression(directionExp,actionExp,distance);
    }

}

1.3.2 简单的英语控制指令 为啥要用解释器模式?

 其实,上面这个需求在不用解释器模式的情况下也可以实现,而且只需在一个类就能完成。

public class Client2 {

    private static final String[] expressArr = {"left move 12 and down run 5","down run 3","right run 1 and left move 12 and left run 4 and down move 12"};

    public static void main(String[] args) {
        System.out.println("不用解释器模式实现需求:");
        System.out.println("--------------");
        for (String str : expressArr) {
            StringBuilder sb = new StringBuilder();
            String[] strArr = str.split(" ");
            for (String s : strArr) {
                switch (s) {
                    case "and":
                        sb.append(" 然后 ");
                        break;
                    case "up":
                        sb.append("向上");
                        break;
                    case "down":
                        sb.append("向下");
                        break;
                    case "left":
                        sb.append("向左");
                        break;
                    case "right":
                        sb.append("向右");
                        break;
                    case "move":
                        sb.append("移动");
                        break;
                    case "run":
                        sb.append("快速移动");
                        break;
                    default:
                        sb.append(s).append("个单位");
                }
            }
            System.out.println(sb);
        }
//        运行结果:
//        不用解释器模式实现需求:
//        --------------
//        向左移动12个单位 然后 向下快速移动5个单位
//        向下快速移动3个单位
//        向右快速移动1个单位 然后 向左移动12个单位 然后 向左快速移动4个单位 然后 向下移动12个单位
    }
    
}

不用解释器模式实现的代码量更少、类的数量也更少而且运行速度也更快。所以为什么要用解释器模式呢?

  1. 文法规则让需求更加清晰。上面分析的5个表达式完整的表示了这个需求,通过拆解表达式的形式,由复合语句到不可拆分表达式。
  2. 扩展及修改方便。不用解释器的情况下条件判断语句太多了,如果修改或者增加某个含义,则在修改代码时将会遇到很大的困难。而解释器模式把各种语句以类的形式来表示,只需要修改相应类即可。

1.3.3 Context的作用

上下文Context 类用于存储解释器之外的一些全局信息。通常作为参数被传递到所有表达式的解释方法interpret()中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据。此外,还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。

需求描述:实现一套简单的基于字符串界面的格式化指令,可以根据输入的指令在字符串界面中输出一些格式化内容。

例如:”LOOP 2 PRINT 今天发工资啦 SPACE SPACE PRINT 亲爱的老婆  BREAK END PRINT 走 SPACE SPACE PRINT 下馆子去!” 将输出如下结果:

今天发工资啦  亲爱的老婆

今天发工资啦  亲爱的老婆

走  下馆子去!

BREAK

换行

SPACE

空格

END

循环结束

PRINT

打印,后面字符串表示打印的内容

LOOP

循环,后面的数字表示循环次数

  表 关键字表示含义

每个关键字对应一条命令,程序根据关键字执行相应的处理操作。

primitive ::= ‘PRINT string’ | ‘BREAK’ | ‘SPACE’; // 基本命令,string 为字符串

command ::= loop | primitive; // 语句命令

expression ::= command *; //  表达式,一个表达式包含多条命令

loop ::= LOOP number expression ‘END’; // j循环命令,number 为自然数

public class Context {

    private final String[] commandStrArr;
    private int currentPos = 0;

    public Context(String str) {
        commandStrArr = str.split(" ");
    }

    public String getCurrentCommand() {
        return commandStrArr[currentPos];
    }

    public void skip() { ++currentPos;}

    public void interpreter(List<AbstractCommand> commandList) {
        while (true) {
            if (currentPos >= commandStrArr.length) {
                break;
            }
            if ("END".equals(commandStrArr[currentPos])) {
                skip();
                break;
            }
            AbstractCommand command = new ConcreteCommand();
            command.interpreter(this);
            commandList.add(command);
        }
    }

}

public interface AbstractCommand {

    void interpreter(Context context);

    void execute();

}

public class ConcreteCommand implements AbstractCommand{

    private AbstractCommand command;

    @Override
    public void interpreter(Context context) {
        if ("LOOP".equals(context.getCurrentCommand())) {
            command = new LoopCommand();
        } else {
            command = new PrimitiveCommand();
        }
        command.interpreter(context);
    }

    @Override
    public void execute() {
        command.execute();
    }
}

public class ExpressionCommand implements AbstractCommand{

    private final List<AbstractCommand> commandList = new ArrayList<>();

    @Override
    public void interpreter(Context context) {
        context.interpreter(commandList);
    }

    @Override
    public void execute() {
        for (AbstractCommand command : commandList)
            command.execute();
    }

}

public class LoopCommand implements AbstractCommand{

    private Integer number;
    private AbstractCommand command;

    @Override
    public void interpreter(Context context) {
        context.skip();
        String value = context.getCurrentCommand();
        context.skip();
        number = Integer.valueOf(value);
        command = new ExpressionCommand();
        command.interpreter(context);
    }

    @Override
    public void execute() {
        for (int i = 0; i < number; i++) {
            command.execute();
        }
    }

}

public class PrimitiveCommand implements AbstractCommand {

    private String value;

    @Override
    public void interpreter(Context context) {
        switch (context.getCurrentCommand()) {
            case "PRINT":
                context.skip();
                value = context.getCurrentCommand();
                break;
            case "BREAK":
                value = "\n";
                break;
            case "SPACE":
                value = " ";
                break;
        }
        context.skip();
    }

    @Override
    public void execute() {
        System.out.print(value);
    }

}

public class Client {

    private final static String commandStr = "LOOP 2 LOOP 2 PRINT 今天发工资啦 SPACE SPACE PRINT 亲爱的老婆 BREAK END PRINT 走 SPACE SPACE PRINT 下馆子去 BREAK END";

    public static void main(String[] args) {
        Context context = new Context(commandStr);
        AbstractCommand command = new ExpressionCommand();
        command.interpreter(context);
        command.execute();
//        运行结果:
//        今天发工资啦  亲爱的老婆
//        今天发工资啦  亲爱的老婆
//        走  下馆子去
//        今天发工资啦  亲爱的老婆
//        今天发工资啦  亲爱的老婆
//        走  下馆子去
    }

}

在这里,Context的作用是存储一些公共变量及实现公有方法。

1.3.4 解释器模式实现简单加减法

需求描述:实现简单的加减法接收器,只需输入一个表达式,它就能计算出表达式结果。比如输入字符串:“2+4-1+4-5”,计算机输出4。

number ::= a integer;// 一个整数

operation ::= expression ‘+|-’ expression

expression ::= number | operation

public class Context {

    private final String[] expressionArr;
    private Integer currentPos = 0;

    public Context(String str) {
        expressionArr = str.split(" ");
    }

    public String getCurrentExp() {
        return getExp(0);
    }

    public String getNextExp() {
        return getExp(1);
    }

    public String getPreExp() {
        return getExp(-1);
    }

    private String getExp(int num) {
        return currentPos + num >= expressionArr.length || currentPos + num < 0 ? null : expressionArr[currentPos + num];
    }

    public void skip(int num) {
        currentPos += num;
    }

    public void interpreter(AbstractExpression preExpression) {

    }

}


public interface AbstractExpression {

    void interpreter(Context context);

    Integer execute();

}

public class ConcreteExpression implements AbstractExpression{

    private AbstractExpression expression;

    @Override
    public void interpreter(Context context) {
        if (context.getNextExp() != null) {
            expression = new OperationExpression();
        } else {
            expression = new NumberExpression();
        }
        expression.interpreter(context);
    }

    @Override
    public Integer execute() {
        return expression.execute();
    }

}

public class NumberExpression implements AbstractExpression{

    private Integer number;

    @Override
    public void interpreter(Context context) {
        number = Integer.valueOf(context.getCurrentExp());
        String type = context.getPreExp();
        if ("-".equals(type)) {
            number = -number;
        }
    }

    @Override
    public Integer execute() {
        return number;
    }
}

public class OperationExpression implements AbstractExpression{

    private AbstractExpression leftExpression;
    private AbstractExpression rightExpression;

    @Override
    public void interpreter(Context context) {
        leftExpression = new NumberExpression();
        leftExpression.interpreter(context);
        context.skip(2);
        rightExpression = new ConcreteExpression();
        rightExpression.interpreter(context);
    }

    @Override
    public Integer execute() {
        return leftExpression.execute() + rightExpression.execute();
    }

}

/* 实现简单的加减法接收器,只需输入一个表达式,
它就能计算出表达式结果。比如输入字符串:
“2 + 4 - 1 + 4 - 5”,计算机输出4。 */
public class Client {

    private static final String STR = "23 - 5 + 22 - 3 - 33 - 22 + 1";

    public static void main(String[] args) {
        Context context = new Context(STR);
        AbstractExpression expression = new ConcreteExpression();
        expression.interpreter(context);
        System.out.println(expression.execute());
//        运行结果:
//        17
    }

}

2 优缺点

优点:

  1. 易于改变和扩展文法,使用类来表示文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 实现文法较为容易。增加新的解释表达式较为方便,只需增加相关表达式类即可,原有表达式类无须修改,符合开闭原则。

缺点:

  1. 对于复杂文法难以维护。如果一种语言包含太多文法规则,类的数量将会急剧增加,导致系统难以管理和维护,可以考虑使用语法分析程序等方式来取代解释器模式。
  2. 执行效率较低,使用了大量循环和递归调用,而且代码调试过程也比较麻烦。

3 适用场景

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 执行效率不是关键,一个语言的文法较为简单。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/837297.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot统一功能处理(拦截器)

1.用户登录权限校验 1.1自定义拦截器 写一个类去实现HandlerInterceptor接口表示当前类是一个拦截器,再重写HandlerInterceptor接口中的方法,preHandle为在方法执行前拦截,postHandle为方法执行中拦截,afterCompletion为方法执行中拦截.需要在什么时候拦截就重写什么方法 Co…

微信公众号自动登录方案

基于微信公众号登录 借助微信公众号来试实现社区登录。登录的时候展示的是一个二维码&#xff0c;但实际上的操作是借助这个展示的过程&#xff0c;和前端构建一个半长连接&#xff0c;当用户向公众号发送验证码之后&#xff0c;微信公众平台会将用户发送的消息转发给服务器&a…

Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)

文章目录 Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)服务器端大体结构图BLL层&#xff08;控制层&#xff09;DAL层&#xff08;数据控制层&#xff09;模型层DLC 服务器配置类 发送消息类 以及消息类 Unity进阶–通过PhotonServer实现联网…

Gartner发布《2023年全球RPA魔力象限》:90%RPA厂商,将提供生成式AI自动化

8月3日&#xff0c;全球著名咨询调查机构Gartner发布了《2023年全球RPA魔力象限》&#xff0c;通过产品能力、技术创新、市场影响力等维度&#xff0c;对全球16家卓越RPA厂商进行了深度评估。 弘玑Cyclone&#xff08;Cyclone Robotics&#xff09;、来也&#xff08;Laiye&am…

【蓝图】p47下车减速功能

p47下车减速功能 p47下车减速功能加速功能下车减速功能 p47下车减速功能 加速功能 上图是ue自带的加速功能&#xff0c;检测到按w时输入轴会传1给设置油门输入&#xff0c;就会加速 所以&#xff0c;减速也可以通过蓝图反方向制作 下车减速功能 打开Sedan蓝图类的上下车图表…

day51-Mybatis-Plus/代码生成器

1.Mybatis-Plus 定义&#xff1a;是一个Mybatis的增强工具&#xff0c;只在Mybatis基础上增强不做改变&#xff0c;简化开发&#xff0c;提升效率 2.MP实战 2.1 创建springboot工程&#xff0c;勾选web&#xff0c;引入依赖 <dependency> <groupId>mysql<…

人工智能可解释性分析导论(初稿)

目录 思维导图 1.黑箱所带来的问题 2.从应用面论述为什么要进行可解释性分析 2.1可解释性分析指什么 2.2可解释性分析结合人工智能应用实例 2.3 可解释性分析的脑回路&#xff08;以可视化为例如何&#xff09; 3.如何研究可解释性分析 3.1使用好解释的模型 3.2传统机器学…

antDv table组件滚动截图方法的实现

在开发中经常遇到table内容过多产生滚动的场景&#xff0c;正常情况下不产生滚动进行截图就很好实现&#xff0c;一旦产生滚动就会变得有点棘手。 下面分两种场景阐述解决的方法过程 场景一&#xff1a;右侧不固定列的情况 场景二&#xff1a;右侧固定列的情况 场景一 打开…

理解树的结构

树的重要性 二分查找算法、几种核心的排序算法以及图算法都与树有非常密切的关系。有句话锁&#xff0c;“没学会树&#xff0c;算法相当于白学”&#xff0c;可见&#xff0c;树在算法中的地位。 树的考察方面 层次遍历以及拓展问题 前后序遍历与拓展问题 中序遍历与搜索树问…

数据结构入门指南:带头双向循环链表

目录 文章目录 前言 1.结构与优势 2.链表实现 2.1 定义链表 2.2 创建头节点 2.3 尾插 2.4 输出链表 2.5 尾删 2.6 头插 2.7头删 2.8 节点个数 2.9 查找 2.10 位置插入 2.11 位置删除 2.12 销毁链表 3. 源码 总结 前言 链表一共有8种结构&#xff0c;但最常用的就是无头单…

Docker网络模型使用详解(2)Docker网络模式

安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootlocalhost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE ebcfad6f4255 bridge bridge local b881c67f8813 compose_lnmp_lnmp…

Vue2升级Vue3报错:Right-hand side of ‘instanceof‘ is not an object

属性prop设置多类型报错&#xff1a; Vue2 写法&#xff1a;支持用竖线隔开。Vue2 Prop expandLevel: {type: Number | String,default: 1, }, Vue3 写法&#xff1a;改为数组&#xff0c;不支持竖线隔开。Vue3 Prop expandLevel: {type: [Number, String],default: 1, }

二次元美少女【InsCode Stable Diffusion 美图活动一期】

目录 Stable Diffusion 模型在线使用地址 一、背景介绍 二、模板介绍&#xff1a; 三、操作步骤 1.在线运行地址 2.进入在线运行网址&#xff0c;并点击运行及使用 3.购买GPU并创建项目 4.打开工作台并选择算力资源 5.点击下图中所示框框 6.进入Stable Diffusion WebU…

VR内容研发公司 | VR流感病毒实验虚拟现实课件

由广州华锐互动开发的《VR流感病毒实验虚拟现实课件》是一种新型的教学模式&#xff0c;可以为学生提供更加真实和直观的流感病毒分离鉴定实验操作体验&#xff0c;从而提高学生的实验技能和工作效率。 《VR流感病毒实验虚拟现实课件》涉及了生物安全二级实验室(BSL-2)和流感病…

.jpeg转.jpg,cv2.resize()

from PIL import Image import os # 定义原文件夹路径和目标文件夹路径 source_folder "path/to/source/folder" target_folder "path/to/target/folder" # 遍历原文件夹中的所有图片文件 for filename in os.listdir(source_folder): if fil…

【iOS安全】安装Filza || 安装Flexdecrypt

&#xff08;成功&#xff09;使用Cydia安装Filza 直接在Cydia里搜索filza&#xff0c;安装“Filza File Manager” 使用Filza安装flexdecrypt 参考&#xff1a; https://github.com/JohnCoates/flexdecrypt 下载flexdecrypt.deb到手机&#xff1a; https://github.com/JohnC…

猎聘:2023届高校毕业生就业数据报告(附下载

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 较 2022 届应届生职位同比增长较明显的TOP5 一级行业为能源/化工/环保、医疗健康、汽车、机械/制造、电子/通信/半导体&#xff0c;其中能源/化工/环保同比增长为 42.30%&#xff0c;增速最高.在全世…

mybatisplus实现自动填充 时间

mybatisplus实现自动填充功能——自动填充时间 数据库表中的字段 创建时间 (createTime)更新时间 (updateTime) 每次 增删改查的时候&#xff0c;需要通过对Entity的字段&#xff08;createTime&#xff0c;updateTime&#xff09;进行set设置&#xff0c;但是&#xff0c;每…

Systemui的介绍以及与普通应用的差异

一.SystemUI的介绍 简介 SystemUI是Android操作系统的一个关键组件&#xff0c;主要负责管理和提供用户界面的核心元素&#xff0c;如状态栏、导航栏和锁屏界面等。从下面两点出发了解SystemUI的特性&#xff1a; 一下就是systemui的部分界面&#xff0c;还包括锁屏界面&…

Android Tencent Shadow 插件接入指南

Android Tencent Shadow 插件接入指南 插件化简述一、clone 仓库二、编译运行官方demo三、发布Shadow到我们本地仓库3.1、安装Nexus 3.x版本3.2、修改发布配置3.3、发布仓库3.4、引用仓库包 四、编写我们自己的代码4.1、新建项目导入maven等共同配置4.1.1、导入buildScript4.1.…