国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
動(dòng)態(tài)調(diào)用動(dòng)態(tài)語(yǔ)言,第 2 部分: 在運(yùn)行時(shí)尋找、執(zhí)行和修改腳本

動(dòng)態(tài)調(diào)用動(dòng)態(tài)語(yǔ)言,第 2 部分: 在運(yùn)行時(shí)尋找、執(zhí)行和修改腳本

動(dòng)態(tài)地修改業(yè)務(wù)邏輯



級(jí) 別: 中級(jí)

Tom McQueeney (tom.mcqueeney@gmail.com), 首席技術(shù)顧問(wèn), Idea Integration

2007 年 9 月 24 日

Java™ 腳本編程 API(Java scripting API)是 Java SE 6 中新增的,它向后兼容 Java SE 5,支持以一種簡(jiǎn)單且統(tǒng)一的方式在運(yùn)行時(shí)從 Java 應(yīng)用程序調(diào)用數(shù)十種腳本語(yǔ)言。本系列的 第 1 部分 介紹了這個(gè) API 的基本特性。第 2 部分進(jìn)一步講解它的功能,演示如何在無(wú)需停止并重新啟動(dòng)應(yīng)用程序的情況下,在運(yùn)行時(shí)執(zhí)行外部 Ruby、Groovy 和 JavaScript 腳本以修改業(yè)務(wù)邏輯。

Java SE 6 中新增的 Java 腳本編程 API 為運(yùn)行用各種動(dòng)態(tài)語(yǔ)言編寫的外部程序(并與之共享代碼和數(shù)據(jù))提供了一種統(tǒng)一的方式。Java 應(yīng)用程序與腳本語(yǔ)言的強(qiáng)大功能和靈活性相結(jié)合是非常有意義的,尤其是在腳本語(yǔ)言能夠更簡(jiǎn)潔地執(zhí)行某些任務(wù)的情況下。但是,Java 腳本編程 API 不僅僅能夠以一種統(tǒng)一的方式在 Java 程序中添加許多種腳本語(yǔ)言代碼,它還支持在運(yùn)行時(shí)尋找、讀取和執(zhí)行腳本??梢岳眠@些動(dòng)態(tài)功能在程序運(yùn)行時(shí)修改腳本,從而修改應(yīng)用程序的邏輯。本文演示如 何使用 Java 腳本編程 API 調(diào)用外部腳本來(lái)動(dòng)態(tài)地修改程序邏輯。還要討論在將一種或多種腳本語(yǔ)言集成到 Java 應(yīng)用程序中時(shí)可能遇到的問(wèn)題。

第 1 部分 用一個(gè) Hello World 風(fēng)格的應(yīng)用程序介紹了 Java 腳本編程 API。這里將展示一個(gè)更真實(shí)的示例應(yīng)用程序,這個(gè)程序使用腳本編程 API 創(chuàng)建一個(gè)動(dòng)態(tài)的規(guī)則引擎,它可以以外部 Groovy、JavaScript 和 Ruby 腳本的形式定義規(guī)則。這些規(guī)則決定申請(qǐng)人是否符合某些抵押產(chǎn)品的住宅貸款條件。如果用腳本語(yǔ)言定義業(yè)務(wù)規(guī)則,規(guī)則就更容易編寫,也便于非程序員(比如貸款 審查員)閱讀。通過(guò)使用 Java 腳本編程 API 將這些規(guī)則放在程序之外,還可以支持在應(yīng)用程序運(yùn)行時(shí)修改規(guī)則和添加新的抵押產(chǎn)品。

真實(shí)的應(yīng)用程序

這個(gè)示例應(yīng)用程序?yàn)樘摌?gòu)的 Shaky Ground Financial 公司處理住宅貸款申請(qǐng)。住宅抵押行業(yè)不斷地推出新的貸款產(chǎn)品,還常常修改對(duì)合格申請(qǐng)人的限制規(guī)則。Shaky Ground 公司不但希望能夠快速地添加和刪除抵押產(chǎn)品,還需要快速修改業(yè)務(wù)規(guī)則,從而控制哪些人符合產(chǎn)品的貸款條件。

Java 腳本編程 API 正好能夠滿足這種需求。這個(gè)應(yīng)用程序由一個(gè) ScriptMortgageQualifier 類組成,這個(gè)類負(fù)責(zé)判斷打算購(gòu)買某一資產(chǎn)的貸款人是否符合給定的抵押貸款產(chǎn)品的條件。清單 1 給出這個(gè)類。


清單 1. ScriptMortgageQualifier 類
                        // Imports and Javadoc not shown.                        public class ScriptMortgageQualifier {                        private ScriptEngineManager scriptEngineManager = new ScriptEngineManager();                        public MortgageQualificationResult qualifyMortgage(                        Borrower borrower,                        Property property,                        Loan loan,                        File mortgageRulesFile                        ) throws FileNotFoundException, IllegalArgumentException, ScriptException                        {                        ScriptEngine scriptEngine = getEngineForFile(mortgageRulesFile);                        if (scriptEngine == null) {                        throw new IllegalArgumentException(                        "No script engine on classpath to handle file: " + mortgageRulesFile                        );                        }                        // Make params accessible to scripts by adding to engine's context.                        scriptEngine.put("borrower", borrower);                        scriptEngine.put("property", property);                        scriptEngine.put("loan", loan);                        // Make return-value object available to scripts.                        MortgageQualificationResult scriptResult = new MortgageQualificationResult();                        scriptEngine.put("result", scriptResult);                        // Add an object scripts can call to exit early from processing.                        scriptEngine.put("scriptExit", new ScriptEarlyExit());                        try {                        scriptEngine.eval(new FileReader(mortgageRulesFile));                        } catch (ScriptException se) {                        // Re-throw exception unless it's our early-exit exception.                        if (se.getMessage() == null ||                        !se.getMessage().contains("ScriptEarlyExitException")                        ) {                        throw se;                        }                        // Set script result message if early-exit exception embedded.                        Throwable t = se.getCause();                        while (t != null) {                        if (t instanceof ScriptEarlyExitException) {                        scriptResult.setMessage(t.getMessage());                        break;                        }                        t = t.getCause();                        }                        }                        return scriptResult;                        }                        /** Returns a script engine based on the extension of the given file. */                        private ScriptEngine getEngineForFile(File f) {                        String fileExtension = getFileExtension(f);                        return scriptEngineManager.getEngineByExtension(fileExtension);                        }                        /** Returns the file's extension, or "" if the file has no extension */                        private String getFileExtension(File file) {                        String scriptName = file.getName();                        int dotIndex = scriptName.lastIndexOf('.');                        if (dotIndex != -1) {                        return scriptName.substring(dotIndex + 1);                        } else {                        return "";                        }                        }                        /** Internal exception so ScriptEarlyExit.exit can exit scripts early */                        private static class ScriptEarlyExitException extends Exception {                        public ScriptEarlyExitException(String msg) {                        super(msg);                        }                        }                        /** Object passed to all scripts so they can indicate an early exit. */                        private static class ScriptEarlyExit {                        public void noMessage() throws ScriptEarlyExitException {                        throw new ScriptEarlyExitException(null);                        }                        public void withMessage(String msg) throws ScriptEarlyExitException {                        throw new ScriptEarlyExitException(msg);                        }                        }                        }

