`
mingj
  • 浏览: 22794 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

play! framework hot swap 浅析

阅读更多
play! 最大的卖点就在于 hot swap,正如它自己宣称的:
reach your maximum productivity。play! 允许开发人员修改java文件,保存,然后刷新浏览器,立马可以看到效果。不需要编译,也不需要重启服务器。
Java 要想实现动态更新 class 文件,不外乎两种手段:替换 classloader、替换 JVM。因为替换 JVM 引起的开销更大,需要维护 JVM 的堆、栈等运行信息,所以 hot swap 通常是选择替换 classloader。比如 grails 里面就是选择替换 classloader,它会自己维护一个线程,定期轮询源文件是否发生修改,以替换原来的 classloader。那么 play! 宣称的 hot swap 又是怎么实现的呢?
让我们来看看play! 的内部流程:
1. play! 使用了 Apache Mina 作为底层的 http server,然后使用了自己关于 Mina IoHandler 接口的实现—— HttpHandler
2. 当浏览器发起一个 request:
2.1 Mina Server 生成一个 Mina Request,转发给 HttpHandler 的 messageReceived 方法
2.2 play! 解析 Mina Request 和 Mina Session,包装成自己的 Request 对象
Request request = parseRequest(minaRequest, session);

2.3 play! 检测 Route 文件修改情况,根据 Route 配置信息将 Route/Action 的信息赋给 Request 对象
Router.detectChanges();
Router.route(request);

2.4 play! 根据当前配置的开发模式来采用不同的策略调用 Action 来理 Request
if (Play.mode == Play.Mode.DEV) {
Invoker.invokeInThread(new MinaInvocation(session, minaRequest, minaResponse, request, response));
} else {
Invoker.invoke(new MinaInvocation(session, minaRequest, minaResponse, request, response));
}

2.5 如果 play! 当前是 DEV 模式,invokeInThread方法会让 invocation 对象代理 run() 方法
public void run() {
try {
before();
execute();
after();
} catch (Throwable e) {
onException(e);
} finally {
_finally();
}
}

咱们来看看 before() 方法:
public static void before() {
Thread.currentThread().setContextClassLoader(Play.classloader);
if(!Play.id.equals("test")) {
Play.detectChanges();
if (!Play.started) {
Play.start();
}
}
//
}

在 Play 类的 detectChanges() 方法里面,有这么一句:
classloader.detectChanges();

哈哈,play! 修改源文件后,刷新浏览器即见效的奥秘就在这里了。再进去看看 play! 自定义 classloader 的 detectChanges() 方法:
public void detectChanges() {
// Now check for file modification
List<ApplicationClass> modifieds = new ArrayList<ApplicationClass>();
for (ApplicationClass applicationClass : Play.classes.all()) {
if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) {
applicationClass.refresh();
modifieds.add(applicationClass);
}
}
List<ClassDefinition> newDefinitions = new ArrayList<ClassDefinition>();
Map<Class, Integer> annotationsHashes = new HashMap<Class, Integer>();
for (ApplicationClass applicationClass : modifieds) {
annotationsHashes.put(applicationClass.javaClass, computeAnnotationsHash(applicationClass.javaClass));
if (applicationClass.compile() == null) {
Play.classes.classes.remove(applicationClass.name);
} else {
applicationClass.enhance();
BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource);
newDefinitions.add(new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode));
}
}
try {
HotswapAgent.reload(newDefinitions.toArray(new ClassDefinition[newDefinitions.size()]));
} catch (ClassNotFoundException e) {
throw new UnexpectedException(e);
} catch (UnmodifiableClassException e) {
throw new UnexpectedException(e);
}
// Check new annotations
for (Class clazz : annotationsHashes.keySet()) {
if (annotationsHashes.get(clazz) != computeAnnotationsHash(clazz)) {
throw new RuntimeException("Annotations change !");
}
}
// Now check if there is new classes or removed classes
int hash = computePathHash();
if (hash != this.pathHash) {
// Remove class for deleted files !!
for (ApplicationClass applicationClass : Play.classes.all()) {
if (!applicationClass.javaFile.exists()) {
Play.classes.classes.remove(applicationClass.name);
}
if(applicationClass.name.contains("$")) {
Play.classes.classes.remove(applicationClass.name);
}
}
throw new RuntimeException("Path has changed");
}
}

HotswapAgent类的 reload 方法如下:
public static void reload(ClassDefinition definitions) throws UnmodifiableClassException, ClassNotFoundException {
instrumentation.redefineClasses(definitions);
}

读到这里,也就弄清楚了 play! 怎么实现 hot swap 的原理了,还是调用java.lang.instrument目录下的类和方法来实现的 hot swap。不存在魔法,play! 还是选择了替换 classloader,只不过这个替换动作发生在处理 http request 的时候,于是开发人员用起来就是“刷新浏览器就可以看见效果了”。

17
1
分享到:
评论
8 楼 為妳伏筆。 2010-03-01  
duker 写道
play 目前还是个玩具..不能用的..
不过分析下其有用的技术,也不错..
http://duker.iteye.com/blog/306082


不能用于实际项目吗???
我们总监怎么让我我们学play。
我们将要用play做一个实际应用的门户网站。。。。
7 楼 bachmozart 2008-12-31  
引用

hot swap 通常是选择替换 classloader。比如 grails 里面就是选择替换 classloader,它会自己维护一个线程,定期轮询源文件是否发生修改,以替换原来的 classloader


听着觉得有点乱,是不是该这么说呀
hot swap 选择的是重新实现一个classloader,然后内部维护一个所有类实例的实例池,发现有源文件发生变化,则重新load一次class ,替换的是原来的class不是classloader,classloader从一开始就是使用他自己定义的那个,跟类更新没啥关系
6 楼 avaj 2008-12-31  
目前来说,在开发模式下,新加类,删除类都不需要重启。
5 楼 avaj 2008-12-31  
Quake Wang 写道

他是倚靠jvm的hot swap来作的吧?象jvm不支持的hot swap:add/remove classchange interface这些操作它是不是也还要重启动呢?


add class 不需要重启
4 楼 QuakeWang 2008-12-31  
他是倚靠jvm的hot swap来作的吧?象jvm不支持的hot swap:
add/remove class
change interface
这些操作它是不是也还要重启动呢?
3 楼 container 2008-12-31  
定位不同,何来玩具一说?作者根本就没有用play去开发企业级应用的意思...

他想做的是类ROR,类PHP...在互联网领域Play还是合适的。
2 楼 mingj 2008-12-30  
duker 写道

play 目前还是个玩具..不能用的..不过分析下其有用的技术,也不错..http://duker.iteye.com/blog/306082


没错,play 现阶段只是玩具,不可能也不会用在真实项目上
而网站之类的开发 rails又已经可以完全满足
对play 感兴趣是因为我们最近也在做一个rails-like的开源项目
所以,详细看了看play的一些特性

之后我还会写一篇博客,具体谈谈为什么不选择play
倒是和你不谋而合了,握手~~
1 楼 duker 2008-12-30  
play 目前还是个玩具..不能用的..
不过分析下其有用的技术,也不错..
http://duker.iteye.com/blog/306082

相关推荐

Global site tag (gtag.js) - Google Analytics