Play Framework hotswap及源码分析

reach your maximum productivity。play! 允许开发人员修改java
首页 新闻资讯 行业资讯 Play Framework hotswap及源码分析

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);
  • 1.

2.3 play! 检测 Route 文件修改情况,根据 Route 配置信息将 Route/Action 的信息赋给 Request 对象

复制

Router.detectChanges();  Router.route(request);
  • 1.

  • 2.

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));  }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

2.5 如果 play! 当前是 DEV 模式,invokeInThread方法会让 invocation 对象代理 run() 方法

复制

public void run() {  try {  before();  execute();  after();  } catch (Throwable e) {  onException(e);  } finally {  _finally();  }  }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

咱们来看看 before() 方法:

复制

public static void before() {  Thread.currentThread().setContextClassLoader(Play.classloader);  if(!Play.id.equals("test")) {  Play.detectChanges();  if (!Play.started) {  Play.start();  }  }  //  }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

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

复制

classloader.detectChanges();
  • 1.

哈哈,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");  }  }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

  • 42.

  • 43.

  • 44.

  • 45.

  • 46.

  • 47.

  • 48.

  • 49.

HotswapAgent类的 reload 方法如下:

复制

public static void reload(ClassDefinition definitions) throws UnmodifiableClassException, ClassNotFoundException {  instrumentation.redefineClasses(definitions);  }
  • 1.

  • 2.

  • 3.

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

原文链接:http://mingj.iteye.com/blog/307238

【编辑推荐】

  1. Play Framework总结性介绍

  2. 有可能挑战Java优势的四种技术

  3. Think in Java之斐波那契数列

  4. Play Framework介绍:Hello World

  5. Play Framework介绍:主要概念

13    2012-02-23 12:53:40    Java Play Framework