這個(gè)類相當(dāng)簡(jiǎn)單,因?yàn)樗阉袠I(yè)務(wù)決策任務(wù)都委派給了外部腳本。每個(gè)腳本表示一個(gè)抵押產(chǎn)品。每個(gè)腳本文件中的代碼包含一系 列業(yè)務(wù)規(guī)則,這些規(guī)則定義了符合這種抵押產(chǎn)品要求的貸款人類型、資產(chǎn)類型和貸款類型。由于采用了這種方式,只需在腳本目錄中添加新的腳本文件,就可以添加 新的抵押產(chǎn)品。如果某一抵押產(chǎn)品的業(yè)務(wù)邏輯改變了,那么只需更新腳本來(lái)反映規(guī)則的變化。

通過(guò)用腳本語(yǔ)言編寫抵押產(chǎn)品業(yè)務(wù)規(guī)則,可以展示 Java 腳本編程 API 的功能。這個(gè)程序還說(shuō)明有時(shí)候腳本語(yǔ)言代碼更容易閱讀、修改和理解,即使是非程序員也可以掌握腳本代碼。







ScriptMortgageQualifier 類的工作方式

ScriptMortgageQualifier 中的主要方法是 qualifyMortgage()。 這個(gè)方法通過(guò)參數(shù)接受以下信息:

  • 貸款人
  • 要購(gòu)買的資產(chǎn)
  • 貸款細(xì)節(jié)
  • 一個(gè) File 對(duì)象,其中包含要執(zhí)行的腳本

這個(gè)方法的任務(wù)是用業(yè)務(wù)實(shí)體參數(shù)運(yùn)行腳本文件并返回一個(gè)結(jié)果對(duì)象,這個(gè)對(duì)象指出貸款人是否符合抵押產(chǎn)品的要求。這里沒(méi)有給 出 BorrowerPropertyLoan 的代碼。它們只是簡(jiǎn)單的實(shí)體類,可以在本文的源代碼中找到它們的代碼(見(jiàn) 下 載)。

為了找到一個(gè) ScriptEngine 來(lái)運(yùn)行腳本文件,qualifyMortgage() 方法使用了 getEngineForFile() 內(nèi)部 helper 方法。getEngineForFile() 方法使用 scriptEngineManager 實(shí)例變量(這個(gè)變量在類實(shí)例化時(shí)被設(shè)置為一個(gè) ScriptEngineManager) 尋找能夠處理具有給定文件擴(kuò)展名的腳本的腳本引擎。getEngineForFile() 方法使用 ScriptEngineManager.getEngineByExtension() 方法(見(jiàn) 清 單 1 中的粗體代碼)搜索并返回 ScriptEngine。

找到腳本引擎之后,qualifyMortgage() 將它接收的實(shí)體參數(shù)綁定到引擎的上下文,從而讓腳本能夠使用這些參數(shù)。前三個(gè) scriptEngine.put() 調(diào)用(也是粗體代碼)執(zhí)行這些綁定。第四個(gè) scriptEngine.put() 調(diào)用創(chuàng)建一個(gè)新的 MortgageQualificationResult Java 對(duì)象并通過(guò)腳本引擎共享它。腳本可以通過(guò)設(shè)置這個(gè)對(duì)象的屬性將它的運(yùn)行結(jié)果返回給 Java 應(yīng)用程序,qualifyMortgage() 將返回這個(gè)共享對(duì)象。腳本使用 result 全局變量訪問(wèn)這個(gè) Java 對(duì)象。每個(gè)腳本負(fù)責(zé)使用這個(gè)共享對(duì)象將自己的結(jié)果返回給 Java 應(yīng)用程序。

最后一個(gè) scriptEngine.put() 調(diào)用讓腳本可以通過(guò) scriptExit 變量使用一個(gè)內(nèi)部 helper 類(ScriptEarlyExit,見(jiàn) 清 單 1)的實(shí)例。ScriptEarlyExit 定義了兩個(gè)簡(jiǎn)單的方法 —— withMessage()noMessage(),它們惟一的作用是拋出一個(gè)異常。如果腳本調(diào)用 scriptExit.withMessage()scriptExit.noMessage(),那么方法拋出一個(gè) ScriptEarlyExitException 異常。腳本引擎會(huì)捕捉這個(gè)異常、終止腳本處理并向調(diào)用腳本的 eval() 方法拋出一個(gè) ScriptException 異常。

通過(guò)以這種迂回的方式提前退出腳本,就可以以一致的方式從函數(shù)或方法外的腳本處理過(guò)程返回。并非所有腳本語(yǔ)言都提供了這種 方式所需的語(yǔ)句。例如,在 JavaScript 中,在執(zhí)行高層代碼時(shí)(這個(gè)示例應(yīng)用程序中的抵押處理腳本正是采用這種構(gòu)造方式),無(wú)法使用 return 語(yǔ)句。共享對(duì)象 scriptExit 解決了這個(gè)問(wèn)題,一旦腳本判斷出貸款人不符合抵押產(chǎn)品的要求,用任何語(yǔ)言編寫的腳本都可以通過(guò)這個(gè)對(duì)象退出。

