<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java規(guī)則引擎easy-rules詳細(xì)介紹

          共 9439字,需瀏覽 19分鐘

           ·

          2022-01-09 11:47

          最近在思考一個基于規(guī)則進(jìn)行挑選的技術(shù)重構(gòu),想通過規(guī)則引擎進(jìn)行實(shí)現(xiàn),借著這個機(jī)會正好可以詳細(xì)了解一下規(guī)則引擎。本篇文章將會詳細(xì)介紹規(guī)則引擎easy-rules的使用。項(xiàng)目地址:github.com/j-easy/easy…

          簡介

          Easy Rules是一個簡單但功能強(qiáng)大的Java規(guī)則引擎,提供以下特性:

          • 輕量級框架和易于學(xué)習(xí)的API

          • 基于POJO的開發(fā)

          • 支持從原始規(guī)則創(chuàng)建組合規(guī)則

          • 支持通過表達(dá)式(如MVEL,SPEL和JEXL)定義規(guī)則

          開始使用

          引入依賴

          <dependency>
          <groupId>org.jeasygroupId>
          <artifactId>easy-rules-coreartifactId>
          <version>4.1.0version>
          dependency>
          復(fù)制代碼

          上面只引入了core模塊依賴,如需要其它模塊內(nèi)容,再引入對應(yīng)依賴即可。

          定義規(guī)則

          介紹

          大多數(shù)業(yè)務(wù)規(guī)則可以用以下定義表示:

          • name:規(guī)則命名空間中的唯一規(guī)則名稱

          • description:規(guī)則的簡要描述

          • priority:規(guī)則的優(yōu)先級

          • facts:觸發(fā)規(guī)則時的一組已知事實(shí)

          • conditions:在給定一些事實(shí)的情況下,為了應(yīng)用該規(guī)則,需要滿足的一組條件

          • actions:滿足條件時要執(zhí)行的一組操作(可能會添加/刪除/修改事實(shí))

          Easy Rules為定義業(yè)務(wù)規(guī)則的每個關(guān)鍵點(diǎn)提供了抽象。Easy Rules中的規(guī)則由Rule接口表示:

          public interface Rule extends Comparable<Rule> {

          /**
          * 此方法封裝了規(guī)則的條件。
          * @return 如果根據(jù)提供的事實(shí)可以應(yīng)用規(guī)則,則為true,否則為false
          */

          boolean evaluate(Facts facts);

          /**
          * 此方法封裝了規(guī)則的操作。
          * @throws 如果在執(zhí)行操作期間發(fā)生錯誤,則拋出異常
          */

          void execute(Facts facts) throws Exception;

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

          }
          復(fù)制代碼

          evaluate()方法封裝了必須為true才能觸發(fā)規(guī)則的條件。execute()方法封裝了在滿足規(guī)則條件時應(yīng)該執(zhí)行的操作。條件和操作由ConditionAction接口表示。

          規(guī)則可以用兩種不同的方式定義:

          • 通過在POJO上添加注解來聲明

          • 通過RuleBuilder API編程

          這些是定義規(guī)則的最常用方法,但是如果需要,您也可以實(shí)現(xiàn)Rule接口或擴(kuò)展BasicRule類。

          使用注解定義規(guī)則

          Easy Rules提供了@Rule注解,可以將POJO轉(zhuǎn)換為規(guī)則。

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

          @Condition
          public boolean when(@Fact("fact") fact) {
          // 規(guī)則條件
          return true;
          }

          @Action(order = 1)
          public void then(Facts facts) throws Exception {
          // 規(guī)則為true時的操作1
          }

          @Action(order = 2)
          public void finally() throws Exception {
          // 規(guī)則為true時的操作2
          }
          }
          復(fù)制代碼

          @Condition注解用來標(biāo)記評估規(guī)則條件的方法,這個方法必須是public,可以有一個或多個帶@Fact注解的參數(shù),并返回一個boolean類型。只有一個方法可以用@Condition注解標(biāo)記。

          @Action注解用來標(biāo)記執(zhí)行操作的方法,規(guī)則可以有多個操作??梢允褂?code>order屬性以指定的順序執(zhí)行操作。

          使用RuleBuilder定義規(guī)則

          RuleBuilder允許你用流式API定義規(guī)則。

          Rule rule = new RuleBuilder()
          .name("myRule")
          .description("myRuleDescription")
          .priority(3)
          .when(condition)
          .then(action1)
          .then(action2)
          .build();
          復(fù)制代碼

          在本例中,conditionCondition接口的實(shí)例,action1action2Action接口的實(shí)例。

          組合規(guī)則

          Easy Rules允許從原始規(guī)則創(chuàng)建復(fù)雜的規(guī)則。一個CompositeRule由一組規(guī)則組成。組合規(guī)則是一個抽象概念,因?yàn)榻M合規(guī)則可以以不同的方式觸發(fā)。Easy Rules提供了3種CompositeRule的實(shí)現(xiàn)。

          • UnitRuleGroup:單元規(guī)則組是作為一個單元使用的組合規(guī)則,要么應(yīng)用所有規(guī)則,要么不應(yīng)用任何規(guī)則。

          • ActivationRuleGroup:激活規(guī)則組觸發(fā)第一個適用規(guī)則并忽略組中的其他規(guī)則。規(guī)則首先按照其在組中的自然順序(默認(rèn)情況下優(yōu)先級)進(jìn)行排序。

          • ConditionalRuleGroup:條件規(guī)則組將具有最高優(yōu)先級的規(guī)則作為條件,如果具有最高優(yōu)先級的規(guī)則的計(jì)算結(jié)果為true,那么將觸發(fā)其余的規(guī)則。

          組合規(guī)則可以從原始規(guī)則創(chuàng)建并像常規(guī)規(guī)則一樣注冊。

          // 從兩個原始規(guī)則創(chuàng)建組合規(guī)則
          UnitRuleGroup myUnitRuleGroup =
          new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
          myUnitRuleGroup.addRule(myRule1);
          myUnitRuleGroup.addRule(myRule2);

          // 像常規(guī)規(guī)則一樣注冊組合規(guī)則
          Rules rules = new Rules();
          rules.register(myUnitRuleGroup);

          RulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.fire(rules, someFacts);
          復(fù)制代碼

          規(guī)則優(yōu)先級

          Easy Rules中的每個規(guī)則都有一個優(yōu)先級。這表示觸發(fā)注冊規(guī)則的默認(rèn)順序。默認(rèn)情況下,值越低優(yōu)先級越高。要覆蓋此行為,您應(yīng)該重寫compareTo()方法以提供自定義優(yōu)先級策略。

          • 如果是繼承BasicRule,可以在構(gòu)造方法中指定優(yōu)先級,或者重寫getPriority()方法。

          • 如果是使用POJO定義規(guī)則,可以通過@Rule注解的priority屬性指定優(yōu)先級,或者使用@Priority注解標(biāo)記一個方法。這個方法必須是public,無參卻返回類型為Integer。

          • 如果使用RuleBuilder定義規(guī)則,可以使用RuleBuilder#priority()方法指定優(yōu)先級。

          Rules API

          Easy rules中的一組規(guī)則由rules API表示。它的使用方法如下:

          Rules rules = new Rules();
          rules.register(myRule1);
          rules.register(myRule2);
          復(fù)制代碼

          Rules表示已注冊規(guī)則的命名空間,因此,在同一命名空間下,每一個已經(jīng)注冊的規(guī)則必須有唯一的名稱。

          Rules是通過Rule#compareTo()方法進(jìn)行比較的,因此,Rule的實(shí)現(xiàn)應(yīng)該正確的實(shí)現(xiàn)compareTo()方法來確保單一空間下?lián)碛形ㄒ坏囊?guī)則名稱。

          定義事實(shí)

          Easy Rules中的一個事實(shí)是由Fact表示的:

          public class Fact<T> {
          private final String name;
          private final T value;
          }
          復(fù)制代碼

          一個事實(shí)有一個名稱和一個值,兩者都不能為null。另一方面,Facts API 表示一組事實(shí)并充當(dāng)事實(shí)的命名空間。這意味著,在一個Facts實(shí)例中,事實(shí)必須有唯一的名稱。

          下面是一個如何定義事實(shí)的例子:

          Fact fact = new Fact("foo", "bar");
          Facts facts = new Facts();
          facts.add(fact);
          復(fù)制代碼

          你也可以使用一個更短的版本,用put方法創(chuàng)建命名的事實(shí),如下所示:

          Facts facts = new Facts();
          facts.put("foo", "bar");
          復(fù)制代碼

          可以使用@Fact注解將事實(shí)注入到規(guī)則的條件和操作方法中。在以下規(guī)則中,rain事實(shí)被注入到itRains方法的rain參數(shù)中:

          @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
          }

          }
          復(fù)制代碼

          類型為Facts的參數(shù)將被注入所有已知的事實(shí)。

          注意:

          • 如果條件方法中缺少注入的事實(shí),引擎將記錄一個警告,并認(rèn)為條件被計(jì)算為false

          • 如果動作方法中缺少注入的事實(shí),則不會執(zhí)行該動作,并且拋出org.jeasy.rules.core.NoSuchFactException異常。

          定義規(guī)則引擎

          Easy Rules提供了RulesEngine接口的兩種實(shí)現(xiàn):

          • DefaultRulesEngine:根據(jù)規(guī)則的自然順序(默認(rèn)為優(yōu)先級)應(yīng)用規(guī)則。

          • InferenceRulesEngine:在已知的事實(shí)上不斷地應(yīng)用規(guī)則,直到?jīng)]有更多的規(guī)則可用。

          創(chuàng)建規(guī)則引擎

          可以使用構(gòu)造方法創(chuàng)建規(guī)則引擎。

          RulesEngine rulesEngine = new DefaultRulesEngine();
          // or
          RulesEngine rulesEngine = new InferenceRulesEngine();
          復(fù)制代碼

          可以按如下方式觸發(fā)已注冊的規(guī)則。

          rulesEngine.fire(rules, facts);
          復(fù)制代碼

          規(guī)則引擎參數(shù)

          Easy Rules引擎可以配置以下參數(shù):

          參數(shù)類型默認(rèn)值
          rulePriorityThresholdintMaxInt
          skipOnFirstAppliedRulebooleanfalse
          rulePriorityThresholdintfalse
          skipOnFirstFailedRulebooleanfalse
          skipOnFirstNonTriggeredRulebooleanfalse
          • skipOnFirstAppliedRule:當(dāng)一個規(guī)則成功應(yīng)用時,跳過余下的規(guī)則。

          • skipOnFirstFailedRule:當(dāng)一個規(guī)則失敗時,跳過余下的規(guī)則。

          • skipOnFirstNonTriggeredRule:當(dāng)一個規(guī)則未觸發(fā)時,跳過余下的規(guī)則。

          • rulePriorityThreshold:當(dāng)優(yōu)先級超過指定的閾值時,跳過余下的規(guī)則。

          可以使用RulesEngineParameters API指定這些參數(shù):

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

          RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
          復(fù)制代碼

          如果你想從你的引擎中獲取參數(shù),你可以使用以下代碼段:

          RulesEngineParameters parameters = myEngine.getParameters();
          復(fù)制代碼

          這允許在創(chuàng)建引擎參數(shù)后重新設(shè)置引擎參數(shù)。

          定義規(guī)則監(jiān)聽器

          可以通過RuleListener API來監(jiān)聽規(guī)則執(zhí)行事件:

          public interface RuleListener {

          /**
          * 在評估規(guī)則之前觸發(fā)。
          *
          * @param rule 正在被評估的規(guī)則
          * @param facts 評估規(guī)則之前的已知事實(shí)
          * @return 如果規(guī)則應(yīng)該評估,則返回true,否則返回false
          */

          default boolean beforeEvaluate(Rule rule, Facts facts) {
          return true;
          }

          /**
          * 在評估規(guī)則之后觸發(fā)
          *
          * @param rule 評估之后的規(guī)則
          * @param facts 評估規(guī)則之后的已知事實(shí)
          * @param evaluationResult 評估結(jié)果
          */

          default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }

          /**
          * 運(yùn)行時異常導(dǎo)致條件評估錯誤時觸發(fā)
          *
          * @param rule 評估之后的規(guī)則
          * @param facts 評估時的已知事實(shí)
          * @param exception 條件評估時發(fā)生的異常
          */

          default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }

          /**
          * 在規(guī)則操作執(zhí)行之前觸發(fā)。
          *
          * @param rule 當(dāng)前的規(guī)則
          * @param facts 執(zhí)行規(guī)則操作時的已知事實(shí)
          */

          default void beforeExecute(Rule rule, Facts facts) { }

          /**
          * 在規(guī)則操作成功執(zhí)行之后觸發(fā)
          *
          * @param rule t當(dāng)前的規(guī)則
          * @param facts 執(zhí)行規(guī)則操作時的已知事實(shí)
          */

          default void onSuccess(Rule rule, Facts facts) { }

          /**
          * 在規(guī)則操作執(zhí)行失敗時觸發(fā)
          *
          * @param rule 當(dāng)前的規(guī)則
          * @param facts 執(zhí)行規(guī)則操作時的已知事實(shí)
          * @param exception 執(zhí)行規(guī)則操作時發(fā)生的異常
          */

          default void onFailure(Rule rule, Facts facts, Exception exception) { }

          }
          復(fù)制代碼

          可以實(shí)現(xiàn)這個接口來提供自定義行為,以便在每個規(guī)則之前/之后執(zhí)行。要注冊監(jiān)聽器,請使用以下代碼段:

          DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.registerRuleListener(myRuleListener);
          復(fù)制代碼

          可以注冊任意數(shù)量的偵聽器,它們將按照注冊順序執(zhí)行。

          注意:當(dāng)使用組合規(guī)則時,監(jiān)聽器是圍繞組合規(guī)則調(diào)用的。

          定義規(guī)則引擎監(jiān)聽器

          可以通過RulesEngineListener API來監(jiān)聽規(guī)則引擎的執(zhí)行事件:

          public interface RulesEngineListener {

          /**
          * 在執(zhí)行規(guī)則集之前觸發(fā)
          *
          * @param rules 要觸發(fā)的規(guī)則集
          * @param facts 觸發(fā)規(guī)則前的事實(shí)
          */

          default void beforeEvaluate(Rules rules, Facts facts) { }

          /**
          * 在執(zhí)行規(guī)則集之后觸發(fā)
          *
          * @param rules 要觸發(fā)的規(guī)則集
          * @param facts 觸發(fā)規(guī)則前的事實(shí)
          */

          default void afterExecute(Rules rules, Facts facts) { }
          }
          復(fù)制代碼

          RulesEngineListener允許我們在觸發(fā)整個規(guī)則集之前/之后提供自定義行為??梢允褂萌缦路绞阶员O(jiān)聽器。

          DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.registerRulesEngineListener(myRulesEngineListener);
          復(fù)制代碼

          可以注冊任意數(shù)量的監(jiān)聽器,它們將按照注冊順序執(zhí)行。

          表達(dá)式語言(EL)支持

          Easy Rules支持用MVEL、SpELJEXL定義規(guī)則。

          EL提供者注意事項(xiàng)

          EL提供者在行為上有一些區(qū)別。例如,當(dāng)一個事實(shí)在條件中缺失時,MVEL拋出一個異常,而SpEL將忽略它并返回false。因此,在選擇Easy Rules使用哪個EL之前,你應(yīng)該了解這些差異。

          通過編程的方式定義規(guī)則

          條件、動作和規(guī)則分別由MVELCondition/SpELCondition/JexlCondition、MVELAction/SpELAction/JexlActionMVELRule/SpELRule/JexlRule類表示。下面是一個使用MVEL定義規(guī)則的例子:

          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);");
          復(fù)制代碼

          通過規(guī)則描述文件定義規(guī)則

          可以使用規(guī)則描述文件定義規(guī)則,使用MVELRuleFactory/SpELRuleFactory/JexlRuleFactory來從描述符文件創(chuàng)建規(guī)則。下面是一個在alcohol-rule.yml中以YAML格式定義的MVEL規(guī)則示例:

          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");"
          復(fù)制代碼
          MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
          MVELRule alcoholRule = ruleFactory.createRule(new FileReader("alcohol-rule.yml"));
          復(fù)制代碼

          還可以使用一個文件創(chuàng)建多個規(guī)則。

          ---
          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!");"
          復(fù)制代碼

          可以使用如下方式將這些規(guī)則加載到rules對象中。

          MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
          Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));
          復(fù)制代碼

          Easy Rules還支持從JSON描述符加載規(guī)則。具體參考文檔,這里不做展開。

          規(guī)則定義中的錯誤處理

          關(guān)于條件中不正確表達(dá)式的引擎行為

          對于條件求值過程中可能發(fā)生的任何運(yùn)行時異常(丟失事實(shí)、表達(dá)式中輸入錯誤等),引擎將記錄一個警告,并認(rèn)為條件求值為false。可以使用RuleListener#onEvaluationError來監(jiān)聽評估錯誤。

          關(guān)于操作中不正確表達(dá)式的引擎行為

          對于任何在執(zhí)行操作時可能發(fā)生的運(yùn)行時異常(丟失事實(shí)、表達(dá)式中輸入錯誤等),該操作將不會執(zhí)行,引擎將記錄一個錯誤??梢允褂?code>RuleListener#onFailure來監(jiān)聽操作執(zhí)行異常。當(dāng)一個規(guī)則失敗時,引擎將移動到下一個規(guī)則,除非設(shè)置了skipOnFirstFailedRule參數(shù)。

          實(shí)際栗子

          本栗子使用Easy Rules實(shí)現(xiàn)FizzBuzz應(yīng)用程序。FizzBuzz是一個簡單的應(yīng)用程序,需要從1數(shù)到100,并且:

          • 如果數(shù)字是5的倍數(shù),則打印“fizz”

          • 如果數(shù)字是7的倍數(shù),請打印“buzz”

          • 如果數(shù)字是5和7的倍數(shù),請打印“fizzbuzz”

          • 否則打印數(shù)字本身

          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();
          }
          }
          復(fù)制代碼

          我們將為每個需求編寫一條規(guī)則:

          @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;
          }
          }
          復(fù)制代碼
          @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;
          }
          }
          復(fù)制代碼
          public class FizzBuzzRule extends UnitRuleGroup {

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

          @Override
          public int getPriority() {
          return 0;
          }
          }
          復(fù)制代碼
          @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;
          }
          }
          復(fù)制代碼

          以下是對這些規(guī)則的一些解釋:

          • FizzRuleBuzzRule很簡單,它們會檢查輸入是5的倍數(shù)還是7的倍數(shù),然后打印結(jié)果。

          • FizzBuzzRule是一個組合規(guī)則。通過FizzRuleBuzzRule創(chuàng)建?;愡x擇為UnitRuleGroup,要么滿足并應(yīng)用這兩個規(guī)則,要么什么都不應(yīng)用。

          • NonFizzBuzzRule是既不是5的倍數(shù)也不是7的倍數(shù)時的規(guī)則。

          請注意,我們已經(jīng)設(shè)置了優(yōu)先級,因此規(guī)則的觸發(fā)順序與Java示例中的示例相同。

          然后,我們必須將這些規(guī)則注冊到一個規(guī)則集中,并使用一個規(guī)則引擎來觸發(fā)它們:

          public class FizzBuzzWithEasyRules {
          public static void main(String[] args) {
          // 創(chuàng)建規(guī)則引擎
          RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
          RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

          // 創(chuàng)建規(guī)則
          Rules rules = new Rules();
          rules.register(new FizzRule());
          rules.register(new BuzzRule());
          rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
          rules.register(new NonFizzBuzzRule());

          // 觸發(fā)規(guī)則
          Facts facts = new Facts();
          for (int i = 1; i <= 100; i++) {
          facts.put("number", i);
          fizzBuzzEngine.fire(rules, facts);
          System.out.println();
          }
          }
          }
          復(fù)制代碼

          注意,我們已經(jīng)設(shè)置了skipOnFirstAppliedRule參數(shù),以便在成功應(yīng)用規(guī)則時跳過后續(xù)的規(guī)則。


          作者:夜盡天明_
          鏈接:https://juejin.cn/post/7048917724126248967
          來源:稀土掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。



          瀏覽 117
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人大片 | 日本黄色电影免费看 | 人妻斩蜜桃视频网站 | 大香蕉手机视频 | 就爱操b网在线 |