Java规则引擎easy-rules详细介绍

目录
  • 简介
  • 开始使用
    • 引入依赖
    • 定义规则
      • 使用注解定义规则
      • 使用RuleBuilder定义规则
      • 组合规则
      • 规则优先级
      • Rules API
    • 定义事实
    • 定义规则引擎
      • 创建规则引擎
      • 规则引擎参数
    • 定义规则监听器
    • 定义规则引擎监听器
    • 表达式语言(EL)支持
      • EL提供者注意事项
      • 通过编程的方式定义规则
      • 通过规则描述文件定义规则
      • 规则定义中的错误处理
  • 实际栗子

最近在思考一个基于规则进行挑选的技术重构,想通过规则引擎进行实现,借着这个机会正好可以详细了解一下规则引擎。本篇文章将会详细介绍规则引擎easy-rules的使用。项目地址:https://github.com/j-easy/easy-rules

简介

Easy Rules是一个简单但功能强大的Java规则引擎,提供以下特性:

  • 轻量级框架和易于学习的API
  • 基于POJO的开发
  • 支持从原始规则创建组合规则
  • 支持通过表达式(如MVEL,SPEL和JEXL)定义规则

开始使用

引入依赖

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.1.0</version>
</dependency>

上面只引入了core模块依赖,如需要其它模块内容,再引入对应依赖即可。

定义规则

介绍

大多数业务规则可以用以下定义表示:

  • name:规则命名空间中的唯一规则名称
  • description:规则的简要描述
  • priority:规则的优先级
  • facts:触发规则时的一组已知事实
  • conditions:在给定一些事实的情况下,为了应用该规则,需要满足的一组条件
  • actions:满足条件时要执行的一组操作(可能会添加/删除/修改事实)

Easy Rules为定义业务规则的每个关键点提供了抽象。Easy Rules中的规则由Rule接口表示:

public interface Rule extends Comparable<Rule> {

    /**
    * 此方法封装了规则的条件。
    * @return 如果根据提供的事实可以应用规则,则为true,否则为false
    */
    boolean evaluate(Facts facts);

    /**
    * 此方法封装了规则的操作。
    * @throws 如果在执行操作期间发生错误,则抛出异常
    */
    void execute(Facts facts) throws Exception;

    //Getters and setters for rule name, description and priority omitted.

}

evaluate()方法封装了必须为true才能触发规则的条件。execute()方法封装了在满足规则条件时应该执行的操作。条件和操作由Condition和Action接口表示。
规则可以用两种不同的方式定义:

  • 通过在POJO上添加注解来声明
  • 通过RuleBuilder API编程

这些是定义规则的最常用方法,但是如果需要,您也可以实现Rule接口或扩展BasicRule类。

使用注解定义规则

Easy Rules提供了@Rule注解,可以将POJO转换为规则。

@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {

    @Condition
    public boolean when(@Fact("fact") fact) {
        // 规则条件
        return true;
    }

    @Action(order = 1)
    public void then(Facts facts) throws Exception {
        // 规则为true时的操作1
    }

    @Action(order = 2)
    public void finally() throws Exception {
        // 规则为true时的操作2
    }
}

@Condition注解用来标记评估规则条件的方法,这个方法必须是public,可以有一个或多个带@Fact注解的参数,并返回一个boolean类型。只有一个方法可以用@Condition注解标记。
@Action注解用来标记执行操作的方法,规则可以有多个操作。可以使用order属性以指定的顺序执行操作。

使用RuleBuilder定义规则

RuleBuilder允许你用流式API定义规则。

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();

在本例中,condition是Condition接口的实例,action1和action2是Action接口的实例。

组合规则

Easy Rules允许从原始规则创建复杂的规则。一个CompositeRule由一组规则组成。组合规则是一个抽象概念,因为组合规则可以以不同的方式触发。Easy Rules提供了3种CompositeRule的实现。

  • UnitRuleGroup:单元规则组是作为一个单元使用的组合规则,要么应用所有规则,要么不应用任何规则。
  • ActivationRuleGroup:激活规则组触发第一个适用规则并忽略组中的其他规则。规则首先按照其在组中的自然顺序(默认情况下优先级)进行排序。
  • ConditionalRuleGroup:条件规则组将具有最高优先级的规则作为条件,如果具有最高优先级的规则的计算结果为true,那么将触发其余的规则。