qualifyMortgage 中,對(duì)腳本引擎的 eval 方法的調(diào)用(見(jiàn)粗體代碼)使用一個(gè) try/catch 塊捕捉 ScriptException 異常。catch 塊中的代碼檢查 ScriptException 錯(cuò)誤消息,從而判斷這個(gè)腳本異常是由 ScriptEarlyExitException 造成的,還是由真正的腳本錯(cuò)誤造成的。如果錯(cuò)誤消息包含名稱 ScriptEarlyExitException,那么代碼就 認(rèn)為一切正常并忽略這個(gè)腳本異常。

這種在 Java 腳本編程 API 的腳本異常錯(cuò)誤消息中搜索字符串的技術(shù)有點(diǎn)兒笨拙,但這對(duì)于本示例中使用的 Groovy、JavaScript 和 Ruby 語(yǔ)言解釋器是有效的。如果所有腳本語(yǔ)言實(shí)現(xiàn)將從調(diào)用的 Java 代碼拋出的 Java 異常添加到異常堆棧中,那么會(huì)更方便,這樣就可以使用 Throwable.getCause() 方法獲取這些異常。JRuby 和 Groovy 等解釋器會(huì)這樣做,但是內(nèi)置的 Rhino JavaScript 解釋器并不這樣做。







運(yùn)行代碼:ScriptMortgageQualifierRunner

為了測(cè)試 ScriptMortgageQualifier 類,將使用測(cè)試數(shù)據(jù)表示四個(gè)貸款人、貸款人打算購(gòu)買的一項(xiàng)資產(chǎn)和一筆抵押貸款。我們將用一個(gè)貸款人、資產(chǎn)和貸款運(yùn)行所有三個(gè)腳本,檢查貸款人是否滿足腳本 所代表的抵押產(chǎn)品的業(yè)務(wù)規(guī)則。

清單 2 給出 ScriptMortgageQualifierRunner 程序的部分代碼,我們將用這個(gè)程序創(chuàng)建測(cè)試對(duì)象、在一個(gè)目錄中尋找腳本文件并通過(guò) 清 單 1 中的 ScriptMortgageQualifier 類運(yùn)行它們。為了節(jié)省篇幅,這里沒(méi)有給出這個(gè)程序的 createGoodBorrower()、createAverageBorrower()createInvestorBorrower()、createRiskyBorrower()、createProperty()createLoan() helper 方法。這些方法的作用僅僅是創(chuàng)建實(shí)體對(duì)象并設(shè)置測(cè)試所需的值。在 下 載 一節(jié)中可以獲得所有方法的完整源代碼。


清單 2. ScriptMortgageQualifierRunner 程序
                        // Imports and some helper methods not shown.                        public class ScriptMortgageQualifierRunner {                        private static File scriptDirectory;                        private static Borrower goodBorrower = createGoodBorrower();                        private static Borrower averageBorrower = createAverageBorrower();                        private static Borrower investorBorrower = createInvestorBorrower();                        private static Borrower riskyBorrower = createRiskyBorrower();                        private static Property property = createProperty();                        private static Loan loan = createLoan();                        /**                        * Main method to create a File for the directory name on the command line,                        * then call the run method if that directory exists.                        */                        public static void main(String[] args) {                        if (args.length > 0 && args[0].contains("-help")) {                        printUsageAndExit();                        }                        String dirName;                        if (args.length == 0) {                        dirName = "."; // Current directory.                        } else {                        dirName = args[0];                        }                        scriptDirectory = new File(dirName);                        if (!scriptDirectory.exists() || !scriptDirectory.isDirectory()) {                        printUsageAndExit();                        }                        run();                        }                        /**                        * Determines mortgage loan-qualification status for four test borrowers by                        * processing all script files in the given directory. Each script will determine                        * whether the given borrower is qualified for a particular mortgage type                        */                        public static void run() {                        ScriptMortgageQualifier mortgageQualifier = new ScriptMortgageQualifier();                        for(;;) { // Requires Ctrl-C to exit                        runQualifications(mortgageQualifier, goodBorrower, loan, property);                        runQualifications(mortgageQualifier, averageBorrower, loan, property);                        loan.setDownPayment(30000.0); // Reduce down payment to 10%                        runQualifications(mortgageQualifier, investorBorrower, loan, property);                        loan.setDownPayment(10000.0); // Reduce down payment to 3 1/3%                        runQualifications(mortgageQualifier, riskyBorrower, loan, property);                        waitOneMinute();                        }                        }                        /**                        * Reads all script files in the scriptDirectory and runs them with this borrower's                        * information to see if he/she qualifies for each mortgage product.                        */                        private static void runQualifications(                        ScriptMortgageQualifier mortgageQualifier,                        Borrower borrower,                        Loan loan,                        Property property                        ) {                        for (File scriptFile : getScriptFiles(scriptDirectory)) {                        // Print info about the borrower, loan and property.                        System.out.println("Processing file: " + scriptFile.getName());                        System.out.println("  Borrower: " + borrower.getName());                        System.out.println("  Credit score: " + borrower.getCreditScore());                        System.out.println("  Sales price: " + property.getSalesPrice());                        System.out.println("  Down payment: " + loan.getDownPayment());                        MortgageQualificationResult result = null;                        try {                        // Run the script rules for this borrower on the loan product.                        result = mortgageQualifier.qualifyMortgage(                        borrower, property, loan, scriptFile                        );                        } catch (FileNotFoundException fnfe) {                        System.out.println(                        "Can't read script file: " + fnfe.getMessage()                        );                        } catch (IllegalArgumentException e) {                        System.out.println(                        "No script engine available to handle file: " +                        scriptFile.getName()                        );                        } catch (ScriptException e) {                        System.out.println(                        "Script '" + scriptFile.getName() +                        "' encountered an error: " + e.getMessage()                        );                        }                        if (result == null) continue; // Must have hit exception.                        // Print results.                        System.out.println(                        "* Mortgage product: " + result.getProductName() +                        ", Qualified? " + result.isQualified() +                        "\n* Interest rate: " + result.getInterestRate() +                        "\n* Message: " + result.getMessage()                        );                        System.out.println();                        }                        }                        /** Returns files with a '.' other than as the first or last character. */                        private static File[] getScriptFiles(File directory) {                        return directory.listFiles(new FilenameFilter() {                        public boolean accept(File dir, String name) {                        int indexOfDot = name.indexOf('.');                        // Ignore files w/o a dot, or with dot as first or last char.                        if (indexOfDot < 1 || indexOfDot == (name.length() - 1)) {                        return false;                        } else {                        return true;                        }                        }                        });                        }                        private static void waitOneMinute() {                        System.out.println(                        "\nSleeping for one minute before reprocessing files." +                        "\nUse Ctrl-C to exit..."                        );                        System.out.flush();                        try {                        Thread.sleep(1000 * 60);                        } catch (InterruptedException e) {                        System.exit(1);                        }                        }                        }                        

