I have two jar files (for the example lets call them Updater.jar and Code.jar). Updater.jar is launched with its main method, and then it launches itself again with a premain method:
package Update;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class InstructionLauncher {
private List<UpdateInstruction> instructions = new ArrayList<UpdateInstruction>();
private static InstructionLauncher instance;
private Process process;
public static InstructionLauncher initialise(){
if(instance !=null) return instance;
else return new InstructionLauncher();
}
public void registerPremain(UpdateInstruction inst){
instructions.add(inst);
}
public void launchNext(){
UpdateInstruction inst = instructions.get(0);
String cls = inst.getClassName() + "." + inst.getMethodName();
String[] args = new String[]{"java", "-javaagent", "JOSUpdater.jar", "-jar", inst.getClassName() + "." + inst.getMethodName()};
ProcessBuilder builder = new ProcessBuilder(args);
try {
exportResource(cls, cls);
} catch (Exception e) {
UpdateManager.revert();
}
try {
Process p = builder.start();
process = p;
} catch (IOException e) {
e.printStackTrace();
}
while(!process.isAlive())launchNext();
}
private InstructionLauncher(){
instance = this;
}
//From http://stackoverflow.com/questions/10308221/how-to-copy-file-inside-jar-to-outside-the-jar
private String exportResource(String resourceName, String clazz) throws Exception {
InputStream stream = null;
OutputStream resStreamOut = null;
String jarFolder;
try {
stream = Class.forName(clazz).getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
if(stream == null) {
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
}
int readBytes;
byte[] buffer = new byte[4096];
jarFolder = new File(Class.forName(clazz).getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getPath().replace('\\', '/');
resStreamOut = new FileOutputStream(jarFolder + resourceName);
while ((readBytes = stream.read(buffer)) > 0) {
resStreamOut.write(buffer, 0, readBytes);
}
} catch (Exception ex) {
throw ex;
} finally {
stream.close();
resStreamOut.close();
}
return jarFolder + resourceName;
}
}
The premain method looks like this at the moment:
package Update;
import java.lang.instrument.Instrumentation;
public class PremainLauncher {
public static void premain(String args, Instrumentation inst){
inst.addTransformer(new Transformer(), true);
System.out.println("Registered instruction for package: " + args);
}
}
What I am wondering, is how do I add the whole external JAR (Code.jar in this example) into the path for the instrumentation?
I know about the Instrumentation.retransformClasses method, but to use that I would need to get a List> of all the classes in the jar, which I have been unable to complete.
Lets say that Code.jar has three class files: Main.class, writer.class and display.class. Is there a way to get a list of each of their class object, not their name?
A Java agent can add jar files simply via the Instrumentation interface it received in the startup method, e.g.
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;
public class PremainLauncher {
public static void premain(String args, Instrumentation inst) throws IOException{
inst.appendToSystemClassLoaderSearch(new JarFile("Code.jar"));
inst.addTransformer(new Transformer(), true);
System.out.println("Registered instruction for package: " + args);
}
}
See Instrumentation.appendToSystemClassLoaderSearch(…). If you intent to instrument JRE classes as well in a way that the instrumented classes need access to the classes of Code.jar, you’ll have to change the bootstrap path instead.
Note that this has to happen as early as possible:
The Java™ Virtual Machine Specification specifies that a subsequent attempt to resolve a symbolic reference that the Java virtual machine has previously unsuccessfully attempted to resolve always fails with the same error that was thrown as a result of the initial resolution attempt. Consequently, if the JAR file contains an entry that corresponds to a class for which the Java virtual machine has unsuccessfully attempted to resolve a reference, then subsequent attempts to resolve that reference will fail with the same error as the initial attempt.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With