Load libs from a different classloader in a web application - java

When a web application is loaded in Tomcat it is loaded by a specific classloader, right?
I assume that all libraries (under WEB-INF\lib) used by this web application are all loaded by this same classloader?
In this case, is there a way to load a library under a different classloader without any issues?
The reason I want to do this is because Axis uses some configuration properties that are bound to the classloader and would like to do requests with different properties thereby use a different classloader.
Is this possible?

If you want to load classes programmatically at run time, you can use URLClassLoader, but it can be quite tricky to really get it right. You would do something like this:
URL[] urls = new URL[] {
/* URL to your axis jar */,
/* other URLs you need */
};
URLClassLoader classLoader = new URLClassLoader(urls, getClass().getClassLoader());
Class<...> axisClass = classLoader.findClass(/* fully qualified name */);
Then you should be able to create a new instance of this class and use it.
Edit: Here is a more concrete example, albeit not using Axis because it would be too difficult to set up. I have create a JAR file that contains the following class:
public class Hello {
public Hello(String config) {
}
public String getMessage() {
return "Hello World";
}
}
I have copied this jar file to the source folder of my test project, so I can find it using UrlClassloaderTest.class.getResource("hello.jar"). In a web app, you should probably put it into WebContent/WEB-INF (or something similar) and use the method javax.servlet.ServletContext.getRealPath("WEB-INF/hello.jar") to find it. I can then access the Hello class using the URLClassLoader and reflection:
public class UrlClassloaderTest {
public static void main(String[] args) throws Exception {
URL jarUrl = UrlClassloaderTest.class.getResource("hello.jar");
URLClassLoader cl = new URLClassLoader(new URL[] { jarUrl }, UrlClassloaderTest.class.getClassLoader());
Class helloClass = cl.loadClass("test.Hello");
Constructor constructor = helloClass.getConstructor(String.class);
Object helloObject = constructor.newInstance("some configuration");
Method messageMethod = helloClass.getMethod("getMessage");
String message = (String) messageMethod.invoke(helloObject);
System.out.println(message);
}
}
Note that I can not use Hello as a type here because it is not on the class path of the application, and so it is not known to the class loader of the class UrlClassLoaderTest!

Related

get the execution location from within a library jar

I have a class named Utils with a static method that should determine the execution location.
public class Utils {
public static Path getExecutionLocation() throws URISyntaxException {
return Paths.get(Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI());
}
}
Within eclipse this gives me: C:\Users\USERNAME\workspace\PROJECT\bin\main\
Run as a jar this gives me: C:\PATH\TO\JAR\thatJar.jar
Both is correct and expected.
Now I have that Utils class inside a library called someLib.jar.
When I use that library in another project it works if I build a jar of that project with someLib.jar inside.
But in eclipse it returns the path to someLib.jar.
I want it to return the path to the execution directory of the project:
C:\Users\USERNAME\workspace\A_PROJECT_USING_SOMELIB\bin\main\
I tried
return new File(ClassLoader.getSystemClassLoader().getResource(".").getPath()).toPath();
But that failed inside a jar because getResource(".") results in null.
I could give getExecutionLocation a class from inside the project as a parameter and excute getProtectionDomain() on that. But I want to ask here if someone knows a better solution.
Try this:
public class Utils {
public static Path getExecutionLocation(Class c) throws URISyntaxException {
return Paths.get(c.getProtectionDomain().getCodeSource().getLocation().toURI());
}
}
So you pass the class as an argument to the function. For example:
public class Main
{
public static void main(String[] args)
{
Utils.getExecutionLocation(Main.class);
}
}
Note that there may be more straightforward solutions,
But this is the first one that came into my mind, and I thought, why not :)
I've found a solution based on ClassLoader.getSystemClassLoader() approach that gives me the class loader responsible for the main entry point of the project that is using someLib.jar.
Within an IDE I can use getResource(".") to get URL of the root path of all resources (and sources).
From jar file this does not work. So I use getResource("META-INF") to get URL of the META-INF folder (that has the manifest inside) and should always exist in jar files.
Maybe still not optimal. But so far I can work with it.
public static Path getExecutionLocation() throws URISyntaxException, IOException {
// System ClassLoader is on the highest level and responsible for the main entry point
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// get the resources root path (works within IDEs)
URL executionLocation = systemClassLoader.getResource(".");
// fallback for jars
if(executionLocation == null) {
// look for META-INF folder
URL metaInfLocation = systemClassLoader.getResource("META-INF");
// URL looks like "jar:file:/C:/path/to/jar/jarfile.jar!/META-INF"
// openConnection on URL - does not really establish connection but checks if URL would be valid
JarURLConnection connection = (JarURLConnection) metaInfLocation.openConnection();
// extracts URL to jar file
executionLocation = connection.getJarFileURL();
}
// impossible to determine
if(executionLocation == null) throw new RuntimeException("Impossible to determine exeution location");
return Paths.get(executionLocation.toURI());
}

Add or remove specific jars from custom ClassLoader

