1.概述 本篇隨筆主要講述了在線程序通過腳本或者代碼進行更新的一個例子. a.在線程序通常會有更改內存數據或者修復錯誤邏輯的需求. b.更改內存數據則通常是找到要修改的對象,然后直接通過加載更新腳本更新對象數據. c.更改錯誤邏輯,通常是新寫一個class,繼承出錯的類并覆寫出錯的邏輯方法,然后將所有class的實例對象重新替換一下,一定要注意數據的拷貝,即從舊對象復制到新對象. d.本篇通過兩種方式,一種更新代碼就是.java,在外部編譯好.class,然后通知在線程序進行更新,在線程序利用classloader加載更新代碼,然后執行更新操作(所有的腳本都會實現一個更新接口). e.第二種方式就是直接使用更新腳本groovy,groovy相比java來說,寫起來更簡單(太tm方便了),然后在線程序這邊直接通過GroovyScriptEngine直接運行groovy更新腳本._____________________________________________________________________________________________________________________2.例子 本文的例子是有一個Player對象,該對象有幾個屬性和一個方法。更新腳本則是在線修改玩家對象數據以及修改錯誤的方法.3.代碼.package com.mavsplus.example.java.compile;
/**
* 測試的玩家對象
*
* @author landon
* @since 1.8.0_25
*/
public class Player implements Cloneable {
// ---三個測試屬性,需要有動態修改的需求,如線上玩家等級出錯,需要腳本將內存中的值直接修改為正確的值 --//
public long id;
public String name;
public int lv;
public int vipLv;
// -- 測試方法,需要有動態修改的需求,如線上的時候發現該方法實現有問題,需要將方法邏輯修正為正確的實現 --//
public boolean isVip() {
return vipLv > 0;
}
@Override
public String toString() {
return "Player [id=" + id + ", name=" + name + ", lv=" + lv + ", vipLv=" + vipLv + "]";
}
@Override
public Object clone() {
try {
Player clonePlayer = (Player) super.clone();
return clonePlayer;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}
package com.mavsplus.example.java.compile;
/**
* 腳本執行接口
*
* @author landon
* @since 1.8.0_25
*/
public interface IScriptExecute {
public void execute();
}
package com.mavsplus.example.java.compile.script;import com.mavsplus.example.java.compile.IScriptExecute;import com.mavsplus.example.java.compile.OnlineServer;import com.mavsplus.example.java.compile.Player;import com.mavsplus.example.java.compile.PlayerService;/**
* 修改Player內存數據腳本
*
* @author landon
* @since 1.8.0_25
*/public class ModifyPlayerFieldScript implements IScriptExecute { public void execute() { System.out.println("Execute Script:ModifyPlayerFieldScript
"); PlayerService playerService = OnlineServer.playerService; if (playerService != null) { // 注意這里是5L,如果5的話則返回null.5默認為int,而Key的參數是Long,切記
Player errPlayer = playerService.playerMap.get(5L); if (errPlayer != null) { errPlayer.lv = 10086; errPlayer.vipLv = 11; } } }} package com.mavsplus.example.java.compile.script;import java.util.ArrayList;import java.util.List;import com.mavsplus.example.java.compile.IScriptExecute;import com.mavsplus.example.java.compile.OnlineServer;import com.mavsplus.example.java.compile.Player;/**
* 修改Player線上業務邏輯
*
* @author landon
* @since 1.8.0_25
*/public class ModifyPlayerLogicScript implements IScriptExecute { @Override public void execute() { System.out.println("Execute Script:ModifyPlayerLogicScript
"); List<Player> newPlayers = new ArrayList<>(); // 這樣更新的一個很大的問題在于得找到所有的Player對象,如果Player不在線怎么辦?
// 改進:線上其實可以在獲取Player對象的方法中進行統一進行處理(均都根據id查詢Player,用腳本覆寫改方法返回ModifiedPlayer且重新加入cache).-->這樣就不care是否是在線玩家還是離線玩家了
for (Player oldPlayer : OnlineServer.playerService.playerMap.values()) { // 如果是多線程處理的這里也可以直接進行克隆
ModifiedPlayer newPlayer = new ModifiedPlayer(oldPlayer); newPlayers.add(newPlayer); } for (Player newPlayer : newPlayers) { OnlineServer.playerService.playerMap.put(newPlayer.id, newPlayer); } } // 修改的Player對象,修正了方法邏輯實現,需要將線上的對象給替換掉,注意數據的克隆
private static class ModifiedPlayer extends Player { public ModifiedPlayer(Player player) { this.id = player.id; this.name = player.name; this.vipLv = player.vipLv; this.lv = player.lv; } // 覆寫錯誤的數據邏輯
@Override public boolean isVip() { return false; } }} package com.mavsplus.example.java.compile.groovyimport com.mavsplus.example.java.compile.OnlineServer;import com.mavsplus.example.java.compile.Player;import com.mavsplus.example.java.compile.PlayerService;// 同ModifyPlayerFieldScript,不過由groovy實現
// 生成:target\classes\com\mavsplus\example\java\compile\groovy\ModifyPlayerField.class
def execute() { println "execute groovy:ModifyPlayerField
"; PlayerService playerService = OnlineServer.playerService; if (playerService != null) { Player errPlayer = playerService.playerMap.get(7L); if (errPlayer != null) { errPlayer.lv = 99999; errPlayer.vipLv = 15; } }}execute(); package com.mavsplus.example.java.compile.groovyimport java.util.ArrayList;import java.util.List;import com.mavsplus.example.java.compile.OnlineServer;import com.mavsplus.example.java.compile.Player;// 生成:target\classes\com\mavsplus\example\java\compile\groovy\ModifiedPlayer.class ModifyPlayerLogic.class
class ModifiedPlayer extends Player { ModifiedPlayer(Player player) { this.id = player.id; this.name = player.name; this.vipLv = player.vipLv; this.lv = player.lv; } // 覆寫錯誤的數據邏輯
@Override boolean isVip() { return vipLv > 5; }}def execute(){ println "Execute groovy:ModifyPlayerLogic
" List<Player> newPlayers = new ArrayList<>(); for (Player oldPlayer : OnlineServer.playerService.playerMap.values()) { ModifiedPlayer newPlayer = new ModifiedPlayer(oldPlayer); newPlayers.add(newPlayer); } for (Player newPlayer : newPlayers) { OnlineServer.playerService.playerMap.put(newPlayer.id, newPlayer); }}execute(); package com.mavsplus.example.java.compile;
import groovy.util.GroovyScriptEngine;
import java.io.File;
/**
*
* 腳本更新服務,用來加載script目錄下腳本class
*
* <p>
* 線上更新的時候可以將腳本打成包,并將包放在classpath下
*
* <p>
* 當然也可以放到一個一個專門的目錄,用自定義classloader進行加載
*
* <p>
* start0方法用來執行groovy
*
* @author landon
* @since 1.8.0_25
*/
public class ScriptUpdateService {
public void loadScriptAndExecute(String scriptClassName) throws Exception {
String fullName = "com.mavsplus.example.java.compile.script." + scriptClassName;
Class<IScriptExecute> clazz = (Class<IScriptExecute>) ClassLoader.getSystemClassLoader().loadClass(fullName);
IScriptExecute instance = clazz.newInstance();
instance.execute();
}
public void start() {
try {
// 掃描script目錄
String scriptPath = getClass().getResource("").getPath() + "\\script";
File file = new File(scriptPath);
File[] subFiles = file.listFiles();
for (File subFile : subFiles) {
String fileClassName = subFile.getName();
int dotCharIndex = fileClassName.indexOf('.');
String loadClassName = fileClassName.substring(0, dotCharIndex);
if (loadClassName.contains("$")) {
continue;
}
loadScriptAndExecute(loadClassName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 加載groovy
public void start0() {
try {
String groovyPath = "E:\\github\\mavsplus-all\\mavsplus-examples\\src\\main\\java\\com\\mavsplus\\example\\java\\compile\\groovy";
GroovyScriptEngine engine = new GroovyScriptEngine(groovyPath);
engine.run("ModifyPlayerField.groovy", "");
engine.run("ModifyPlayerLogic.groovy", "");
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.mavsplus.example.java.compile;
import java.util.HashMap;
import java.util.Map;
/**
* 玩家服務
*
* @author landon
* @since 1.8.0_25
*/
public class PlayerService {
// -- 玩家map --//
public Map<Long, Player> playerMap = new HashMap<>();
public void start() {
for (Player player : playerMap.values()) {
System.out.println(player);
System.out.println("isVip:" + player.isVip());
}
}
}
package com.mavsplus.example.java.compile;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* 線上運行的服務程序
*
* <p>
* 該example的主要目的在于測試在程序運行過程中,需要改變一些內存中的一些數據或者操作,需要動態修改.目前初步的實現方式是服務程序可以接收一個.java
* , 這個可以理解為補丁或者更新腳本,程序收到該腳本后,在內存中動態編譯執行,進而修改一些數據或者操作
*
* <p>
* 當然也可以直接將補丁腳本編譯好,然后將編譯后的class放到classpath下,通知服務程序用classloader進行加載并更新.
*
* <p>
* 后續可以使用jvm腳本如groovy做對比分析
*
* @author landon
* @since 1.8.0_25
*/
public class OnlineServer {
public static PlayerService playerService;
public static ScriptUpdateService scriptUpdateService;
public static void init() {
playerService = new PlayerService();
scriptUpdateService = new ScriptUpdateService();
}
public static void initData() {
// 初始化部分測試數據
for (int i = 0; i < 10; i++) {
Player player = new Player();
player.id = i + 1;
player.lv = ThreadLocalRandom.current().nextInt(100) + 1;
player.name = "landon" + i;
player.vipLv = ThreadLocalRandom.current().nextInt(10) + 1;
playerService.playerMap.put(player.id, player);
}
}
// 模擬服務啟動
public static void start() throws Exception {
while (true) {
playerService.start();
System.out.println("----------------------------------");
TimeUnit.SECONDS.sleep(5);
// 目前測試這樣操作,實際線上通過網絡消息通知更新服務器更新具體的某個腳本即可
scriptUpdateService.start();
playerService.start();
System.out.println("----------------------------------");
TimeUnit.SECONDS.sleep(5);
// 加載groovy腳本
scriptUpdateService.start0();
playerService.start();
System.out.println("----------------------------------");
}
}
public static void main(String[] args) throws Exception {
init();
initData();
start();
}
}
4.代碼部分解釋 1.OnlineServer是一個在線運行的程序,它包括一個玩家服務。目前程序只是簡單打印所有的玩家信息. 2.程序運行的時候,比如我們發現線上某一個Player的數據有問題,我們需要進行在線進行修正,所以我們會寫一個更新腳本ModifyPlayerFieldScript.java,然后將其編譯.然后通知在線程序利用classloader加載更新腳本并執行(執行默認的execute方法) 3.作為對比,同時寫了功能一樣的groovy更新腳本,然后通知在線程序利用GroovyScriptEngine直接運行指定的groovy腳本. 4.同時程序運行的時候,會發現Player類的一個方法實現有bug,我們需要在線進行修正,所以我們會寫一個更新腳本ModifyPlayerLogicScript.java(主要原理在于繼承Player并覆寫出錯方法),然后將其編譯,然后通知在在線程序更新. 5.作為對比,也同樣寫了一個功能一樣的groovy更新腳本實現.5.輸出:Player [id=1, name=landon0, lv=93, vipLv=6]isVip:truePlayer [id=2, name=landon1, lv=37, vipLv=9]isVip:truePlayer [id=3, name=landon2, lv=25, vipLv=2]isVip:truePlayer [id=4, name=landon3, lv=78, vipLv=4]isVip:truePlayer [id=5, name=landon4, lv=27, vipLv=8]isVip:truePlayer [id=6, name=landon5, lv=84, vipLv=1]isVip:truePlayer [id=7, name=landon6, lv=95, vipLv=4]isVip:truePlayer [id=8, name=landon7, lv=100, vipLv=8]isVip:truePlayer [id=9, name=landon8, lv=41, vipLv=9]isVip:truePlayer [id=10, name=landon9, lv=100, vipLv=5]isVip:true----------------------------------Execute Script:ModifyPlayerFieldScript
Execute Script:ModifyPlayerLogicScript
Player [id=1, name=landon0, lv=93, vipLv=6]isVip:falsePlayer [id=2, name=landon1, lv=37, vipLv=9]isVip:falsePlayer [id=3, name=landon2, lv=25, vipLv=2]isVip:falsePlayer [id=4, name=landon3, lv=78, vipLv=4]isVip:falsePlayer [id=5, name=landon4, lv=10086, vipLv=11]isVip:falsePlayer [id=6, name=landon5, lv=84, vipLv=1]isVip:falsePlayer [id=7, name=landon6, lv=95, vipLv=4]isVip:falsePlayer [id=8, name=landon7, lv=100, vipLv=8]isVip:falsePlayer [id=9, name=landon8, lv=41, vipLv=9]isVip:falsePlayer [id=10, name=landon9, lv=100, vipLv=5]isVip:false----------------------------------execute groovy:ModifyPlayerField
Execute groovy:ModifyPlayerLogic
Player [id=1, name=landon0, lv=93, vipLv=6]isVip:truePlayer [id=2, name=landon1, lv=37, vipLv=9]isVip:truePlayer [id=3, name=landon2, lv=25, vipLv=2]isVip:falsePlayer [id=4, name=landon3, lv=78, vipLv=4]isVip:falsePlayer [id=5, name=landon4, lv=10086, vipLv=11]isVip:truePlayer [id=6, name=landon5, lv=84, vipLv=1]isVip:falsePlayer [id=7, name=landon6, lv=99999, vipLv=15]isVip:truePlayer [id=8, name=landon7, lv=100, vipLv=8]isVip:truePlayer [id=9, name=landon8, lv=41, vipLv=9]isVip:truePlayer [id=10, name=landon9, lv=100, vipLv=5]isVip:false----------------------------------
posted on 2015-08-07 21:59
landon 閱讀(1210)
評論(0) 編輯 收藏 所屬分類:
Script 、
HotSwap 、
ClassLoader