I am attempting to load classes dynamically into a component. I am using a file chooser to select the .JAR file that will be loaded and then a option pane to get the name of the class.
I have trawled the internet looking for how to convert a java file to a URL in order to load it in URLClassLoader and I have come up with:
File myFile = filechooser.getSelectedFile();
String className = JOptionPane.showInputDialog(
this, "Class Name:", "Class Name", JOptionPane.QUESTION_MESSAGE);
URL myUrl= null;
try {
myUrl = myFile.toURL();
} catch (MalformedURLException e) {
}
URLClassLoader loader = new URLClassLoader(myUrl);
loader.loadClass(className);
I am now getting a 'cannot find symbol' error for loading the URL into the URLClassLoader
I like the ClassPathHacker class mentioned in the answer by Zellus, but it's full of deprecated calls and bad practices, so here's a rewritten version that also caches the Classloader and the addUrl method:
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.IOException;
import java.io.File;
public class ClassPathHacker{
private static final Class<URLClassLoader> URLCLASSLOADER =
URLClassLoader.class;
private static final Class<?>[] PARAMS = new Class[] { URL.class };
public static void addFile(final String s) throws IOException{
addFile(new File(s));
}
public static void addFile(final File f) throws IOException{
addURL(f.toURI().toURL());
}
public static void addURL(final URL u) throws IOException{
final URLClassLoader urlClassLoader = getUrlClassLoader();
try{
final Method method = getAddUrlMethod();
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[] { u });
} catch(final Exception e){
throw new IOException(
"Error, could not add URL to system classloader");
}
}
private static Method getAddUrlMethod()
throws NoSuchMethodException{
if(addUrlMethod == null){
addUrlMethod =
URLCLASSLOADER.getDeclaredMethod("addURL", PARAMS);
}
return addUrlMethod;
}
private static URLClassLoader urlClassLoader;
private static Method addUrlMethod;
private static URLClassLoader getUrlClassLoader(){
if(urlClassLoader == null){
final ClassLoader sysloader =
ClassLoader.getSystemClassLoader();
if(sysloader instanceof URLClassLoader){
urlClassLoader = (URLClassLoader) sysloader;
} else{
throw new IllegalStateException(
"Not an UrlClassLoader: "
+ sysloader);
}
}
return urlClassLoader;
}
}
ClassPathHacker.java found in this forum thread, is an option to load classes dynamically.
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
public class ClassPathHacker {
private static final Class[] parameters = new Class[]{URL.class};
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method
public static void addFile(File f) throws IOException {
addURL(f.toURL());
}//end method
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
}//end class
Take a look at this related question: How should I load Jars dynamically at runtime?
The constructor of URLClassLoader takes an array of URLs, not a single URL.
I rewrote this in scala in case anyone needs as it isn't 100% trivial :)
/*
* Class which allows URLS to be "dynamically" added to system class loader
*/
object class_path_updater {
val URLCLASSLOADER = classOf[URLClassLoader]
var urlClassLoader = getUrlClassLoader
var addUrlMethod = getAddUrlMethod
/*
* addFile - have to use reflection to retrieve and call class loader addURL method as it is protected
*/
def addFile(s: String) = {
val urlClassLoader = getUrlClassLoader
try {
val method = getAddUrlMethod
method.setAccessible(true)
val v = (new File(s)).toURI.toURL
invoke(urlClassLoader, method, Array[AnyRef](v))
def invoke(proxy: AnyRef, m: Method, args: Array[AnyRef]) = m.invoke(proxy, args: _*)
}
}
private def getAddUrlMethod: Method = {
if (addUrlMethod == null) addUrlMethod = URLCLASSLOADER.getDeclaredMethod("addURL", classOf[URL])
addUrlMethod
}
private def getUrlClassLoader: URLClassLoader = {
if (urlClassLoader == null) {
val sysLoader = ClassLoader.getSystemClassLoader
sysLoader match {
case x: URLClassLoader => urlClassLoader = sysLoader.asInstanceOf[URLClassLoader]
case _ => throw new IllegalStateException("Not a UrlClassLoader: " + sysLoader)
}
}
urlClassLoader
}
}
Related
I am trying to test a module that uses a ContextWrapper class.
I try to mock it using this code:
ContextWrapper contextWrapper = mock(ContextWrapper.class);
File myFile = mock(File.class);
doReturn(mFile).when(contextWrapper).getDir(anyString(), anyInt());
The method being tested still uses its own declaration of ContextWrapper
and return an Exception
java.lang.RuntimeException: Method getDir in android.content.ContextWrapper not mocked
I've checked other entries related to this issue and I discovered that it may be due to being an exclusive Android class.
I also tried Mockito v2's
given(contextWrapper.getDir(AppFolders.getFolderPath(), Context.MODE_PRIVATE)).willReturn(mFile);
Edit:
Here is the module I am trying to test
public void send(final String id, final ErrorReport errorReport, final MyCallback callback) {
if(null == errorReport || null == errorReport.getType()){
callback.result("Error Report Empty"));
return;
}
try {
processResponse(sendReport(id, errorReport),
callback,
new Executable() {
#Override
public void execute() throws Exception {
callback.result("SUCCESS");
}
},
origin);
} catch (Exception e) {
e.printStackTrace();
}}
and
here is the module where the ContextWrapper is located
private MyResponse sendReport(String id, ErrorReport errorReport) throws Exception {
ContextWrapper contextWrapper = new ContextWrapper(context);
AppFolders.setCustomerInfoZipFilename(id);
File logFolder = contextWrapper.getDir(AppFolders.getFolderPath(), Context.MODE_PRIVATE);
File zipFile = new File(logFolder.getAbsolutePath() + AppFolders.getCustomerInfoZipFilename());
String zipBase64 = getBase64String(zipFile);
ErrorReportData errorReportData = new ErrorReportData();
errorReportData.setData(zipBase64);
MyResponse response = myClient.errorReport(errorReportData);
return response;}
And here is the test module
public class Send extends BaseTest{
private static final String URL_METHOD_CALL = "send/";
private File myFile;
private ContextWrapper contextWrapper;
#Test
public void when_send_response_valid_then_callback_success() throws Exception {
ErrorReport errorReport = new ErrorReport();
errorReport.setType(new ErrorType());
contextWrapper = mock(ContextWrapper.class);
myFile = mock(File.class);
whenNew(ContextWrapper.class).withArguments(any(Context.class)).thenReturn(contextWrapper);
doReturn(myFile).when(contextWrapper).getDir(anyString(), anyInt());
MyResponse response = MockClient.getMockClient().submit(mockHost+URL_METHOD_CALL, StatusCode.STATUS_SUCCESS);
doSetToken();
when(client.errorReport(any(ErrorReportData.class), any(ClientImpl.Header.class))).thenReturn(response);
service.send(mockUser, errorReport, new Callback<String>() {
#Override
public void result(Response<String> response) {
assertEquals(StatusCode.STATUS_SUCCESS, response.getStatus());
}
});
}}
Try this
File myFile = mock(File.class);
when(contextWrapper.getDir(anyString(), anyInt())).thenReturn(myFile);
I made a java project, the project only contais this class:
package test.processor;
public abstract class Processor {
public abstract void loadData(String objectId);
public abstract void processData();
public abstract void saveData(String objectId);
}
The project is exported as a jar file (processor.jar)
Then I made another project that imports processor.jar and there is a class that extends Processor:
package test.process;
import test.processor.Processor;
public class Process extends Processor{
#Override
public void loadData(String objectId) {
System.out.println("LOAD DATAAAAAAAAAAAA");
}
#Override
public void processData() {
System.out.println("PROCESS DATAAAAAAAAAAAA");
}
#Override
public void saveData(String objectId) {
System.out.println("SAVE DATAAAAAAAAAAAA");
}
}
This project is also exported as jar (plugin.jar).
Finally, I coded something to load the plugins dynamically:
import test.processor.Processor;
public class Test {
public void testPlugins(){
Processor plugin = (Processor) loadJar(
"C:\\Users\\...\\Desktop\\plugin.jar",
"test.process.Process");
processor.loadData("dada");
}
private Object loadJar(String jar, String className){
File jarFile = new File(jar);
Object instance = null;
try {
URL jarpath = jarFile.toURI().toURL();
String jarUrl = "jar:" + jarpath + "!/";
URL urls[] = { new URL(jarUrl) };
URLClassLoader child = new URLClassLoader(urls);
Class classToLoad = Class.forName(nomeClasse, true, child);
instance = classToLoad.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
return instance;
}
}
If I run that code inside a main method it works correctly, once I try to run it in the server there is a problem when loading the class, I get a ClassNotFoundException (Processor).
I tried putting the jar in the tomcat/lib, project/WEB-INF/lib and nothing changed.
Any idea of what Im doing wrong?
I didn't solve it the way I wanted, but I solved it:
First I tried loading the process.jar manually:
private Object loadJars(String processJar, String pluginJar, String className){
File processJarFile = new File(processJar);
File pluginJarFile = new File(pluginJar);
Object instance = null;
try {
URL processJarPath = processJarFile.toURI().toURL();
String processJarUrl = "jar:" + processJarPath + "!/";
URL pluginJarPath = pluginJarFile.toURI().toURL();
String pluginJarUrl = "jar:" + pluginJarPath + "!/";
URL urls[] = { new URL(processJarUrl), new URL(pluginJarUrl) };
URLClassLoader child = new URLClassLoader(urls);
Class classToLoad = Class.forName(nomeClasse, true, child);
instance = classToLoad.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
return instance;
}
That loads the Process class correctly, the problem happens in the testPlugins mehod, once it tries to cast to Processor (ClassCastException, can't cast Process to Processor):
public void testPlugins(){
Processor plugin = (Processor) loadJars("C:\\Users\\...\\Desktop\\processor.jar",
"C:\\Users\\...\\Desktop\\plugin.jar",
"test.process.Process");
processor.loadData("dada");
}
Still need to read a lot about classloading but I guess the problem is that it doesn't recognize the Processor loaded from C:\Users\...\Desktop\processor.jar as the same as the Processor loaded from the webapp context or it "forgets" Process extends Processor.
I was in a hurry so I didn't have time to research, to solve the problem I invoked the methods using reflection:
public void modifiedTestPlugins(){
Object plugin = loadJar("C:\\Users\\...\\Desktop\\processor.jar",
"C:\\Users\\...\\Desktop\\plugin.jar",
"test.process.Process");
try {
Method processData = findMethod(obj.getClass(), "processData");
//here I invoke the processData method, it prints: PROCESS DATAAAAAAAAAAAA
loadData.invoke(processData, new Object[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
private static Method findMethod(Class clazz, String methodName) throws Exception {
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName))
return methods[i];
}
return null;
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
Objective:
Run a class
Change a second class
Save and Compile second class
Without stopping and starting first class the changes to second class should be visible in the console
Problem:
Currently the changes do not reflect after saving and compiling.
I think Joachim's code fragment works perfectly fine (not tested):
public class Autosaver implements Runnable {
public static void main(String args[]) {
Autosaver instance = new Autosaver();
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(instance, 0, 5, TimeUnit.SECONDS);
}
#Override
public void run() {
try {
Class<? extends Test> Test_class = reloadClass(Test.class.getProtectionDomain().getCodeSource().getLocation(), Test.class.getName());
new Autorunner(Test_class, new File("Test.txt")).run();
} catch (Exception e) {
e.printStackTrace();
}
}
private static <X> Class<X> reloadClass(URL classLocation, String className) throws ClassNotFoundException, IOException {
URLClassLoader loader = new URLClassLoader(new URL[] { classLocation }, String.class.getClassLoader());
#SuppressWarnings({"unchecked"})
Class<X> result = (Class<X>) loader.loadClass(className);
loader.close();
return result;
}
}
If you want to reload a changed classfile, you don't have to ask the classloader which has already loaded the pre-version, this will deliver always this already loaded version . Use a new classloader, for example
...
Class<?> reloadClass(String classLocation, String className) throws Exception {
URL url = new File(classLocation).toURI().toURL();
URLClassLoader cl = new URLClassLoader(new URL[] { url }, String.class.getClassLoader());
Class<?> c = cl.loadClass(className);
cl.close();
return c;
}
...
EDIT:
Ok, i tested it with a simplified version of your code. The changes in my is only a little bit cosmetic (copied from Binkan Salaryman). It works.
public class Autorunner extends Thread {
private Class runnable;
private File output;
public Autorunner(Class runnable, File output) {
this.runnable = runnable;
this.output = output;
}
#Override
public void run() {
try {
//This is only to get the location of the classfile
URL url = Test.class.getProtectionDomain().getCodeSource().getLocation();
Class runtimeClass = reloadClass(url,Test.class.getName());
Method method = runtimeClass.getMethod("main", String[].class);
method.invoke(null, (Object) null);
System.out.flush();
} catch (NoSuchMethodException | SecurityException | IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException ex) {
}
}
Class<?> reloadClass(URL classLocation, String className) throws ClassNotFoundException, IOException {
URLClassLoader cl = new URLClassLoader(new URL[] { classLocation }, String.class.getClassLoader());
Class<?> c = cl.loadClass(className);
cl.close();
return c;
}
Here is a tested, fully working, demonstrative class hotswap test program.
Before running, you need to create "./Test.jar" and "./tmp/Test.jar" and put in a file "Test.class" (without package in code, without folder in jar) you've compiled with a main method and a System.out.println statement.
If something does not work as expected, be sure to give detailed error descriptions and what you've tried.
Code for "./Autosaver.jar" (name doesn't matter):
public class Program {
private static final File Test_classLocation = new File("./Test.jar");
private static final File alternativeTest_classLocation = new File("./tmp/Test.jar");
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
System.out.println("Test.class location = " + Test_classLocation.getAbsolutePath());
System.out.println("alternative Test.class location = " + alternativeTest_classLocation.getAbsolutePath());
while (true) {
testInvocation();
swapFiles(Test_classLocation, alternativeTest_classLocation);
Thread.sleep(3000L);
testInvocation();
swapFiles(Test_classLocation, alternativeTest_classLocation);
Thread.sleep(3000L);
}
}
private static void testInvocation() throws IOException, ClassNotFoundException {
Class<?> Test_class = reloadClass(Test_classLocation.toURI().toURL(), "Test");
invokeMain(Test_class, new String[0]);
}
private static void swapFiles(File a, File b) throws IOException {
Path aTempPath = new File(b.getAbsolutePath() + ".tmp").toPath();
Files.move(a.toPath(), aTempPath);
Files.move(b.toPath(), a.toPath());
Files.move(aTempPath, b.toPath());
}
private static <X> Class<X> reloadClass(URL classLocation, String className) throws ClassNotFoundException, IOException {
URLClassLoader loader = new URLClassLoader(new URL[]{classLocation}, null);
#SuppressWarnings({"unchecked"})
Class<X> result = (Class<X>) loader.loadClass(className);
loader.close();
return result;
}
private static void invokeMain(Class<?> mainClass, String[] args) {
try {
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[]{args});
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
System.err.println("invocation of " + mainClass.getName() + ".main(" + String.join(",", args) + ") threw an exception:");
e.printStackTrace();
}
}
}
Code for "./Test.jar!Test.class":
public class Test {
public static void main(String[] args) {
System.out.println("old " + Test.class);
}
}
Code for "./tmp/Test.jar!Test.class":
public class Test {
public static void main(String[] args) {
System.out.println("new" + Test.class);
}
}
Output:
Test.class location = D:\rd\test\out\artifacts\Autosaver\.\Test.jar
alternative Test.class location = D:\rd\test\out\artifacts\Autosaver\.\tmp\Test.jar
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
old class Test
new class Test
...
You can download a zip of the demo here.
I use spring framework to find the class and its methods and arguments dynamically.
these are the methods I use :
public List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
List<Class> candidates = new ArrayList<Class>();
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + "**/*.class";
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (isCandidate(metadataReader)) {
candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
}
}
}
return candidates;
}
public String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
try {
Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
if (c.getAnnotation(Controller.class) != null) {
return true;
}
}
catch(Throwable e){
}
return false;
}
I load the class which has got annotation #Controller. It is working fine but I want to load only the class not interface also how do I get the methods and the arguments of the class loaded.
EDIT :
This is how I get all the class names and try to get the methods name :
List classNames = hexgenClassUtils.findMyTypes("com.hexgen.*");
Iterator<Class> it = classNames.iterator();
while(it.hasNext())
{
Class obj = it.next();
System.out.println("Class :"+obj.toString());
cls = Class.forName(obj.toString());
Method[] method = cls.getMethods();
for (Method method2 : method) {
System.out.println("Method name : "+method2.toGenericString());
}
// TODO something with obj
}
The problem I face is class com.hexgen.api.facade.HexgenWebAPI here class is coming because of which I am not able to load the class dynamically and get the following exception.
java.lang.ClassNotFoundException: class com.hexgen.api.facade.HexgenWebAPI so how to solve it.
Kindly help me to find the solution.
Best Regards
try
Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
if (!c.isInterface() && c.getAnnotation(Controller.class) != null) {
return true;
}
Despite warnings to drop my present course of action, I currently see no better way to solve my problem. I must generate Java code at runtime, then compile it, load it and reference it.
Problem is that the generated code imports code that has already been loaded by the system class loader (I suppose) - that is, code present in one of the jars on my classpath.
(I run inside a Tomcat 6 web container over Java 6.) You may ask yourselves why that is a problem - well I sure don't know - but fact is that I get compilation errors:
/W:/.../parser/v0.5/AssignELParser.java:6:
package com.xxx.yyy.zzz.configuration
does not exist
Following some examples off the internet I have defined the following classes:
class MemoryClassLoader extends ChainedAction {
private static final Logger LOG = Logger.getLogger(MemoryClassLoader.class);
private LoaderImpl impl;
private class LoaderImpl extends ClassLoader {
// The compiler tool
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Compiler options
private final Iterable<String> options = Arrays.asList("-verbose");
// DiagnosticCollector, for collecting compilation problems
private final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
// Our FileManager
private final MemoryFileManager manager = new MemoryFileManager(this.compiler);
public LoaderImpl(File sourceDirectory) {
List<Source> list = new ArrayList<Source>();
File[] files = sourceDirectory.listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return name.endsWith(Kind.SOURCE.extension);
}
});
for (File file : files) {
list.add(new Source(file));
}
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, list);
Boolean compilationSuccessful = task.call();
LOG.info("Compilation has " + ((compilationSuccessful) ? "concluded successfully" : "failed"));
// report on all errors to screen
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
LOG.warn(diagnostic.getMessage(null));
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (this.manager) {
Output output = manager.map.remove(name);
if (output != null) {
byte[] array = output.toByteArray();
return defineClass(name, array, 0, array.length);
}
}
return super.findClass(name);
}
}
#Override
protected void run() {
impl = new LoaderImpl(new File(/* Some directory path */));
}
}
class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
final Map<String, Output> map = new HashMap<String, Output>();
MemoryFileManager(JavaCompiler compiler) {
super(compiler.getStandardFileManager(null, null, null));
}
#Override
public Output getJavaFileForOutput(Location location, String name, Kind kind, FileObject source) {
Output output = new Output(name, kind);
map.put(name, output);
return output;
}
}
class Output extends SimpleJavaFileObject {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output(String name, Kind kind) {
super(URI.create("memo:///" + name.replace('.', '/') + kind.extension), kind);
}
byte[] toByteArray() {
return this.baos.toByteArray();
}
#Override
public ByteArrayOutputStream openOutputStream() {
return this.baos;
}
}
class Source extends SimpleJavaFileObject {
public Source(File file) {
super(file.toURI(), Kind.SOURCE);
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
StringBuilder sb = new StringBuilder("");
try {
File file = new File(uri);
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
sb = new StringBuilder((int) file.length());
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sb.toString();
}
}
It seems that the inner class LoaderImpl by extending the ClassLoader class and by not calling an explicit super constructor should reference as its parent class loader the system class loader.
If it does so then why do I get the "runtime" compilation error - above? Why does it not find the code for the imported class?
Not sure if it can help, but have you tried to specify classpath explicitly?
getClassPath()
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL[] urls = ((URLClassLoader) classLoader).getURLs();
StringBuilder buf = new StringBuilder(1000);
buf.append(".");
String separator = System.getProperty("path.separator");
for (URL url : urls) {
buf.append(separator).append(url.getFile());
}
}
classPath = buf.toString();
and then
options.add("-classpath");
options.add(getClassPath());
I also can't see where do you pass LoaderImpl instance to the compiler. Shouldn't it be done explicitly?