ScriptMortgageQualifierRunner 中的 main() 方法搜索命令行上提供的腳本文件目錄,如果這個(gè)目錄存在,就用目錄的 File 對(duì)象設(shè)置一個(gè)靜態(tài)變量,并調(diào)用 run() 方法執(zhí)行進(jìn)一步的處理。

run() 方法對(duì) 清 單 1 中的 ScriptMortgageQualifier 類進(jìn)行實(shí)例化,然后用一個(gè)無(wú)限循環(huán)調(diào)用內(nèi)部方法 runQualifications(), 測(cè)試四個(gè)貸款人/貸款場(chǎng)景。這個(gè)無(wú)限循環(huán)模擬連續(xù)的抵押申請(qǐng)?zhí)幚?。這個(gè)循環(huán)讓我們可以在腳本目錄中添加或修改腳本文件(抵押貸款產(chǎn)品),這些修改會(huì)動(dòng)態(tài)地 生效,不需要停止應(yīng)用程序。因?yàn)檫@個(gè)應(yīng)用程序的業(yè)務(wù)邏輯放在外部腳本中,所以可以在運(yùn)行時(shí)動(dòng)態(tài)地修改業(yè)務(wù)邏輯。

對(duì)于腳本目錄中的每個(gè)腳本文件,runQualifications() helper 方法分別調(diào)用 ScriptMortgageQualifer.qualifyMortgage 一次。每個(gè)調(diào)用前面有一系列打印語(yǔ)句,它們輸出腳本文件和貸款人的相關(guān)信息;調(diào)用之后,用打印語(yǔ)句顯示結(jié)果,即貸款人是否符合抵押產(chǎn)品的要求。腳本代碼使 用共享的 MortgageQualificationResult Java 對(duì)象返回其結(jié)果,檢查這個(gè)對(duì)象的屬性就可以判斷貸款人是否合格。

本文的源代碼 ZIP 文件包含三個(gè)用 Groovy、JavaScript 和 Ruby 編寫的腳本文件示例。它們分別代表一種標(biāo)準(zhǔn)的 30 年期固定利率抵押貸款產(chǎn)品。腳本中的代碼判斷貸款人是否符合這種抵押類型的要求,然后通過(guò)調(diào)用腳本引擎 put() 方法中提供的共享全局變量 result 來(lái)返回結(jié)果。全局變量 resultMortgageQualificationResult 類的實(shí)例(部分代碼見(jiàn)清單 3)。


清單 3. MortgageQualificationResult 類
                        public class MortgageQualificationResult {                        private boolean qualified;                        private double interestRate;                        private String message;                        private String productName;                        // .. Standard setters and getters not shown.                        }

腳本設(shè)置 result 的屬性,從而指出貸款人是否符合抵押貸款的要求以及應(yīng)該采用的利率。腳本可以通過(guò) messageproductName 屬性指出導(dǎo)致貸款人不合格的原因和返回相關(guān)的產(chǎn)品名稱。







腳本文件

在給出 ScriptMortgageQualifierRunner 的輸出之前,我們先看看這個(gè)程序運(yùn)行的 Groovy、JavaScript 和 Ruby 腳本文件。Groovy 腳本中的業(yè)務(wù)邏輯定義了一種條件相當(dāng)寬松的抵押產(chǎn)品,同時(shí)由于金融風(fēng)險(xiǎn)比較高,因此利率比較高。JavaScript 腳本代表一種政府擔(dān)保的抵押貸款,這種貸款要求貸款人必須滿足最大收入和其他限制。Ruby 腳本定義的抵押產(chǎn)品業(yè)務(wù)規(guī)則要求貸款人有良好的信用記錄,這些人要支付足夠的首付款,這種抵押貸款的利率比較低。

清單 4 給出 Groovy 腳本,即使您不了解 Groovy,也應(yīng)該能夠看懂這個(gè)腳本。


清單 4. Groovy 抵押腳本
                        /*                        This Groovy script defines the "Groovy Mortgage" product.                        This product is relaxed in its requirements of borrowers.                        There is a higher interest rate to make up for the looser standard.                        All borrowers will be approved if their credit history is good, they can                        make a down payment of at least 5%, and they either earn more than                        $2,000/month or have a net worth (assets minus liabilities) of $25,000.                        */                        // Our product name.                        result.productName = 'Groovy Mortgage'                        //  Check for the minimum income and net worth                        def netWorth = borrower.totalAssets - borrower.totalLiabilities                        if (borrower.monthlyIncome < 2000 && netWorth < 25000) {                        scriptExit.withMessage "Low monthly income of ${borrower.monthlyIncome}" +                        ' requires a net worth of at least $25,000.'                        }                        def downPaymentPercent = loan.downPayment / property.salesPrice * 100                        if (downPaymentPercent < 5) {                        scriptExit.withMessage 'Down payment of ' +                        "${String.format('%1$.2f', downPaymentPercent)}% is insufficient." +                        ' 5% minimum required.'                        }                        if (borrower.creditScore < 600) {                        scriptExit.withMessage 'Credit score of 600 required.'                        }                        // Everyone else qualifies. Find interest rate based on down payment percent.                        result.qualified = true                        result.message = 'Groovy! You qualify.'                        switch (downPaymentPercent) {                        case 0..5:   result.interestRate = 0.08; break                        case 6..10:  result.interestRate = 0.075; break                        case 11..15: result.interestRate = 0.07; break                        case 16..20: result.interestRate = 0.065; break                        default:     result.interestRate = 0.06; break                        }                        

請(qǐng)注意全局變量 result、borrower、loanproperty,腳本使用這些變量訪問(wèn)和設(shè)置共享 Java 對(duì)象中的值。這些變量名是通過(guò)調(diào)用 ScriptEngine.put() 方法設(shè)置的。

還要注意 result.productName = 'Groovy Mortgage' 這樣的 Groovy 語(yǔ)句。這個(gè)語(yǔ)句似乎是直接設(shè)置 MortgageQualificationResult 對(duì)象的字符串屬性 productName,但是,清 單 3 清楚地說(shuō)明它是一個(gè)私有的實(shí)例變量。這并不 表示 Java 腳本編程 API 允許違反封裝規(guī)則,而是說(shuō)明通過(guò)使用 Java 腳本編程 API,Groovy 和大多數(shù)其他腳本語(yǔ)言解釋器可以很好地操作共享的 Java 對(duì)象。如果一個(gè) Groovy 語(yǔ)句嘗試設(shè)置或讀取 Java 對(duì)象的私有屬性值,Groovy 就會(huì)尋找并使用 JavaBean 風(fēng)格的公共 settergetter 方法。例如,語(yǔ)句 result.productName = 'Groovy Mortgage' 會(huì)自動(dòng)轉(zhuǎn)換為適當(dāng)?shù)?Java 語(yǔ)句:result.setProductName("Groovy Mortgage")。這個(gè) Java setter 語(yǔ)句也是有效的 Groovy 代碼,可以在腳本中使用,但是直接使用屬性賦值語(yǔ)句更符合 Groovy 的風(fēng)格。

