From aa0900d4cf6479bc598951ea446ea3f5f95a23b6 Mon Sep 17 00:00:00 2001 From: chiba Date: Tue, 21 Jun 2005 16:39:16 +0000 Subject: [PATCH] HotSwap support git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@184 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- Readme.html | 29 +++ build.xml | 41 +++- sample/hotswap/HelloWorld.java | 5 + sample/hotswap/Test.java | 25 +++ sample/hotswap/logging/HelloWorld.java | 6 + src/main/javassist/{ => tool}/Dump.java | 4 +- src/main/javassist/tool/HotSwapper.java | 250 ++++++++++++++++++++++++ src/main/javassist/tool/package.html | 6 + 8 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 sample/hotswap/HelloWorld.java create mode 100644 sample/hotswap/Test.java create mode 100644 sample/hotswap/logging/HelloWorld.java rename src/main/javassist/{ => tool}/Dump.java (95%) create mode 100644 src/main/javassist/tool/HotSwapper.java create mode 100644 src/main/javassist/tool/package.html diff --git a/Readme.html b/Readme.html index 63aa0094..dce5997a 100644 --- a/Readme.html +++ b/Readme.html @@ -240,6 +240,33 @@ with a web browser running on the local host. (Or, see sample/evolve/start.html.) +

8. sample/hotswap/*.java

+ +

This shows dynamic class reloading by the JPDA. +To run, first type the following commands: + +

+ +

If your Java is 1.4, then type: + +

+ +

If you are using Java 5, then type: + +

+ +

Note that the class path must include JAVA_HOME/lib/tools.jar. +

Hints

To know the version number, type this command: @@ -258,6 +285,8 @@ see javassist.Dump.

- version 3.1

- version 3.0 in January 18, 2005 diff --git a/build.xml b/build.xml index 712c7827..3c3e3bdc 100644 --- a/build.xml +++ b/build.xml @@ -6,7 +6,7 @@ - + @@ -51,7 +51,25 @@ debug="on" deprecation="on" optimize="off" - includes="sample/**"> + includes="sample/**" + excludes="sample/hotswap/**"> + + + + + + + - To run the sample programs, change the current directory + To run the sample programs without ant, change the current directory to ${build.classes.dir}. @@ -175,4 +193,21 @@ Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved.]]> + + + ** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH + + + + + + + + + ** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH + + + + + diff --git a/sample/hotswap/HelloWorld.java b/sample/hotswap/HelloWorld.java new file mode 100644 index 00000000..51d8fcd9 --- /dev/null +++ b/sample/hotswap/HelloWorld.java @@ -0,0 +1,5 @@ +public class HelloWorld { + public void print() { + System.out.println("hello world"); + } +} diff --git a/sample/hotswap/Test.java b/sample/hotswap/Test.java new file mode 100644 index 00000000..c0ae1771 --- /dev/null +++ b/sample/hotswap/Test.java @@ -0,0 +1,25 @@ +import java.io.*; +import javassist.tool.HotSwapper; + +public class Test { + public static void main(String[] args) throws Exception { + HotSwapper hs = new HotSwapper(8000); + new HelloWorld().print(); + + File newfile = new File("logging/HelloWorld.class"); + byte[] bytes = new byte[(int)newfile.length()]; + new FileInputStream(newfile).read(bytes); + System.out.println("** reload a logging version"); + + hs.reload("HelloWorld", bytes); + new HelloWorld().print(); + + newfile = new File("HelloWorld.class"); + bytes = new byte[(int)newfile.length()]; + new FileInputStream(newfile).read(bytes); + System.out.println("** reload the original version"); + + hs.reload("HelloWorld", bytes); + new HelloWorld().print(); + } +} diff --git a/sample/hotswap/logging/HelloWorld.java b/sample/hotswap/logging/HelloWorld.java new file mode 100644 index 00000000..88f08664 --- /dev/null +++ b/sample/hotswap/logging/HelloWorld.java @@ -0,0 +1,6 @@ +public class HelloWorld { + public void print() { + System.out.println("** HelloWorld.print()"); + System.out.println("hello world"); + } +} diff --git a/src/main/javassist/Dump.java b/src/main/javassist/tool/Dump.java similarity index 95% rename from src/main/javassist/Dump.java rename to src/main/javassist/tool/Dump.java index 69ba4625..f8a1a5dd 100644 --- a/src/main/javassist/Dump.java +++ b/src/main/javassist/tool/Dump.java @@ -13,7 +13,7 @@ * License. */ -package javassist; +package javassist.tool; import java.io.*; import javassist.bytecode.ClassFile; @@ -25,7 +25,7 @@ import javassist.bytecode.ClassFileWriter; * the class file is broken. * *

For example, - *

+ * * *