I want to create dynamically a classloader for executing JSR223 script in a controlled environment but failing,
I'm trying remove/add jars using current(parent) ClassLoader, I tried solution Dynamically removing jars from classpath
public class DistributionClassLoader extends ClassLoader {
public DistributionClassLoader(ClassLoader parent) {
super(parent);
}
private Map<String, ClassLoader> classLoadersByDistribution =
Collections.synchronizedMap(new WeakHashMap<>());
private final AtomicReference<String> distribution = new AtomicReference<>();
#Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
final ClassLoader delegate = classLoadersByDistribution.get(distribution.get());
if (delegate != null) return Class.forName(name, true, delegate);
throw new ClassNotFoundException(name);
}
public void addDistribution(String key, ClassLoader distributionClassLoader){
classLoadersByDistribution.put(key,distributionClassLoader);
}
public void makeDistributionActive(String key){distribution.set(key);}
public void removeDistribution(String key){
final ClassLoader toRemove = classLoadersByDistribution.remove(key);
}
}
But it didn't include all my jars, in test this work
ClassLoader cl = this.getClass().getClassLoader();
Class cls = cl.loadClass("org.springframework.http.HttpStatus");
But using the solution doesn't find class
ClassLoader cl = new DistributionClassLoader(this.getClass().getClassLoader());
Class cls = cl.loadClass("org.springframework.http.HttpStatus");
Exception:
java.lang.ClassNotFoundException: org.springframework.http.HttpStatus
at com.DistributionClassLoader.loadClass(DistributionClassLoader.java:24)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
How can I select specific jars to add or remove from ClassLoader?
EDIT
I'm able to load jars using #czdepski answer but I still want to remove all/most classes except JDK's
Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});
You got the delegation wrong. You never check the parent class loader if it has this class.
If we look at the Javadoc for ClassLoader.loadClass(String,boolean) we find:
Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:
Invoke findLoadedClass(String) to check if the class has already been loaded.
Invoke the loadClass method on the parent class loader. If the parent is null the class loader built into the virtual machine is used, instead.
Invoke the findClass(String) method to find the class.
If the class was found using the above steps, and the resolve flag is true, this method will then invoke the resolveClass(Class) method on the resulting Class object.
Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.
You did override loadClass, but don't do any delegation to it's parent ClassLoader.
Instead you call classLoadersByDistribution.get(distribution.get());, which is most likely null (hard to tell, but always expect WeakHashMap.get() to return null).
If delegate is not null, then you try to load the class from there. This means the loaded class won't use your ClassLoader to load new classes, but instead the ClassLoader you delegated to.
After all, this sounds like a XY Problem. You want to execute some code using the scripting API and somehow control the environment.
Did you try to use a SecurityManager?
About your comment that you need your own ClassLoader to create a ScriptEngineManager: This ClassLoader is used to search for ScriptEngineFactory implementations. This is done using a service provider interface.
If you don't use your own script engine, this should not matter to you.
If your goal is to add a few jars so the engine can use it, create a new URLClassLoader with the platform class loader as parent. (Or extension class loader, depends on the java version.)
Set this ClassLoader as Thread.setContextClassLoader() and create the ScriptEngine.
If you did choose the parent of the URLClassLoader correctly, it will not see classes loadable by the application class loader.

How can I load classes into an running Java Programm