現(xiàn)在看看清單 5 中的 JavaScript 抵押產(chǎn)品腳本。這個(gè) JavaScript 腳本代表一種政府擔(dān)保的貸款,政府支持這種貸款是為了提高公民的住宅擁有率。所以,業(yè)務(wù)規(guī)則要求這是貸款人購(gòu)買的第一套住宅,而且貸款人打算在此居住,而 不是出租獲利。


清單 5. JavaScript 抵押腳本
                        /**                        * This script defines the "JavaScript FirstTime Mortgage" product.                        * It is a government-sponsored mortgage intended for low-income, first-time                        * home buyers without a lot of assets who intend to live in the home.                        * Bankruptcies and bad (but not terrible!) credit are OK.                        */                        result.productName = 'JavaScript FirstTime Mortgage'                        if (!borrower.intendsToOccupy) {                        result.message = 'This mortgage is not intended for investors.'                        scriptExit.noMessage()                        }                        if (!borrower.firstTimeBuyer) {                        result.message = 'Only first-time home buyers qualify for this mortgage.'                        scriptExit.noMessage()                        }                        if (borrower.monthlyIncome > 4000) {                        result.message = 'Monthly salary of $' + borrower.monthlyIncome +                        ' exceeds the $4,000 maximum.'                        scriptExit.noMessage()                        }                        if (borrower.creditScore < 500) {                        result.message = 'Your credit score of ' + borrower.creditScore +                        ' does not meet the 500 requirement.'                        scriptExit.noMessage()                        }                        // Qualifies. Determine interest rate based on loan amount and credit score.                        result.qualified = true                        result.message = 'Congratulations, you qualify.'                        if (loan.loanAmount > 450000) {                        result.interestRate = 0.08 // Big loans and poor credit require higher rate.                        } else if (borrower.creditScore < 550) {                        result.interestRate = 0.08                        } else if (borrower.creditScore < 600) {                        result.interestRate = 0.07                        } else if (borrower.creditScore < 700) {                        result.interestRate = 0.065                        } else { // Good credit gets best rate.                        result.interestRate = 0.06                        }

注意,JavaScript 代碼不能像 Groovy 腳本那樣使用 Java scriptExit.withMessage() 方法在一個(gè)語(yǔ)句中設(shè)置不合格消息并退出腳本。這是因?yàn)?Rhino JavaScript 解釋器并不把拋出的 Java 異常在 ScriptException 堆棧跟蹤中作為嵌入的 “錯(cuò)誤原因” 向上傳遞。因此,在堆棧跟蹤中更難找到 Java 代碼拋出的腳本異常消息。所以 清 單 5 中的 JavaScript 代碼需要單獨(dú)設(shè)置結(jié)果消息,然后再調(diào)用 scriptExit.noMessage() 來(lái)產(chǎn)生異常,從而終止腳本處理。

第三個(gè)抵押產(chǎn)品腳本是用 Ruby 編寫的,見(jiàn)清單 6。這種抵押產(chǎn)品要求貸款人具有良好的信用記錄,他們可以支付百分之二十的首付款。


清單 6. Ruby 抵押腳本
                        # This Ruby script defines the "Ruby Mortgage" product.                        # It is intended for premium borrowers with its low interest rate                        # and 20% down payment requirement.                        # Our product name                        $result.product_name = 'Ruby Mortgage'                        # Borrowers with credit unworthiness do not qualify.                        if $borrower.credit_score < 700                        $scriptExit.with_message "Credit score of #{$borrower.credit_score}" +                        " is lower than 700 minimum"                        end                        $scriptExit.with_message 'No bankruptcies allowed' if $borrower.hasDeclaredBankruptcy                        # Check other negatives                        down_payment_percent = $loan.down_payment / $property.sales_price * 100                        if down_payment_percent < 20                        $scriptExit.with_message 'Down payment must be at least 20% of sale price.'                        end                        # Borrower qualifies. Determine interest rate of loan                        $result.message = "Qualified!"                        $result.qualified = true                        # Give the best interest rate to the best credit risks.                        if $borrower.credit_score > 750 || down_payment_percent > 25                        $result.interestRate = 0.06                        elsif $borrower.credit_score > 700 && $borrower.totalAssets > 100000                        $result.interestRate = 0.062                        else                        $result.interestRate = 0.065                        end                        

在 JRuby 1.0 中不要忘記 $ 符號(hào)