prints the contents of the constant pool and the list of methods * and fields. diff --git a/src/main/javassist/tool/HotSwapper.java b/src/main/javassist/tool/HotSwapper.java new file mode 100644 index 00000000..4df6ccae --- /dev/null +++ b/src/main/javassist/tool/HotSwapper.java @@ -0,0 +1,250 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tool; + +import com.sun.jdi.*; +import com.sun.jdi.connect.*; +import com.sun.jdi.event.*; +import com.sun.jdi.request.*; +import java.io.*; +import java.util.*; + +class Trigger { + void doSwap() {} +} + +/** + * A utility class for dynamically reloading a class by + * the Java Platform Debugger Architecture (JPDA), or HotSwap. + * It works only with JDK 1.4 and later. + * + *

Note: The new definition of the reloaded class must declare + * the same set of methods and fields as the original definition. The + * schema change between the original and new definitions is not allowed + * by the JPDA. + * + *

To use this class, the JVM must be launched with the following + * command line options: + * + *

+ * + *

Note that 8000 is the port number used by HotSwapper. + * Any port number can be specified. Since HotSwapper does not + * launch another JVM for running a target application, this port number + * is used only for inter-thread communication. + * + *

Furthermore, JAVA_HOME/lib/tools.jar must be included + * in the class path. + * + *

Using HotSwapper is easy. See the following example: + * + *

+ * + *

reload() + * first unload the Test class and load a new version of + * the Test class. + * classFile is a byte array containing the new contents of + * the class file for the Test class. The developers can + * repatedly call reload() on the same HotSwapper + * object so that they can reload a number of classes. + * + * @since 3.1 + */ +public class HotSwapper { + private VirtualMachine jvm; + private MethodEntryRequest request; + private Map newClassFiles; + + private Trigger trigger; + + private static final String HOST_NAME = "localhost"; + private static final String TRIGGER_NAME = "javassist.tool.Trigger"; + + /** + * Connects to the JVM. + * + * @param port the port number used for the connection to the JVM. + */ + public HotSwapper(int port) + throws IOException, IllegalConnectorArgumentsException + { + this(Integer.toString(port)); + } + + /** + * Connects to the JVM. + * + * @param port the port number used for the connection to the JVM. + */ + public HotSwapper(String port) + throws IOException, IllegalConnectorArgumentsException + { + jvm = null; + request = null; + newClassFiles = null; + trigger = new Trigger(); + AttachingConnector connector + = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach"); + + Map arguments = connector.defaultArguments(); + ((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME); + ((Connector.Argument)arguments.get("port")).setValue(port); + jvm = connector.attach(arguments); + EventRequestManager manager = jvm.eventRequestManager(); + request = methodEntryRequests(manager, TRIGGER_NAME); + } + + private Connector findConnector(String connector) throws IOException { + List connectors = Bootstrap.virtualMachineManager().allConnectors(); + Iterator iter = connectors.iterator(); + while (iter.hasNext()) { + Connector con = (Connector)iter.next(); + if (con.name().equals(connector)) { + return con; + } + } + + throw new IOException("Not found: " + connector); + } + + private static MethodEntryRequest methodEntryRequests( + EventRequestManager manager, + String classpattern) { + MethodEntryRequest mereq = manager.createMethodEntryRequest(); + mereq.addClassFilter(classpattern); + mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + return mereq; + } + + private void deleteEventRequest(EventRequestManager manager, + MethodEntryRequest request) { + manager.deleteEventRequest(request); + } + + /** + * Reloads a class. + * + * @param className the fully-qualified class name. + * @param classFile the contents of the class file. + */ + public void reload(String className, byte[] classFile) { + ReferenceType classtype = toRefType(className); + Map map = new HashMap(); + map.put(classtype, classFile); + reload2(map, className); + } + + /** + * Reloads a class. + * + * @param classFiles a map between fully-qualified class names + * and class files. The type of the class names + * is String and the type of the + * class files is byte[]. + */ + public void reload(Map classFiles) { + Set set = classFiles.entrySet(); + Iterator it = set.iterator(); + Map map = new HashMap(); + String className = null; + while (it.hasNext()) { + Map.Entry e = (Map.Entry)it.next(); + className = (String)e.getKey(); + map.put(toRefType(className), e.getValue()); + } + + if (className != null) + reload2(map, className + " etc."); + } + + private ReferenceType toRefType(String className) { + List list = jvm.classesByName(className); + if (list == null || list.isEmpty()) + throw new RuntimeException("no such a class: " + className); + else + return (ReferenceType)list.get(0); + } + + private void reload2(Map map, String msg) { + synchronized (trigger) { + startDaemon(); + newClassFiles = map; + request.enable(); + trigger.doSwap(); + request.disable(); + Map ncf = newClassFiles; + if (ncf != null) { + newClassFiles = null; + throw new RuntimeException("failed to reload: " + msg); + } + } + } + + private void startDaemon() { + new Thread() { + private void errorMsg(Throwable e) { + System.err.print("Exception in thread \"HotSwap\" "); + e.printStackTrace(System.err); + } + + public void run() { + EventSet events = null; + try { + events = waitEvent(); + EventIterator iter = events.eventIterator(); + while (iter.hasNext()) { + Event event = iter.nextEvent(); + if (event instanceof MethodEntryEvent) { + hotswap(); + break; + } + } + } + catch (Throwable e) { + errorMsg(e); + } + try { + if (events != null) + events.resume(); + } + catch (Throwable e) { + errorMsg(e); + } + } + }.start(); + } + + EventSet waitEvent() throws InterruptedException { + EventQueue queue = jvm.eventQueue(); + return queue.remove(); + } + + void hotswap() { + Map map = newClassFiles; + jvm.redefineClasses(map); + newClassFiles = null; + } +} diff --git a/src/main/javassist/tool/package.html b/src/main/javassist/tool/package.html new file mode 100644 index 00000000..1566e121 --- /dev/null +++ b/src/main/javassist/tool/package.html @@ -0,0 +1,6 @@ + + +Utility classes. + + + -- 2.39.5