From: chiba Date: Tue, 21 Jun 2005 16:39:16 +0000 (+0000) Subject: HotSwap support X-Git-Tag: rel_3_17_1_ga~440 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=aa0900d4cf6479bc598951ea446ea3f5f95a23b6;p=javassist.git HotSwap support git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@184 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- 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/Dump.java deleted file mode 100644 index 69ba4625..00000000 --- a/src/main/javassist/Dump.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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; - -import java.io.*; -import javassist.bytecode.ClassFile; -import javassist.bytecode.ClassFileWriter; - -/** - * Dump is a tool for viewing the class definition in the given - * class file. Unlike the JDK javap tool, Dump works even if - * the class file is broken. - * - *

For example, - *

- * - *

prints the contents of the constant pool and the list of methods - * and fields. - */ -public class Dump { - private Dump() {} - - /** - * Main method. - * - * @param args args[0] is the class file name. - */ - public static void main(String[] args) throws Exception { - if (args.length != 1) { - System.err.println("Usage: java Dump "); - return; - } - - DataInputStream in = new DataInputStream( - new FileInputStream(args[0])); - ClassFile w = new ClassFile(in); - PrintWriter out = new PrintWriter(System.out, true); - out.println("*** constant pool ***"); - w.getConstPool().print(out); - out.println(); - out.println("*** members ***"); - ClassFileWriter.print(w, out); - } -} diff --git a/src/main/javassist/tool/Dump.java b/src/main/javassist/tool/Dump.java new file mode 100644 index 00000000..f8a1a5dd --- /dev/null +++ b/src/main/javassist/tool/Dump.java @@ -0,0 +1,57 @@ +/* + * 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 java.io.*; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ClassFileWriter; + +/** + * Dump is a tool for viewing the class definition in the given + * class file. Unlike the JDK javap tool, Dump works even if + * the class file is broken. + * + *

For example, + *

+ * + *

prints the contents of the constant pool and the list of methods + * and fields. + */ +public class Dump { + private Dump() {} + + /** + * Main method. + * + * @param args args[0] is the class file name. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage: java Dump "); + return; + } + + DataInputStream in = new DataInputStream( + new FileInputStream(args[0])); + ClassFile w = new ClassFile(in); + PrintWriter out = new PrintWriter(System.out, true); + out.println("*** constant pool ***"); + w.getConstPool().print(out); + out.println(); + out.println("*** members ***"); + ClassFileWriter.print(w, out); + } +} 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. + + +