在 Ruby 腳本中訪問(wèn)共享的 Java 對(duì)象時(shí),一定要記住 Ruby 的全局變量語(yǔ)法。如果省略了全局變量前面的 $ 符號(hào),那么 JRuby 1.0 和當(dāng)前的 JRuby 1.0.1 二進(jìn)制版本會(huì)拋出一個(gè) RaiseException,而且不提供錯(cuò)誤的相關(guān)信息。JRuby 源代碼存儲(chǔ)庫(kù)中已經(jīng)糾正了這個(gè) bug,所以在以后的二進(jìn)制版本中應(yīng)該不會(huì)出現(xiàn)這個(gè)問(wèn)題。

如清單 6 所示,在 Ruby 腳本中,需要在變量名前面加上 $ 符號(hào),這樣才能訪問(wèn)放在腳本引擎范圍內(nèi)的共享 Java 對(duì)象。這是 Ruby 的全局變量語(yǔ)法。腳本引擎以全局變量的形式向腳本共享 Java 對(duì)象,所以必須使用 Ruby 的全局變量語(yǔ)法。

還要注意,在調(diào)用共享的 Java 對(duì)象時(shí),JRuby 會(huì)自動(dòng)地將 Ruby 式代碼轉(zhuǎn)換為 Java 式代碼。例如,如果 JRuby 發(fā)現(xiàn)代碼按照 Ruby 命名約定(即以下劃線分隔單詞)調(diào)用 Java 對(duì)象上的方法,比如 $result.product_name = 'Ruby Mortgage',那么 JRuby 會(huì)尋找不帶下劃線的大小寫混合式方法名。因此,Ruby 式方法名 product_name= 會(huì)正確地轉(zhuǎn)換為 Java 調(diào)用 result.setProductName("Ruby Mortgage")







程序輸出

現(xiàn)在用這三個(gè)抵押產(chǎn)品腳本文件運(yùn)行 ScriptMortgageQualifierRunner 程序,看看它的輸出。可以使用源代碼下載文件中的 Ant 腳本運(yùn)行這個(gè)程序。如果喜歡使用 Maven,那么可以按照 ZIP 文件中的 README.txt 文件中的說(shuō)明用 Maven 構(gòu)建并運(yùn)行這個(gè)程序。Ant 命令是 ant runrun 任務(wù)確保腳本引擎和語(yǔ)言 JAR 文件在類路徑中。清單 7 給出 Ant 的輸出。


清單 7. Ant 產(chǎn)生的程序輸出
                        > ant run                        Buildfile: build.xml                        compile:                        [mkdir] Created dir: C:\temp\script-article\build-main\classes                        [javac] Compiling 10 source files to C:\temp\script-article\build-main\classes                        run:                        [java] Processing file: GroovyMortgage.groovy                        [java]   Borrower: Good Borrower                        [java]   Credit score: 800                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: Groovy Mortgage, Qualified? true                        [java] * Interest rate: 0.06                        [java] * Message: Groovy! You qualify.                        [java] Processing file: JavaScriptFirstTimeMortgage.js                        [java]   Borrower: Good Borrower                        [java]   Credit score: 800                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: JavaScript FirstTime Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: Only first-time home buyers qualify for this mortgage.                        [java] Processing file: RubyPrimeMortgage.rb                        [java]   Borrower: Good Borrower                        [java]   Credit score: 800                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: Ruby Mortgage, Qualified? true                        [java] * Interest rate: 0.06                        [java] * Message: Qualified!                        [java] Processing file: GroovyMortgage.groovy                        [java]   Borrower: Average Borrower                        [java]   Credit score: 700                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: Groovy Mortgage, Qualified? true                        [java] * Interest rate: 0.06                        [java] * Message: Groovy! You qualify.                        [java] Processing file: JavaScriptFirstTimeMortgage.js                        [java]   Borrower: Average Borrower                        [java]   Credit score: 700                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: JavaScript FirstTime Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: Monthly salary of $4500 exceeds the $4,000 maximum.                        [java] Processing file: RubyPrimeMortgage.rb                        [java]   Borrower: Average Borrower                        [java]   Credit score: 700                        [java]   Sales price: 300000.0                        [java]   Down payment: 60000.0                        [java] * Mortgage product: Ruby Mortgage, Qualified? true                        [java] * Interest rate: 0.065                        [java] * Message: Qualified!                        [java] Processing file: GroovyMortgage.groovy                        [java]   Borrower: Investor Borrower                        [java]   Credit score: 720                        [java]   Sales price: 300000.0                        [java]   Down payment: 30000.0                        [java] * Mortgage product: Groovy Mortgage, Qualified? true                        [java] * Interest rate: 0.06                        [java] * Message: Groovy! You qualify.                        [java] Processing file: JavaScriptFirstTimeMortgage.js                        [java]   Borrower: Investor Borrower                        [java]   Credit score: 720                        [java]   Sales price: 300000.0                        [java]   Down payment: 30000.0                        [java] * Mortgage product: JavaScript FirstTime Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: This mortgage is not intended for investors.                        [java] Processing file: RubyPrimeMortgage.rb                        [java]   Borrower: Investor Borrower                        [java]   Credit score: 720                        [java]   Sales price: 300000.0                        [java]   Down payment: 30000.0                        [java] * Mortgage product: Ruby Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: Down payment must be at least 20% of sale price.                        [java] Processing file: GroovyMortgage.groovy                        [java]   Borrower: Risk E. Borrower                        [java]   Credit score: 520                        [java]   Sales price: 300000.0                        [java]   Down payment: 10000.0                        [java] * Mortgage product: Groovy Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: Down payment of 3.33% is insufficient. 5% minimum required.                        [java] Processing file: JavaScriptFirstTimeMortgage.js                        [java]   Borrower: Risk E. Borrower                        [java]   Credit score: 520                        [java]   Sales price: 300000.0                        [java]   Down payment: 10000.0                        [java] * Mortgage product: JavaScript FirstTime Mortgage, Qualified? true                        [java] * Interest rate: 0.08                        [java] * Message: Congratulations, you qualify.                        [java] Processing file: RubyPrimeMortgage.rb                        [java]   Borrower: Risk E. Borrower                        [java]   Credit score: 520                        [java]   Sales price: 300000.0                        [java]   Down payment: 10000.0                        [java] * Mortgage product: Ruby Mortgage, Qualified? false                        [java] * Interest rate: 0.0                        [java] * Message: Credit score of 520 is lower than 700 minimum                        [java] Sleeping for one minute before reprocessing files.                        [java] Use Ctrl-C to exit...                        