组合规则可以从原始规则创建并像常规规则一样注册。

// 从两个原始规则创建组合规则
UnitRuleGroup myUnitRuleGroup =
    new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);

// 像常规规则一样注册组合规则
Rules rules = new Rules();
rules.register(myUnitRuleGroup);

RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);

规则优先级

Easy Rules中的每个规则都有一个优先级。这表示触发注册规则的默认顺序。默认情况下,值越低优先级越高。要覆盖此行为,您应该重写compareTo()方法以提供自定义优先级策略。

  • 如果是继承BasicRule,可以在构造方法中指定优先级,或者重写getPriority()方法。
  • 如果是使用POJO定义规则,可以通过@Rule注解的priority属性指定优先级,或者使用@Priority注解标记一个方法。这个方法必须是public,无参却返回类型为Integer。
  • 如果使用RuleBuilder定义规则,可以使用RuleBuilder#priority()方法指定优先级。

Rules API

Easy rules中的一组规则由rules API表示。它的使用方法如下:

Rules rules = new Rules();
rules.register(myRule1);
rules.register(myRule2);

Rules表示已注册规则的命名空间,因此,在同一命名空间下,每一个已经注册的规则必须有唯一的名称。

Rules是通过Rule#compareTo()方法进行比较的,因此,Rule的实现应该正确的实现compareTo()方法来确保单一空间下拥有唯一的规则名称。

定义事实

Easy Rules中的一个事实是由Fact表示的:

public class Fact<T> {
   private final String name;
   private final T value;
}

一个事实有一个名称和一个值,两者都不能为null。另一方面,Facts API 表示一组事实并充当事实的命名空间。这意味着,在一个Facts实例中,事实必须有唯一的名称。
下面是一个如何定义事实的例子:

Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);

你也可以使用一个更短的版本,用put方法创建命名的事实,如下所示:

Facts facts = new Facts();
facts.put("foo", "bar");

可以使用@Fact注解将事实注入到规则的条件和操作方法中。在以下规则中,rain事实被注入到itRains方法的rain参数中:

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }

}

类型为Facts的参数将被注入所有已知的事实。
注意:

  • 如果条件方法中缺少注入的事实,引擎将记录一个警告,并认为条件被计算为false。
  • 如果动作方法中缺少注入的事实,则不会执行该动作,并且抛出org.jeasy.rules.core.NoSuchFactException异常。

定义规则引擎

Easy Rules提供了RulesEngine接口的两种实现:

  • DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
  • InferenceRulesEngine:在已知的事实上不断地应用规则,直到没有更多的规则可用。

创建规则引擎

可以使用构造方法创建规则引擎。

RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();

可以按如下方式触发已注册的规则。

rulesEngine.fire(rules, facts);

规则引擎参数

Easy Rules引擎可以配置以下参数:

参数 类型 默认值
rulePriorityThreshold int MaxInt
skipOnFirstAppliedRule boolean false
rulePriorityThreshold int false
skipOnFirstFailedRule boolean false
skipOnFirstNonTriggeredRule boolean false
  • skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。
  • skipOnFirstFailedRule:当一个规则失败时,跳过余下的规则。
  • skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则。
  • rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。

可以使用RulesEngineParameters API指定这些参数:

RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

如果你想从你的引擎中获取参数,你可以使用以下代码段:

RulesEngineParameters parameters = myEngine.getParameters();

这允许在创建引擎参数后重新设置引擎参数。

定义规则监听器

可以通过RuleListener API来监听规则执行事件:

public interface RuleListener {

    /**
     * 在评估规则之前触发。
     *
     * @param rule 正在被评估的规则
     * @param facts 评估规则之前的已知事实
     * @return 如果规则应该评估,则返回true,否则返回false
     */
    default boolean beforeEvaluate(Rule rule, Facts facts) {
        return true;
    }

