java groovy热更新_Java游戏服使用Groovy在线修复玩家内存数据

生命不息,战斗不休。 --剑魔

当玩家因为逻辑bug导致其游戏数据错乱时,通常的做法是停服写SQL脚本修复或在重启服务器时写代码修复。在《Java游戏服热更新》一文中,我们已经提供了一种利用Java agent技术不停服修复玩家数据的方法,但是对于有些项目是打成jar包的情况下,如果采用新类修复玩家数据可能难以操作(原因见《Java游戏服热更新》),这篇将介绍另一种方法,即使用Groovy在线修复玩家内存数据,它是可以方便新增新类的。

百度百科中这样介绍groovy:Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库。

通俗的讲就是,Java运行于JVM之上,Groovy也是运行于JVM之上的,在java项目中可以嵌入Groovy,利用Groovy做一些我们想做的事,Groovy与java项目的集成方式之一是可以用 Groovy 的 ClassLoader ,动态地加载一个脚本(新类)并执行它的行为,Groovy ClassLoader是一个定制的类装载器,负责编译.groovy或.java文件,最终生成java的class类文件并加载它。Groovy ClassLoader可以把它看成是一个自定义类加载器,如果把它挂在AppClassLoader下,那么我们java项目原有在AppClassLoader或其父加载器中的类对它来说是可见可用的,利用这点,足以让我们增加新类来修复玩家内存中错误数据了。

如果将AppClassLoader作为Groovy ClassLoader的父加载器,那么整个类加载器的层级关系为:

null // 即Bootstrap ClassLoader

sun.misc.Launcher.ExtClassLoader // 即Extension ClassLoader

sun.misc.Launcher.AppClassLoader // 即System ClassLoader

org.codehaus.groovy.tools.RootLoader // 以下为User Custom ClassLoader

groovy.lang.GroovyClassLoader

groovy.lang.GroovyClassLoader.InnerLoader

groovy各个类加载器的作用为:

RootLoader:管理了Groovy的classpath,负责加载Groovy及其依赖的第三方库中的类,它不是使用双亲委派模型。

GroovyClassLoader:负责在运行时编译groovy源代码为Class的工作,从而使Groovy实现了将groovy源代码动态加载为Class的功能。

GroovyClassLoader.InnerLoader:Groovy脚本类的直接ClassLoader,它将加载工作委派给GroovyClassLoader,它的存在是为了支持不同源码里使用相同的类名,以及加载的类能顺利被GC。

(参考:《Groovy深入探索——Groovy的ClassLoader体系》)

Java agent是重定义class文件,但Groovy ClassLoader是可以直接使用.groovy或.java源文件的,而groovy语法完全兼容java语法,因此我们初始写groovy代码时,可以先写个.java类,然后直接改名为.groovy文件既可。等熟悉groovy语法后,groovy是可以写出比java更简洁的代码的。从上述可知,Groovy ClassLoader可以编译.groovy或.java文件,最终生成class文件并加载它们,利用这点,我们甚至可以在线撸功能,把写好的java文件上传到远程服务器,服务器Groovy ClassLoader编译并加载这些文件,就可以使我们的java项目不停服添加新功能了。

当知道Groovy ClassLoader作为一个类加载器可以直接编译加载.groovy或.java源文件后,它的使用逻辑就变得简单了,我们可以仿《Java游戏服热更新》逻辑,让它扫描某个目录下的.groovy或.java文件,然后编译并加载它们。

@Service

public class GroovyHotSwap implements Runnable, InitializingBean{

private ScheduledExecutorService executor = null;

private static File path = null;

private static Logger logger = LoggerFactory.getLogger(GroovyHotSwap.class);

@Override

public void afterPropertiesSet() throws Exception {

String grvpath = GameConfig.getInstance().getServerConfigPath();

if (!grvpath.endsWith("/")) {

grvpath += "/";

}

grvpath += "groovy";

path = new File(grvpath);

executor = Executors.newSingleThreadScheduledExecutor();

executor.scheduleAtFixedRate(this, 0, 3000, TimeUnit.MILLISECONDS);

}

@Override

public void run() {

try {

scanGroovyFile();

} catch (Exception e) {

logger.error("error", e);

}

}

public void scanGroovyFile() throws Exception {

File[] files = path.listFiles();

if (files != null && files.length > 0) {

boolean success = false;

long now = System.currentTimeMillis();

File[] bakFiles = files;

int fileNum = files.length;

for (int i = 0; i < fileNum; ++i) {

File file = bakFiles[i];

if (this.isJavaOrGroovyFile(file)) {

GroovyProcessor processor = GroovyUtil.processor(path.getAbsolutePath(), file.getName());

processor.process();

logger.info(String.format("Groovy Reload %s success", file.getPath()));

file.delete();

success = true;

}

}

if (success) {

logger.info(String.format("Groovy Reload success, cost time:%sms", System.currentTimeMillis() - now));

}

}

}

private boolean isJavaOrGroovyFile(File file) {

return file.getName().contains(".java") || file.getName().contains(".groovy");

}

}

我们也是隔几秒扫描某个路径文件夹下是否有.groovy或.java源文件,然后利用Groovy ClassLoader编译并加载它们:

public class GroovyUtil {

private static Map timesMap = new ConcurrentHashMap<>();

private static Map filesMap = new ConcurrentHashMap<>();

private static GroovyClassLoader groovyClassLoader = null;

static public GroovyProcessor processor(String grvpath, String name) throws Exception {

if (!grvpath.endsWith("/")) {

grvpath += "/";

}

if (!name.endsWith(".groovy") && !name.endsWith(".java")) {//支持groovy和java文件

name += ".groovy";

}

return grv(new File(grvpath + name));

}

static public T grv(File file) throws Exception {

if (!file.exists()) {

return null;

}

String pathname = file.getPath();

Long lastModified = timesMap.get(pathname);

if (lastModified == null || lastModified != file.lastModified()) {

if (groovyClassLoader == null) {//避免每次新增类加载器

ClassLoader classLoader = ClassLoader.getSystemClassLoader();//这里我们把应用加载器作为groovy加载器的父加载器

groovyClassLoader = new GroovyClassLoader(classLoader);

}

Class c = groovyClassLoader.parseClass(file);

T script = (T) c.newInstance();

timesMap.put(pathname, file.lastModified());

filesMap.put(pathname, script);

}

return (T) filesMap.get(pathname);

}

}

所有加载的新类继承GroovyProcessor接口,以便统一处理:

public interface GroovyProcessor {

String process() throws Exception;

}

可以随便写个新类测试一下:

public class GroovyTest implements GroovyProcessor {

public static final int a = 50;

static{

System.out.println("a = " + a);

System.out.println(HeroHandler.class.getSimpleName() + " classLoader:" + HeroHandler.class.getClassLoader());

}

@Override

public String process() throws Exception {

System.out.println(GroovyTest.class.getSimpleName() + " classLoader:" + GroovyTest.class.getClassLoader());

//TODO 修复玩家内存数据逻辑

return "sucess";

}

}

最后打印如下:

a = 50

HeroHandler classLoader:sun.misc.Launcher$AppClassLoader@18b4aac2

GroovyTest classLoader:groovy.lang.GroovyClassLoader$InnerLoader@2e77e64a

如此,便实现了使用Groovy在线修复玩家内存数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值