這個(gè)輸出共有 12 個(gè)部分,這是因?yàn)槌绦驅(qū)⑺膫€(gè)貸款人示例提交給三個(gè)腳本,檢查這 12 種組合中貸款人是否符合抵押產(chǎn)品的要求。為了演示本文解釋的技術(shù),這個(gè)程序會(huì)等待一分鐘,然后重復(fù)處理抵押腳本。在這段停頓期間,可以編輯腳本文件來(lái)修改 業(yè)務(wù)規(guī)則,還可以在腳本目錄中添加新的腳本文件來(lái)表示新的抵押產(chǎn)品。在每次重復(fù)運(yùn)行時(shí),程序會(huì)掃描腳本目錄并處理它找到的所有腳本文件。

例如,假設(shè)您希望提高貸款所需的最低信用分?jǐn)?shù)。在一分鐘的停頓期間,可以編輯 src/main/scripts/mortgage-products 目錄中的 JavaScriptFirstTimeMortgage.js 腳本(見(jiàn) 清 單 5),將第 23 行上的業(yè)務(wù)規(guī)則由 if (borrower.creditScore < 500) { 改為 if (borrower.creditScore < 550) {。在下次運(yùn)行規(guī)則時(shí),Risk E. Borrower 就不再符合 JavaScript FirstTime Mortgage 的要求。這個(gè)貸款人的信用分?jǐn)?shù)是 520,這個(gè)分?jǐn)?shù)低于目前的條件。錯(cuò)誤消息現(xiàn)在是 “Your credit score of 520 does not meet the 500 requirement”,但是同樣可以在程序運(yùn)行時(shí)糾正這個(gè)錯(cuò)誤的消息。





避免動(dòng)態(tài)腳本風(fēng)險(xiǎn)

在運(yùn)行時(shí)修改程序的功能是非常強(qiáng)大的,同樣也可能導(dǎo)致風(fēng)險(xiǎn)??梢栽谡谶\(yùn)行的應(yīng)用程序中添加新的功能和新的業(yè)務(wù)規(guī)則,而無(wú) 需停止并重新啟動(dòng)應(yīng)用程序。同樣,也很容易引入新的 bug,甚至是嚴(yán)重的 bug。

但是,動(dòng)態(tài)地修改正在運(yùn)行的應(yīng)用程序并不比修改停止運(yùn)行的應(yīng)用程序更危險(xiǎn)。靜態(tài)技術(shù)僅僅意味著必須重新啟動(dòng)應(yīng)用程序,然后 才能發(fā)現(xiàn)那些新的錯(cuò)誤。良好的軟件開(kāi)發(fā)實(shí)踐表明,對(duì)生產(chǎn)性應(yīng)用程序的任何修改(無(wú)論是動(dòng)態(tài)的,還是靜態(tài)的)都應(yīng)該先接受測(cè)試,然后才能引入生產(chǎn)環(huán)境中。 Java 腳本編程 API 并未改變這一規(guī)則。

外部腳本文件可以在開(kāi)發(fā)期間進(jìn)行常規(guī)的單元測(cè)試??梢允褂?JUnit 或其他測(cè)試工具和模擬 Java 對(duì)象來(lái)測(cè)試腳本,確保腳本在運(yùn)行時(shí)不會(huì)出現(xiàn)錯(cuò)誤并產(chǎn)生所期望的結(jié)果。將應(yīng)用程序邏輯放在外部非 Java 腳本文件中并不意味著無(wú)法測(cè)試這些腳本。

如果您當(dāng)過(guò) Web CGI 腳本程序員,那么一定知道必須注意傳遞給 ScriptEngineeval() 方法的東西。腳本引擎會(huì)立即執(zhí)行傳遞給 eval 方法的代碼。因此,絕不要把來(lái)自不可信來(lái)源的字符串或 Reader 對(duì)象傳遞給腳本引擎。

例如,假設(shè)我們使用腳本編程 API 遠(yuǎn)程監(jiān)視一個(gè) Web 應(yīng)用程序。我們讓腳本引擎能夠訪問(wèn)關(guān)鍵的 Java 對(duì)象,這些對(duì)象提供 Web 應(yīng)用程序的狀態(tài)信息。還創(chuàng)建一個(gè)簡(jiǎn)單的 Web 頁(yè)面,這個(gè)頁(yè)面接受任意腳本表達(dá)式,它將這些表達(dá)式傳遞給腳本引擎進(jìn)行計(jì)算并在 Web 頁(yè)面上顯示輸出。這樣就可以對(duì)正在運(yùn)行的 Java 對(duì)象進(jìn)行查詢并執(zhí)行對(duì)象上的方法,從而幫助判斷應(yīng)用程序的狀態(tài)。

但是,在這種情況下,能夠訪問(wèn)這個(gè) Web 頁(yè)面的任何人都可以執(zhí)行任意腳本語(yǔ)句,可以訪問(wèn)任意共享 Java 對(duì)象。編程時(shí)的失誤、錯(cuò)誤的配置和安全漏洞會(huì)把機(jī)密信息泄露給未授權(quán)用戶,或者讓應(yīng)用程序遭遇拒絕服務(wù)攻擊(例如,攻擊者可以執(zhí)行與 System.exit/bin/rm -fr / 等效的腳本語(yǔ)句)。與任何強(qiáng)大的工具一樣,Java 腳本編程 API 要求您保持謹(jǐn)慎,注意安全。





進(jìn)一步開(kāi)拓的方向

