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;
}
Related
I have a application.yaml
app:
list: /list.txt
list.txt
Also I have a file with list of strings. It locates into /resources(in the root /resource).
first
second
third
class
public class Bean{
#Value("${app.list}")
private List<String> listProp = new ArrayList<>();
public void print(){
System.out.println(listProp);
}
}
I have found that:
public class ResourceReader {
public static String asString(Resource resource) {
try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) {
return FileCopyUtils.copyToString(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static String readFileToString(String path) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(path);
return asString(resource);
}
}
#Configuration
public class ConfigurationResource {
#Configuration
public class ConfigurationResource {
#Value("${app.list}")
private String pathToFile;
#Bean
public List<String> resourceString() {
String blackList = ResourceReader.readFileToString(pathToFile);
return List.of(blackList.split("\n"));
}
}
}
#RequiredArgsConstructor
public class HelloController {
private final List<String> resourceString;
...
}
This is necessary in order not to manually write a list of strings to the property app.name (there are several hundred lines).
However, I find it difficult to figure out how to do it at low cost. So that it can be easily maintained.
maybe there is an easier way ? I would not like to add a hardcoding value in the configuration class
Maybe someone has some ideas ?
Here is the solution from my understanding if you have to keep lines in the text file that you've shared:
public class Bean {
#Value("${app.list}")
private String listProp; // only get name of file
public void print(){
ClassLoader classLoader = getClass().getClassLoader();
InputStream is = classLoader.getResourceAsStream(listProp);
StringBuilder sb = new StringBuilder();
try {
for (int ch; (ch = is.read()) != -1; ) {
sb.append((char) ch);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println(sb);
}
}
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;
}
I am trying to write an annotation Procssor to detect the methods that are annotated with the #PrintMethod annotation. For example in the test Class below, i want to print the codes within the test Method. Is there a way to do it?
From the AnnotationProcessor class stated below, i am only able get the method name but not the details of the method.
Test Class
public class test {
public static void main(String[] args) {
System.out.println("Args");
}
#PrintMethod
private boolean testMethod(String input) {
if(input!=null) {
return true;
}
return false;
}
}
Annotation Processor Class
public class AnnotationProcessor extends AbstractProcessor {
//......
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//retrieve test Anntoation
Set<? extends Element> ann =roundEnv.getElementsAnnotatedWith(PrintMethod.class);
//Print the Method Name
for(Element e: ann) {
String msg="Element ee :"+ee.getSimpleName().toString();
processingEnv.getMessager().printMessage( javax.tools.Diagnostic.Kind.ERROR, msg, e);
}
}
}
I was curious about this too so I decided to try and figure it out. Turns out to be easier than I expected. All you need to do is leverage the Trees api out of the proprietary tools.jar library. I've made a quick annotation processor along these lines here: https://github.com/johncarl81/printMethod
Here's the meat of it:
#SupportedSourceVersion(SourceVersion.RELEASE_6)
#SupportedAnnotationTypes("org.printMethod.PrintMethod")
public class PrintMethodAnnotationProcessor extends AbstractProcessor {
private Trees trees;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv); //initialize the Trees api.
}
#Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
MethodPrintScanner visitor = new MethodPrintScanner();
for (Element e : roundEnvironment.getElementsAnnotatedWith(PrintMethod.class)) {
TreePath tp = trees.getPath(e);
// visit the annotated methods
visitor.scan(tp, trees);
}
return true;
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
And the MethodPrintScanner:
public class MethodPrintScanner extends TreePathScanner {
#Override
public Object visitMethod(MethodTree methodTree, Object o) {
System.out.println(methodTree);
return null;
}
}
You can see that we are able to visit the TreePath associated with the given annotated Element. For each method, we simply println() the methodTree which gives us the contents of the method.
Using your example, here's the output of the program during compilation:
#PrintMethod()
private boolean testMethod(String input) {
if (input != null) {
return true;
}
return false;
}
It's one thing to make it work in your IDE. But it's another to detect them once your code is packed inside jar files. The following code can manage both.
public static List<Class> getPackageClassListHavingAnnotation(String pPackageName,
Class<? extends Annotation> pAnnotation) throws Exception
{
try
{
List<Class> classList = getPackageClassList(pPackageName);
if ((pAnnotation == null) || (classList == null)) return classList;
List<Class> resultList = new ArrayList<Class>(classList.size());
outerLoop:
for (Class clazz : classList)
{
try
{
for (Method method : clazz.getMethods())
{
if (method.isAnnotationPresent(pAnnotation))
{
resultList.add(clazz);
continue outerLoop;
}
}
}
catch (Throwable e)
{
}
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
It requires the following helper methods:
public static List<Class> getPackageClassList(String pPackageName) throws Exception
{
try
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = pPackageName.replace('.', '/');
List<File> dirs = new ArrayList<File>();
List<JarFile> jars = new ArrayList<JarFile>();
Enumeration<URL> resources = classLoader.getResources(path);
if (resources != null)
{
String fileName;
URL resource;
File file;
while (resources.hasMoreElements())
{
resource = resources.nextElement();
fileName = resource.getFile();
if (fileName.contains("!"))
{
// jar file
resource = new URL(StringUtil.getArrayFromString(fileName, "!")[0]);
file = urlToFile(resource);
if (!file.exists()) continue;
jars.add(new JarFile(file));
}
else
{
// class file that is not in a jar file
file = urlToFile(resource);
if (!file.exists()) continue;
dirs.add(file);
}
}
}
List<Class> resultList = new ArrayList<Class>(1000);
List<Class> tmpClassList;
for (File directory : dirs)
{
tmpClassList = getPckDirClassList(directory, pPackageName);
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
for (JarFile jar : jars)
{
tmpClassList = getPckJarClassList(jar, pPackageName);
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
private static List<Class> getPckJarClassList(JarFile pJar, String pPackageName)
{
if ((pJar == null) || (pPackageName == null)) return null;
List<Class> resultList = new ArrayList<Class>(100);
Enumeration<JarEntry> jarEntries = (pJar.entries());
JarEntry jarEntry;
String fullClassName;
while (jarEntries.hasMoreElements())
{
jarEntry = jarEntries.nextElement();
fullClassName = jarEntry.getName().replaceAll("/", ".");
if (!fullClassName.startsWith(pPackageName)) continue;
if (!fullClassName.endsWith(".class")) continue;
// do not do a Class.forName for the following path, this can crash the server
try
{
resultList.add(Class.forName(fullClassName.substring(0, fullClassName.length() - 6)));
}
catch (Throwable e)
{
}
}
return (resultList.isEmpty()) ? null : resultList;
}
/**
* Recursive method to find all classes in a package directory tree.
*/
private static List<Class> getPckDirClassList(File pDirectory, String pPackageName) throws ClassNotFoundException
{
try
{
if ((pDirectory == null) || (pPackageName == null)) return null;
if (!pDirectory.exists()) return null;
File[] files = pDirectory.listFiles();
if ((files == null) || (files.length == 0)) return null;
List<Class> resultList = new ArrayList<Class>(100);
List<Class> tmpClassList;
for (File file : files)
{
if (file.isDirectory())
{
tmpClassList = getPckDirClassList(file, pPackageName + "." + file.getName());
if (tmpClassList != null) resultList.addAll(tmpClassList);
}
else if (file.getName().endsWith(".class"))
{
try
{
resultList.add(Class.forName(pPackageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
catch (Throwable e)
{
}
}
}
return (resultList.isEmpty()) ? null : resultList;
}
catch (Exception e)
{
return null;
}
}
This code has been tested with .jar files on both windows and unix systems. It has also been tested with .java files in IntelliJ on windows.
For my instrumentation tool, I want to provide a wrapping ClassLoader that is used to start a main method after specific classes have been instrumented. My ClassLoader should load instrumented versions of certain classes. But for Jetty and JUnit, this approach is severly limited because they build their own classloading hierarchy.
I don't want to pass VM arguments, so I can't change the SystemClassLoader. But I can force-feed it with my classes by using reflection to make ClassLoader.defineClass(String, byte[], int, int) public.
ClassLoader scl = ClassLoader.getSystemClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
for (String binaryName : classNamesToLoad) {
byte[] bytecode = this.declaredClasses.get(binaryName);
defineClass.invoke(scl, binaryName, bytecode, 0, bytecode.length);
}
defineClass.setAccessible(false);
This is just great - but there's one problem left: If some of my classes inherit from or contain other classes, they have to be loaded in the right order because the SystemClassLoader loads all classes the current one depends on - and would load the uninstrumented version.
Here is an example with some (poorly named) classes and the order they would have to be loaded in:
A
A.A extends B.A
B
B.A extends B.C
B.C
would have to be loaded in order
B
B.C
B.A
A
A.A
if I want to load only the instrumented version.
Is there an easy way out - e.g. a "setSystemClassLoader" method I didn't spot yet?
A workaround by which I wouldn't need to manipulate the SystemClassLoader?
Or do I really have to do a full transitive dependency analysis starting on the classes I want to load to determine the right order (and in this case: is there any "prior art" I can work with)?
Thanks!
Looks like there's no way around the transitive dependency analysis.
I solved it this way, and I really hope someone can profit from this implementation:
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
public class DependencyDetector {
private static class Node implements Comparable<Node> {
private final String binaryName;
private final Node[] imports;
private final int score;
private Node(String binaryName, Node...imports) {
this.binaryName = binaryName;
this.imports = imports;
this.score = calculateScore(imports);
}
public int compareTo(Node o) {
return score - o.score;
}
private int calculateScore(Node...imports) {
int newScore = 0;
for (Node n : imports) {
if (n.score >= newScore) {
newScore = n.score + 1;
}
}
return newScore;
}
}
private Map<String, Node> nodes = new HashMap<String, Node>();
public DependencyDetector add(ClassNode node) {
Node n = nodes.get(node.name);
if (n == null) {
n = createNode(node);
}
return this;
}
private Node createNode(ClassNode node) {
String binaryName = node.name;
String[] importNames = extractImportedBinaryNames(node);
Node[] imports = new Node[importNames.length];
for (int i = 0; i < imports.length; i++) {
String importName = importNames[i];
Node imp = nodes.get(importName);
if (imp == null) {
ClassNode cn = new ClassNode();
String path = importName.replace('.', '/') + ".class";
try {
new ClassReader(
ClassLoader.getSystemResourceAsStream(path)
).accept(cn, ClassReader.SKIP_CODE);
} catch (IOException e) {
throw new RuntimeException(
"could not read class " + importName);
}
imp = createNode(cn);
nodes.put(importName, imp);
}
imports[i] = imp;
}
Node result = new Node(binaryName, imports);
nodes.put(binaryName, result);
return result;
}
private String[] extractImportedBinaryNames(ClassNode node) {
String binaryName = node.name;
ArrayList<String> nodesToAdd = new ArrayList<String>();
int endOfOuter = binaryName.lastIndexOf('$');
if (endOfOuter >= 0) {
nodesToAdd.add(binaryName.substring(0, endOfOuter));
}
if (node.superName != null) {
nodesToAdd.add(node.superName);
}
if (node.interfaces != null) {
for (String interf : (List<String>) node.interfaces) {
if (interf != null) {
nodesToAdd.add(interf);
}
}
}
return nodesToAdd.toArray(new String[nodesToAdd.size()]);
}
public String[] getClassesToLoad(String...binaryNames) {
String[] classNames = binaryNames != null && binaryNames.length > 0
? binaryNames.clone()
: nodes.keySet().toArray(new String[nodes.size()]);
ArrayDeque<Node> dependencyQueue = new ArrayDeque<Node>();
for (String className : classNames) {
Node node = nodes.get(className.replace('.', '/'));
dependencyQueue.add(node);
if (node == null) {
throw new RuntimeException(
"Class " + className + " was not registered");
}
}
HashMap<String, Node> dependencyMap = new HashMap<String, Node>();
while (!dependencyQueue.isEmpty()) {
Node node = dependencyQueue.removeFirst();
dependencyMap.put(node.binaryName, node);
for (Node i : node.imports) {
dependencyQueue.addLast(i);
}
}
ArrayList<Node> usedNodes =
new ArrayList<Node>(dependencyMap.values());
Collections.sort(usedNodes);
String[] result = new String[usedNodes.size()];
int i = 0;
for (Node n : usedNodes) {
result[i++] = n.binaryName.replace('/', '.');
}
return result;
}
public boolean contains(String binaryName) {
return nodes.containsKey(binaryName.replace('.', '/'));
}
}
It's used like this: on a DependencyDetector, you call add(ClassNode) to add a ClassNode and all its dependencies (all classes it extends or implements or is contained by). When you are done building the dependency tree, you call getClassesToLoad() to retrieve all dependencies as a String[] containing the binary names in the necessary order. You can also just ask for a subset of all added classes and their dependencies by specifying the binary names as a parameter of getClassesToLoad(...).
Now, when I instrument classes, I also add the ClassNode to the DependencyDetector and can retrieve everything I need to pass it into a method like this:
/**
* load the specified classes (or all instrumented classes)
* and all their dependencies with the specified ClassLoader.
* #param loader
* #param binaryNames binary names of all classes you want to load
* - none loads all instrumented classes
*/
public void loadIntoClassLoader(ClassLoader loader, String...binaryNames) {
final String[] classNamesToLoad =
dependencies.getClassesToLoad(binaryNames);
Method defineClass = null;
Method findLoadedClass = null;
try {
// crack ClassLoader wide open and force-feed it with our classes
defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class,
int.class, int.class);
defineClass.setAccessible(true);
findLoadedClass = ClassLoader.class.getDeclaredMethod(
"findLoadedClass", String.class);
findLoadedClass.setAccessible(true);
for (String binaryName : classNamesToLoad) {
if (!binaryName.startsWith("java.")) {
if (findLoadedClass.invoke(loader, binaryName) == null) {
byte[] bytecode = getBytecode(binaryName);
defineClass.invoke(loader, binaryName, bytecode,
0, bytecode.length);
} else if (declaredClasses.containsKey(binaryName)) {
throw new RuntimeException(
"Class " + binaryName + " was already loaded, " +
"it must not be redeclared");
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(
"could not load classes into ClassLoader", e);
} finally {
rehideMethod(findLoadedClass);
rehideMethod(defineClass);
}
}
private void rehideMethod(Method m) {
if (m != null) {
try {
m.setAccessible(false);
} catch (Exception e) {
}
}
}
which relies on
private final DependencyDetector dependencies = new DependencyDetector();
private final Map<String, byte[]> declaredClasses = new HashMap<String, byte[]>();
private byte[] getBytecode(String binaryName) {
byte[] bytecode = declaredClasses.get(binaryName);
if (bytecode == null) {
// asBytes loads the class as byte[]
bytecode =
asBytes(binaryName.replace('.', '/') + ".class");
}
return bytecode;
}
That's pretty much it and it works great in every situation I encountered so far.
use instance of to check the object is whether belongs to the class.
if (aAnimal instanceof Fish){
Fish fish = (Fish)aAnimal;
fish.swim();
}
else if (aAnimal instanceof Spider){
Spider spider = (Spider)aAnimal;
spider.crawl();
}
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
}
}