    /**
     * 在评估规则之后触发
     *
     * @param rule 评估之后的规则
     * @param facts 评估规则之后的已知事实
     * @param evaluationResult 评估结果
     */
    default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }

    /**
     * 运行时异常导致条件评估错误时触发
     *
     * @param rule 评估之后的规则
     * @param facts 评估时的已知事实
     * @param exception 条件评估时发生的异常
     */
    default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }

    /**
     * 在规则操作执行之前触发。
     *
     * @param rule 当前的规则
     * @param facts 执行规则操作时的已知事实
     */
    default void beforeExecute(Rule rule, Facts facts) { }

    /**
     * 在规则操作成功执行之后触发
     *
     * @param rule t当前的规则
     * @param facts 执行规则操作时的已知事实
     */
    default void onSuccess(Rule rule, Facts facts) { }

    /**
     * 在规则操作执行失败时触发
     *
     * @param rule 当前的规则
     * @param facts 执行规则操作时的已知事实
     * @param exception 执行规则操作时发生的异常
     */
    default void onFailure(Rule rule, Facts facts, Exception exception) { }

}

可以实现这个接口来提供自定义行为,以便在每个规则之前/之后执行。要注册监听器,请使用以下代码段:

DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRuleListener(myRuleListener);

可以注册任意数量的侦听器,它们将按照注册顺序执行。
注意:当使用组合规则时,监听器是围绕组合规则调用的。

定义规则引擎监听器

可以通过RulesEngineListener API来监听规则引擎的执行事件:

public interface RulesEngineListener {

    /**
     * 在执行规则集之前触发
     *
     * @param rules 要触发的规则集
     * @param facts 触发规则前的事实
     */
    default void beforeEvaluate(Rules rules, Facts facts) { }

    /**
     * 在执行规则集之后触发
     *
     * @param rules 要触发的规则集
     * @param facts 触发规则前的事实
     */
    default void afterExecute(Rules rules, Facts facts) { }
}

RulesEngineListener允许我们在触发整个规则集之前/之后提供自定义行为。可以使用如下方式注册监听器。

DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRulesEngineListener(myRulesEngineListener);

可以注册任意数量的监听器,它们将按照注册顺序执行。

表达式语言(EL)支持

Easy Rules支持用MVEL、SpEL和JEXL定义规则。

EL提供者注意事项

EL提供者在行为上有一些区别。例如,当一个事实在条件中缺失时,MVEL抛出一个异常,而SpEL将忽略它并返回false。因此,在选择Easy Rules使用哪个EL之前,你应该了解这些差异。

通过编程的方式定义规则

条件、动作和规则分别由MVELCondition/SpELCondition/JexlCondition、MVELAction/SpELAction/JexlAction和MVELRule/SpELRule/JexlRule类表示。下面是一个使用MVEL定义规则的例子:

Rule ageRule = new MVELRule()
        .name("age rule")
        .description("Check if person's age is > 18 and marks the person as adult")
        .priority(1)
        .when("person.age > 18")
        .then("person.setAdult(true);");

通过规则描述文件定义规则

可以使用规则描述文件定义规则,使用MVELRuleFactory/SpELRuleFactory/JexlRuleFactory来从描述符文件创建规则。下面是一个在alcohol-rule.yml中以YAML格式定义的MVEL规则示例:

name: "alcohol rule"
description: "children are not allowed to buy alcohol"
priority: 2
condition: "person.isAdult() == false"
actions:
  - "System.out.println("Shop: Sorry, you are not allowed to buy alcohol");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
MVELRule alcoholRule = ruleFactory.createRule(new FileReader("alcohol-rule.yml"));

还可以使用一个文件创建多个规则。

---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:
  - "person.setAdult(true);"
---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:
  - "System.out.println("It rains, take an umbrella!");"

可以使用如下方式将这些规则加载到rules对象中。

MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));

Easy Rules还支持从JSON描述符加载规则。具体参考文档,这里不做展开。

规则定义中的错误处理

关于条件中不正确表达式的引擎行为
对于条件求值过程中可能发生的任何运行时异常(丢失事实、表达式中输入错误等),引擎将记录一个警告,并认为条件求值为false。可以使用RuleListener#onEvaluationError来监听评估错误。

关于操作中不正确表达式的引擎行为
对于任何在执行操作时可能发生的运行时异常(丢失事实、表达式中输入错误等),该操作将不会执行,引擎将记录一个错误。可以使用RuleListener#onFailure来监听操作执行异常。当一个规则失败时,引擎将移动到下一个规则,除非设置了skipOnFirstFailedRule参数。

实际栗子

本栗子使用Easy Rules实现FizzBuzz应用程序。FizzBuzz是一个简单的应用程序,需要从1数到100,并且:

  • 如果数字是5的倍数,则打印“fizz”
  • 如果数字是7的倍数,请打印“buzz”
  • 如果数字是5和7的倍数,请打印“fizzbuzz”
  • 否则打印数字本身
