Play Framework hotswap及源碼分析
play! ***的賣點(diǎn)就在于 hot swap,正如它自己宣稱的:
reach your maximum productivity。play! 允許開發(fā)人員修改java文件,保存,然后刷新瀏覽器,立馬可以看到效果。不需要編譯,也不需要重啟服務(wù)器。
Java 要想實(shí)現(xiàn)動(dòng)態(tài)更新 class 文件,不外乎兩種手段:替換 classloader、替換 JVM。因?yàn)樘鎿Q JVM 引起的開銷更大,需要維護(hù) JVM 的堆、棧等運(yùn)行信息,所以 hot swap 通常是選擇替換 classloader。比如 grails 里面就是選擇替換 classloader,它會(huì)自己維護(hù)一個(gè)線程,定期輪詢?cè)次募欠癜l(fā)生修改,以替換原來的 classloader。那么 play! 宣稱的 hot swap 又是怎么實(shí)現(xiàn)的呢?
讓我們來看看play! 的內(nèi)部流程:
1. play! 使用了 Apache Mina 作為底層的 http server,然后使用了自己關(guān)于 Mina IoHandler 接口的實(shí)現(xiàn)—— HttpHandler
2. 當(dāng)瀏覽器發(fā)起一個(gè) request:
2.1 Mina Server 生成一個(gè) Mina Request,轉(zhuǎn)發(fā)給 HttpHandler 的 messageReceived 方法
2.2 play! 解析 Mina Request 和 Mina Session,包裝成自己的 Request 對(duì)象
- Request request = parseRequest(minaRequest, session);
2.3 play! 檢測 Route 文件修改情況,根據(jù) Route 配置信息將 Route/Action 的信息賦給 Request 對(duì)象
- Router.detectChanges();
- Router.route(request);
2.4 play! 根據(jù)當(dāng)前配置的開發(fā)模式來采用不同的策略調(diào)用 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! 當(dāng)前是 DEV 模式,invokeInThread方法會(huì)讓 invocation 對(duì)象代理 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! 修改源文件后,刷新瀏覽器即見效的奧秘就在這里了。再進(jìn)去看看 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! 怎么實(shí)現(xiàn) hot swap 的原理了,還是調(diào)用java.lang.instrument目錄下的類和方法來實(shí)現(xiàn)的 hot swap。不存在魔法,play! 還是選擇了替換 classloader,只不過這個(gè)替換動(dòng)作發(fā)生在處理 http request 的時(shí)候,于是開發(fā)人員用起來就是“刷新瀏覽器就可以看見效果了”。
原文鏈接:http://mingj.iteye.com/blog/307238
【編輯推薦】