You can't directly replace code that's already been loaded. What you
can do is load new code, and arrange to use the new code instead. For
example, if you want to check for updates when the application is
executed, try this:
1. Write a small loader module that's loaded by the system classloader.
2. Package your real application into a separate JAR file, loaded by a
separate classloader.
3. Check for updates before loading the real application. If there is
an update, you can modify the JAR file *before* you load it.
There are also APIs for replacing a function at a time, but they are
intended for debugging purposes, they don't always work (which is okay
in an interactive debugging environment), and they aren't suitable for
production code.
1. yes you CAN RELOAD CODE that has already been loaded. ( as long as you are
not executing it) , check the sun website there is a working demo
2. it is very easy to do this, but there is 1 small problem , you cannot
update your base class loader easily.
3. split out your jars, that way you can update them on windows, once the
program is started.
4. you can replace the class files , but it means you have to unpack the jar
, remove the class, then put a new one back it, I recommend you work on a JAR
level , not a class level), at a jar level , you Know the jar works because
it compiled. If you use Classes, you have to be 100% sure that every
referenced class is also updated.
the main problem is WINDOWS machines, once you start execution of a jar,
windows marks the whole jar as in use, so you cannot make changes to it.
mac /linux, do not do this!!, you can actually move/rename a file whilst it
is in use, and make copies changes.
The way i do this is:
1. have a properties file.
in the properties file have:
A. Your working class path
B. the system classpath.
C your main program entry point.
here is my example file
loadThisClass=RootClassFrame.RootClassFrame
OurClassPath=MainProgram.jar,ReportWriter.jar,nls_charset12.zip,classes12.zip,
jasperreports-0.6.1.jar,commons-digester-1.3.jar,commons-collections-
2.1.jar,log4j-1.2.8.jar,commons-logging-1.0.2.jar,commons-beanutils-
1.5.jar,bsh-1.3.0.jar,poi-2.0-final-
20040126.jar,servlet.jar,xalan.jar,xercesImpl.jar,xmlParserAPIs.jar,hsqldb-
1.61.jar,itext-1.02b.jar,jung.jar,commons-collections-3.1.jar,colt.jar
SystemClassPath=jasperreports-0.6.1.jar
now the bootloader code.
this program starts by looking in the current directory for any files that
start with "_NEW", and then removes the old jar to replace it with the new
one
then it takes the main class name from the properties file & this starts a
new thread, and loads the main program class.
the main class looks for updates on a server , and downloads them. ( adds a
"new_" to the start of the file , then Quits the program.
However you must ensure that the bootloader jar is NOT in your main jar
package, otherwise when windows launches the java , it LOCKS the .jar file,
that you launch.
so basically at this stage only the bootloader is locked, so we can use it to
add/change/rename files.
the code is useful to you because it is:
1. a Working example , ( currently it sits on about 50 workstations, being
used daily)
2. shows how to play with the system & your own classpath , ( you can still
execute stuff not in the system classpath, by passing your own classpath to
the class loader)
3.but note that it does not , allow the program to continue execution, it
requires a re-start.
to get your continued execution, depends on what extra classes you add into
your program, If it is changes to the GUI, then no amount of trickery ,is
going to update your GUI , by loading new classes.
You would have to throw your GUI away and regenerate it, you may as well just
do a restart of the program.
if it is just to background code ,then it is very easy, just change your
"user classpath" to add in the extra jars,, then call the new classes, via a
NEW classloader , and it will work.
package bootLoader;
//this has become more of a generic boot loader
//it gets the class path from a properties file
// our first class needs to be called "launch_me"
//the properties file comes from the program file.
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.StringTokenizer;
public class bootLoader extends ClassLoader {
private static String loadThisClass = ""; //
"RootClassFrame.RootClassFrame";
private static String OurClassPath1 = "";
private static String SystemClassPath = "";
// private static String loadThisClass2 =
"net.sf.jasperreports.engine.JasperFillManager";
private static String TempPrefix = "new_"; //this holds an extension so
we can downloade without overwriting
private static String backupPrefix = "old_"; //this holds an extension so
we can downloade without overwriting
private static String ourProgramDirectory;
public static URL[] OurClassPath;
private static String classPath;
public static void main(String[] args) throws Exception {
Method mainMethod;
Class toRun;
try {
ourProgramDirectory = System.getProperty("user.dir");
String filesep = File.separator;
//due to a shitty implementation of java in windows we cannot
rename the files that contain the class info
// therefore we look for any files in the current directory that
have a "new_" prefix & replace them.
reNamer();
//we need to trap for errors here
java.util.Properties props = new java.util.Properties();
// java.net.URL url =
ClassLoader.getSystemResource("Theprog.props");
java.io.FileInputStream fis =
new java.io.FileInputStream(new
java.io.File("Theprog.props"));
props.load(fis);
fis.close();
//now read in our properties file
loadThisClass = (props.getProperty("loadThisClass"));
OurClassPath1 = (props.getProperty("OurClassPath"));
SystemClassPath = (props.getProperty("SystemClassPath"));
//(Integer.parseInt(props.getProperty("logLevel")));
// File dir = new File(new File(".").getCanonicalPath());
//this is our new class loader that does not need the classpath
set.
//but we do need an array with files in ( we need to put this
shit in a properties file)
StringTokenizer st = new StringTokenizer(OurClassPath1, ",");
int aa = st.countTokens();
OurClassPath = new URL[aa];
int loop = 0;
while (st.hasMoreTokens()) {
URL x1 =
new URL("file:" + ourProgramDirectory + filesep +
st.nextToken());
OurClassPath[loop] = x1;
loop++;
}
String classPath =
System.getProperties().getProperty("java.class.path", ".");
st = new StringTokenizer(SystemClassPath, ",");
loop = 0;
String it = "";
while (st.hasMoreTokens()) {
it = filesep + st.nextToken() + ":";
loop++;
}
classPath = it + classPath;
System.getProperties().setProperty("java.class.path", classPath);
//
System.out.println(System.getProperties().getProperty("java.class.path",
"."));
ClassLoader loader = new URLClassLoader(OurClassPath);
//force the threads classloader to the url loader
Thread.currentThread().setContextClassLoader(loader);
// toRun = loader.loadClass(loadThisClass);
toRun =
Thread.currentThread().getContextClassLoader().loadClass(loadT
hisClass);
// String[] newArgs = scrubArgs(args);
mainMethod = findMain(toRun);
//debug to print class loaders
ClassLoader current =
Thread.currentThread().getContextClassLoader();
while (current != null) {
System.out.println("load---" + current.getClass());
current = current.getParent();
}
if (mainMethod != null) {
mainMethod.invoke(null, new Object[] { args });
// mainMethod.invoke(null, new object[] { });
} else {
System.out.println("There is no MAIN (String Args[]) to run
");
}
} catch (Exception e) {
System.out.println(
"Exception Caused when trying to load main Class");
e.printStackTrace();
}
}
private static void reNamer() {
//this is real sloppy code it relies on the files starting with
"new_"
String prefixFreeName = "";
String filename = "";
System.out.println("Entered renamer user.dir is: " +
ourProgramDirectory);
try {
File dir = new File(ourProgramDirectory);
String[] children;
//children = dir.list();
// It is also possible to filter the list of returned files.
// This example does not return any files that start with `.'.
FilenameFilter filter =
new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(TempPrefix);
}
};
children = dir.list(filter);
if (children == null) {
// Either dir does not exist or is not a directory
} else {
for (int i = 0; i < children.length; i++) {
// Get filename of file or directory
filename = children
;
//get the filename minus the last 4 characters
prefixFreeName = filename.substring(4,
filename.length());
//we need some error checking here
if (!(0 == ("bootloader.jar".compareToIgnoreCase(
prefixFreeName)))) {
//check if the backup exists first!!
if (new File(backupPrefix + prefixFreeName).exists())
{
// if yes delete it
new File(backupPrefix + prefixFreeName).delete();
//delete any old copies first
}
//check if the current file exists
if (new File(prefixFreeName).exists()) {
if (renamefile(prefixFreeName,
(backupPrefix + prefixFreeName)))
//do the old program file
{
;
} else {
System.out.println("Failed to rename old file
" +
"---" + prefixFreeName + "---to---" +
backupPrefix + prefixFreeName);
}
}
//finally rename the new file to the existing name
if (renamefile(filename, prefixFreeName)) {
;
} else {
System.out.println("Failed to rename new file " +
"---" + filename + "---to---" +
prefixFreeName);
}
} else {
//the file was named boot loader it is a special case
//we need some sort of message to the user to tell
them to rename the bootloader.
;
}
}
}
} catch (Exception e) {
System.out.println("Exception caused Failed to rename a file ");
}
}
private static boolean renamefile(String oldfile, String newfile) {
boolean success = false;
// File (or directory) with old name
File file = new File(oldfile);
// File (or directory) with new name
File file2 = new File(newfile);
// Rename files ONLY
if (!file.isDirectory() && !file2.isDirectory()) {
success = file.renameTo(file2);
}
return success;
}
private static Method findMain(Class passedClass) throws Exception {
Method[] methods = passedClass.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods.getName().equals("main")) {
return methods;
}
}
return null;
}
}
steve