public class FizzBuzz {
  public static void main(String[] args) {
    for(int i = 1; i <= 100; i++) {
      if (((i % 5) == 0) && ((i % 7) == 0))
        System.out.print("fizzbuzz");
      else if ((i % 5) == 0) System.out.print("fizz");
      else if ((i % 7) == 0) System.out.print("buzz");
      else System.out.print(i);
      System.out.println();
    }
    System.out.println();
  }
}

我们将为每个需求编写一条规则:

@Rule
public class FizzRule {

    @Condition
    public boolean isFizz(@Fact("number") Integer number) {
        return number % 5 == 0;
    }

    @Action
    public void printFizz() {
        System.out.print("fizz");
    }

    @Priority
    public int getPriority() {
        return 1;
    }
}

@Rule
public class BuzzRule {

    @Condition
    public boolean isBuzz(@Fact("number") Integer number) {
        return number % 7 == 0;
    }

    @Action
    public void printBuzz() {
        System.out.print("buzz");
    }

    @Priority
    public int getPriority() {
        return 2;
    }
}

public class FizzBuzzRule extends UnitRuleGroup {

    public FizzBuzzRule(Object... rules) {
        for (Object rule : rules) {
            addRule(rule);
        }
    }

    @Override
    public int getPriority() {
        return 0;
    }
}

@Rule
public class NonFizzBuzzRule {

    @Condition
    public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
        return number % 5 != 0 || number % 7 != 0;
    }

    @Action
    public void printInput(@Fact("number") Integer number) {
        System.out.print(number);
    }

    @Priority
    public int getPriority() {
        return 3;
    }
}

以下是对这些规则的一些解释:

  • FizzRule和BuzzRule很简单,它们会检查输入是5的倍数还是7的倍数,然后打印结果。
  • FizzBuzzRule是一个组合规则。通过FizzRule和BuzzRule创建。基类选择为UnitRuleGroup,要么满足并应用这两个规则,要么什么都不应用。
  • NonFizzBuzzRule是既不是5的倍数也不是7的倍数时的规则。

请注意,我们已经设置了优先级,因此规则的触发顺序与Java示例中的示例相同。
然后,我们必须将这些规则注册到一个规则集中,并使用一个规则引擎来触发它们:

public class FizzBuzzWithEasyRules {
    public static void main(String[] args) {
        // 创建规则引擎
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // 创建规则
        Rules rules = new Rules();
        rules.register(new FizzRule());
        rules.register(new BuzzRule());
        rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
        rules.register(new NonFizzBuzzRule());

        // 触发规则
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}

注意,我们已经设置了skipOnFirstAppliedRule参数,以便在成功应用规则时跳过后续的规则。

到此这篇关于Java规则引擎easy-rules详细介绍的文章就介绍到这了,更多相关Java规则引擎easy-rules详细介绍内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java轻量级规则引擎easy-rules使用介绍

    轻量级规则引擎easy-rules--参考 我们在写业务代码经常遇到需要一大堆if/else,会导致代码可读性大大降低,有没有一种方法可以避免代码中出现大量的判断语句呢?答案是用规则引擎,但是传统的规则引擎都比较重,比如开源的Drools,不适合在小需求中应用.最近在github上面看到一个傻瓜式的Java规则引擎Easy-Rules,这里结合自己写的demo介绍如何使用这个规则引擎,希望对大家有所帮助. easy-rules的特点 轻量级类库和容易上手 基于POJO的开发与注解的编程模型 基于

  • Java规则引擎Easy Rules的使用介绍

    1. Easy Rules 概述 Easy Rules是一个Java规则引擎,灵感来自一篇名为<Should I use a Rules Engine?>的文章 规则引擎就是提供一种可选的计算模型.与通常的命令式模型(由带有条件和循环的命令依次组成)不同,规则引擎基于生产规则系统.这是一组生产规则,每条规则都有一个条件(condition)和一个动作(action)---- 简单地说,可以将其看作是一组if-then语句. 精妙之处在于规则可以按任何顺序编写,引擎会决定何时使用对顺序有意义的任

  • Java规则引擎easy-rules详细介绍