This is the code of my Agent.jar
public class Agent
{
public static void agentmain(String s, Instrumentation instrumentation)
{
try
{
ClassLoader classLoader = null;
for (Class clazz : instrumentation.getAllLoadedClasses())
{
String className = clazz.getName();
if (className.equalsIgnoreCase("ave")) /* Just a class from the running Programm */
{
classLoader = clazz.getClassLoader();
}
}
/* In the Cheat.jar are Classes which im trying to load */
ClassLoader loader = URLClassLoader.newInstance(new URL[]{new URL("C:\\Users\\michi\\Desktop\\Injection\\Cheat.jar")}, classLoader);
Class.forName("de.simplyblack.client.client.module.Category", true, loader);
Class.forName("de.simplyblack.client.client.module.Module", true, loader);
Class.forName("de.simplyblack.client.client.module.ModuleManager", true, loader);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
I am loading this with an extra Programm.
VirtualMachine virtualMachine = VirtualMachine.attach(id);
virtualMachine.loadAgent(new File("C:\\Users\\michi\\Desktop\\Injection\\Client.jar").getAbsolutePath());
virtualMachine.detach();
But this is not working.
Later I visit an Class, and make an call for the ModuleManager class.
If I Inject it, i get an
Class not found: de.simplyblack.client.client.module.ModuleManager
error.
Could you please tell me how I can fix that?
It would help me a lot!
Thanks.
When references within a class are resolved, its defining class loader is used. Your code identifies the defining class loader of the class you want to instrument, but then, creates a new class loader using it as parent loader.
When you ask that new loader to load your classes, the classes very likely are loaded, but they are only reachable through your newly created URLClassLoader. They are not available to other class loaders.
Instrumenting classes with code containing new dependencies to other classes can be very tricky. If the instrumented classes have been loaded by the application class loader, you can use Instrumentation.appendToSystemClassLoaderSearch(JarFile) to add your Cheat.jar to the search path, to make the classes available.
For classes loaded by other loaders, things get more complicated. If they follow the standard parent delegation model, you can use appendToBootstrapClassLoaderSearch(JarFile) to make your classes available to all these class loaders.
For a loader not following the delegation model, you would have to dig deeper, e.g. use Reflection with access override, to call defineClass on it making the classes available in that scope.

Unable to access directory classes after setting classpath

I'm having a hard time setting the classpath for a directory to a package of classes. I'm trying to run a jar file that takes a directory as a command line argument. The program uses the directory to access class files in a folder and uses reflection to explore the class fields and methods.
final File folder = new File(args[0]);
classList = dirParse.listFilesForFolder(folder);
I then go through the classList, get the name of each class, and use the Class.forName() method to access the classes.
Class c = Class.forName(className);
For the line above to work, I have to set the classpath to the address of the directory containing the classes.
I can get the program to run just fine when I'm using a directory of classes that do not belong to a package like below:
java -cp "Explorer.jar:/Users/john/Desktop/TestClass/" explorer.ExplorerDemo /Users/john/Desktop/TestClass/
However, for the following line, monopoly is a package and the program throws a ClassNotFoundException after calling Class.forName(className)
java -cp "Explorer.jar:/Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/" explorer.ExplorerDemo /Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/
For testing purposes, I tried adjusting `Class.forName() call to include the package name like below:
Class c = Class.forName("monopoly."+className);
However, this also throws ClassNotFoundException.
Class.forName is a shortcut to obtaining class information within the context of ClassLoader of the current class. Javadoc states that this is equivalent to
Class.forName("Foo", true, this.getClass().getClassLoader())
Provided that you class directory is supplied as runtime parameter and is not part of the original classpath, I would suggest you instantiating custom URLClassLoader instance that will be pointing to your directory.
Sample code:
public class ReflectionClassAnalysis {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
// URLClassLoader supports both directories and jar files
Path directory = Paths.get("/some/directory/");
Path jar = Paths.get("/some/binary.jar");
// You may be interested in providing parent ClassLoader for your new instance
// You can either use current class ClassLoader like
ClassLoader contextClassLoader = ReflectionClassAnalysis.class.getClassLoader();
// or current thread ClassLoader
// ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader myClassLoader = new URLClassLoader(
new URL[]{
directory.toUri().toURL(),
jar.toUri().toURL()
},
contextClassLoader
);
// You may use ClassLoader directly to load class meta
Class<?> externalClass = myClassLoader.loadClass("your.class.name");
// or supply ClassLoader to forName method
// Class.forName("your.class.name", true, myClassLoader);
// Do your class analysis here
}
}
For JAR with classpath instructions please refer to: Run a JAR file from the command line and specify classpath

ClassLoader classes are not properly loaded in the thread

I am creating a new classLoader using URLClassLoader and trying to set it as the classLoader for the current thread.
But it is not working properly for me.
As per my understanding, if I set a classLoader to the current thread, the methods and interfaces referenced by the Current Thread should be from the present classLoader.
But it is not the case with me. The method is picked up from another jar and I am getting classCastExecption.
Following is the code for getting classLoader:
public ClassLoader getClassLoader(boolean b) {
ClassLoader loader = null;
File file = new File(SamVariables.JAR_FILE);
if (file.exists()){
try {
List<URL> urlsList = new ArrayList<URL>();
urlsList.add(file.toURI().toURL());
URL[] urls = new URL[urlsList.size()];
urlsList.toArray(urls);
URLClassLoader url = new URLClassLoader(urls);
try {
loader = Class.forName("org.jboss.naming.remote.client.InitialContextFactory", false, url).getClassLoader();
} catch (ClassNotFoundException e) {
loader = Class.forName("org.jboss.jms.client.JBossConnectionFactory", false, url).getClassLoader();
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
return loader; // I am successfully getting the classLoader for the class
}
I set it to the current thread
Thread.currentThread().setContextClassLoader(getClassLoader);
But later when I try to get the topicConnectionFactory object, it gives me typecast exception:
topicConnectionFactory = (TopicConnectionFactory) topicConnectionFactObj;
It gives me classCastException.
When I checked the TopicConnectionFactory object, it is coming from another jar file which is causing the issue.
As per my understanding, if I set a classLoader to the current thread,
the methods and interfaces referenced by the Current Thread should be
from the present classLoader.
No, this is a misconception. The context class loader is not used unless code specifically uses it. In particular, the context class loader is not used by the JVM (but it is used by specific APIs, such as for finding an XML parser implementation). Instead, the class loader of the originating class is used.
If you want your code to be able to load classes from a custom class loader, then you must load your classes in that class loader. For example, put those classes in a separate JAR, put that JAR on the URLClassLoader class path, and use reflection to load/call your class from that URLClassLoader.

Categories