本文主要關(guān)注讓 Java 應(yīng)用程序能夠在運(yùn)行時(shí)動(dòng)態(tài)地讀取并執(zhí)行外部腳本,以及讓腳本能夠訪問(wèn)顯式提供給它們的 Java 對(duì)象。Java 腳本編程 API 還提供了其他特性。例如:

  • 可以使用腳本語(yǔ)言實(shí)現(xiàn)一個(gè) Java 接口,然后像使用任何其他 Java 接口引用一樣從 Java 代碼調(diào)用腳本代碼。
  • 可以在腳本中實(shí)例化并使用 Java 對(duì)象,還可以讓 Java 應(yīng)用程序能夠訪問(wèn)這些對(duì)象。
  • 可以在裝載動(dòng)態(tài)腳本時(shí)進(jìn)行預(yù)編譯,這可以讓以后的執(zhí)行過(guò)程更快。
  • 可以設(shè)置腳本使用的輸入流和輸出流,這樣就很容易將文件用作腳本的控制臺(tái)輸入源,以及將腳本的控制臺(tái)輸出轉(zhuǎn)發(fā)到 文件或其他流。
  • 可以設(shè)置位置參數(shù),腳本可以將這些參數(shù)用作命令行參數(shù)。

Java 腳本編程 API 定義了腳本引擎可以選擇實(shí)現(xiàn)的一些功能,所以并非所有腳本引擎都提供這些功能。在 參 考資料 中可以找到關(guān)于這些特性和其他特性的讀物和在線參考資料。






下載

描述名字大小下載方法
源代碼和所有 JAR 文件 java-scripting-part2.zip 4.5MB HTTP
關(guān)于下載方法的信息


參考資料

學(xué)習(xí)
  • 您 可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文 。

  • 動(dòng)態(tài)調(diào)用動(dòng)態(tài)語(yǔ)言,第 1 部分”:這篇文章介紹了 Java 腳本編程 API 的特性,并用一個(gè)簡(jiǎn)單的應(yīng)用程序演示了 Java 代碼如何執(zhí)行腳本代碼以及腳本如何執(zhí)行 Java 代碼。

  • JSR-223: Scripting for the Java Platform:這個(gè) Java 規(guī)范提案定義了 Java SE 6 中添加的 Java 腳本編程 API。

  • Java Scripting Programmer's Guide:Sun 的 JDK 6 文檔包含一份 Java 腳本編程 API 程序員指南。

  • Jakarta Bean Scripting Framework:BSF 項(xiàng)目提供了 Java 腳本編程 API 的基礎(chǔ)。

  • Making Scripting Languages JSR-223-Aware”(Thomas Künneth,java.net,2006 年 9 月):這篇文章講解了在沒(méi)有腳本引擎可用的情況下,如何讓 Java 腳本編程 API 可以使用某種腳本語(yǔ)言。

  • Groovy:通過(guò) Groovy 項(xiàng)目 Web 站點(diǎn)進(jìn)一步了解這種用于 Java 平臺(tái)的敏捷的動(dòng)態(tài)語(yǔ)言。

  • 實(shí)戰(zhàn) Groovy :要深入研究 Groovy,可以閱讀 developerWorks 系列文章。

  • Mozilla Rhino: 通過(guò)文檔和其他參考資料進(jìn)一步了解這種 JavaScript 引擎,這種引擎包含 Sun Microsystems 和 BEA Systems 提供的 Java 運(yùn)行時(shí)。

  • JRuby:JRuby 是 Ruby 編程語(yǔ)言的一種純 Java 實(shí)現(xiàn)。項(xiàng)目的 Web 站點(diǎn)提供最新的項(xiàng)目新聞和有關(guān) JRuby 使用方法的其他參考資料。

  • technology bookstore 瀏覽關(guān)于這些主題和其他技術(shù)主題的圖書。

  • developerWorks Java 技術(shù)專區(qū):這里提供數(shù)百篇 關(guān)于 Java 編程各個(gè)方面的文章。


獲得產(chǎn)品和技術(shù)
  • Groovy: 下載最新的 Groovy 版本。

  • Java SE 6BEA JRockit:這些開(kāi)發(fā)工具包和運(yùn)行時(shí)環(huán)境支持 Java 腳本編程 API,還包含 Mozilla Rhino JavaScript 引擎的一個(gè)簡(jiǎn)化版本。

  • Scripting 項(xiàng)目:java.net 上的開(kāi)放源碼 Scripting 項(xiàng)目為 20 多種語(yǔ)言提供了腳本引擎接口,并鏈接到其他著名的 Java 腳本引擎。要想使用這些腳本語(yǔ)言之一,只需安裝這個(gè)項(xiàng)目提供的腳本引擎實(shí)現(xiàn) JAR 文件以及腳本語(yǔ)言解釋器 JAR 文件。

  • Scripting for the Java Platform 1.0 Reference Implementation:JSR-223 參考實(shí)現(xiàn)提供三個(gè) JAR 文件,支持在 Java SE 5 中運(yùn)行 Java 腳本編程 API。下載并解壓 sjp-1_0-fr-ri.zip 文件,將 js.jar、script-api.jar 和 script-js.jar 文件放在自己的類路徑中。


討 論


關(guān)于作者

Tom McQueeney 是 Idea Integration(美國(guó)的一家咨詢公司)的一名 Java 開(kāi)發(fā)人員和應(yīng)用程序架構(gòu)師。他熱衷于將一些動(dòng)態(tài)語(yǔ)言(如 Ruby 和 Groovy)集成到 Java 項(xiàng)目中,從而提高開(kāi)發(fā)的速度、效率和樂(lè)趣。他曾經(jīng)是 O'Reilly OSCON 和 ApacheCon EuropeHe 的發(fā)言人,并且擔(dān)任過(guò) Denver Java Users Group 的總裁一職。他和他的妻子(也是一名認(rèn)證的 Java 架構(gòu)師)居住在華盛頓。


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Groovy JSR 04 發(fā)布,=JAVA+Ruby+Python?
選擇合適的java腳本語(yǔ)言
抵押貸款銀行家協(xié)會(huì)的周度調(diào)查(Mortgage Bankers Association Weekly Survey)
抵押貸款銀行家協(xié)會(huì)的周度調(diào)查(Mortgage Bankers Association W...
別再把"房奴"說(shuō)成house slave了,不然問(wèn)題很大條!
Jenkins之必備groovy基礎(chǔ)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服