    目录 简介 开始使用 引入依赖 定义规则 使用注解定义规则 使用RuleBuilder定义规则 组合规则 规则优先级 Rules API 定义事实 定义规则引擎 创建规则引擎 规则引擎参数 定义规则监听器 定义规则引擎监听器 表达式语言(EL)支持 EL提供者注意事项 通过编程的方式定义规则 通过规则描述文件定义规则 规则定义中的错误处理 实际栗子 最近在思考一个基于规则进行挑选的技术重构,想通过规则引擎进行实现,借着这个机会正好可以详细了解一下规则引擎.本篇文章将会详细介绍规则引擎easy-r

  • Java编程中的构造函数详细介绍

    本文主要是为新手.对java语言感兴趣的人和那些没有系统学习过java基础知识的人进行一个总结,在文章中对构造函数进行了较为详细的说明和讨论,也包含了我个人对于java面向对象中构造函数的一些看法.希望走在java学习道路上的同行者可以有一个较为清晰的认知和理解.当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 1.构造函数的概念 很多java新手谈到构造函数就会犯晕,我们先来看看什么是构造函数. 首先,构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回类型

  • Java与Http协议的详细介绍

    Java与Http协议的详细介绍 引言      http(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议,常基于TCP的连接方式.HTTP协议的主要特点是:      1.支持客户/服务器模式.      2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径.由于HTTP协议简单,通信速度很快.      3.灵活:HTTP允许传输任意类型的数据对象.类型由Content-Type加以标记.      4.无连接:即每次连接只处理一个请求,处理完客户的请求,并收到客

  • Mongodb3.0.5 副本集搭建及spring和java连接副本集配置详细介绍

    Mongodb3.0.5 副本集搭建及spring和java连接副本集配置详细介绍 一.基本环境: mongdb3.0.5数据库 spring-data-MongoDB-1.7.2.jar mongo-Java-driver-3.0.2.jar Linux-redhat6.3 tomcat7 二.搭建mongodb副本集: 1.  分别在三台linux系统机上安装mongodb,(为避免和机器上原有的mongodb端口冲突,这里设为57017): 192.168.0.160 192.168.0.

  • JAVA JNI函数的注册过程详细介绍

    JAVA JNI函数的注册过程详细介绍 我们在java中调用Native code的时候,一般是通过JNI来实现的,我们只需要在java类中加载本地.so库文件,并声明native方法,然后在需要调用的地方调用即可,至于java中native方法的具体实现,全部交给了Native层.我们要在java中正确地调用到本地代码中对应函数的前提是什么呢?答案就是通过一定的机制建立java中native方法和本地代码中函数的一一对应关系,那么这种机制是什么呢?就是JNI函数的注册机制. JNI函数的注册有

  • Java 对象序列化 NIO NIO2详细介绍及解析

    Java 对象序列化 NIO NIO2详细介绍及解析 概要: 对象序列化 对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而可以保存到磁盘或者进行网络传输,其它程序获得这个二进制流后可以将其恢复成原来的Java对象. 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化的含义和意义 序列化 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢

  • java 值Document解析xml详细介绍

    java 值Document解析xml详细介绍 使用jar包:jdom.jar 配置文件格式 global.xml 一.获取输入的值组成的结点 我们将每个结点使用"." 拼接起来, 如结点i ,拼接为abc.def.i, 注意不包含root.解析每个结点的名字. /** * 获取对应结点的名字 * @param name 不包含root结点的拼接,ie: abc.def.i * @return */ private String[] parsePropertyName(String n

  • Java 本地方法Native Method详细介绍

     Java 本地方法Native Method详细介绍 一. 什么是Native Method 简单地讲,一个Native Method就是一个Java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C.这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数. "A native method is a Java meth

  • Java中ArrayList的使用详细介绍

    目录 1.ArrayList类 1.1ArrayList类概述 1.2ArrayList类常用方法 1.2.1构造方法 1.2.2成员方法 1.2.3示例代码 1.3ArrayList存储字符串并遍历 1.3.1案例需求 1.3.2代码实现 1.4ArrayList存储学生对象并遍历 1.4.1案例需求 1.4.2代码实现 1.5ArrayList存储学生对象并遍历升级版 1.5.1案例需求 1.5.2代码实现 总结 1.ArrayList类 1.1ArrayList类概述 在java中,我们会

随机推荐