Test case below. Output:
custom loading of: pkg.TestRun
ran.
wish this would run in my custom classloader
What I want to happen is to see "custom loading of: pkg.TestRun" show up between the second and third lines of the output.
How can I get it to load the dependencies of a class from my custom classloader, even when the first class is loaded from the parent? Note, that in my real case, I get a class not found exception because the equivalent of OtherClass is not known to the parent classloader.
I know one solution is to have the custom class loader explicitly load TestRun. However, how to load TestRun is already known to the parent classloader and I don't want to have to manage finding it separately since it's already done and it might be tricky for me to somehow figure that out when it's already being managed without me doing anything. And I've tried to do something like super.getResource (returns null) or findClass (already sets parent as classloader for it) but neither worked.
So, can I let the parent find the class, but the custom loader define it? Or, is there just a way to make it so that it will always use my custom loader to look for dependencies?
package pkg;
public class TestCL {
static class MyCL extends ClassLoader {
MyCL(ClassLoader parent) {
super(parent);
}
#Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
System.out.println("custom loading of: " + name);
return getParent().loadClass(name);
}
}
public static void main(String[] args) throws Exception {
MyCL cl = new MyCL(Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(cl);
cl.loadClass("pkg.TestRun").getMethod("run", new Class[] {}).invoke(null);
}
}
class TestRun {
public static void run() {
System.out.println("ran.");
OtherClass.runAlso();
}
}
class OtherClass {
public static void runAlso() {
System.out.println("wish this would run in my custom classloader");
}
}
The problem was you were not loading anything using your custom classloader
It asks it parent to load the class, so everything was being loaded by the main classloader.
Try this modified code:
package stackoverflow.june2012.classloader;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class TestCL {
static class MyCL extends URLClassLoader {
MyCL(URL[] urls) {
// NOTE: No parent classloader!
super(urls, null);
}
#Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
System.out.println("custom loading of: " + name);
return super.loadClass(name, resolve);
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass: " + name);
System.out.println("NOTE: Only called if this classloader does NOT have a parent");
return super.findClass(name);
}
}
public static void main(String[] args) throws Exception {
URL url = new File("./bin").toURI().toURL();
System.out.println("url to search for classes: " + url);
MyCL cl = new MyCL(new URL[] {url});
Thread.currentThread().setContextClassLoader(cl);
Class loadClass = cl.loadClass("stackoverflow.june2012.classloader.TestRun");
System.out.println("Loaded TestRun using classloader: " + loadClass.getClassLoader());
loadClass.getMethod("run", new Class[] {}).invoke(null);
}
}
And I had to move your other 2 classes into a separate file, otherwise it cant be accessed as it was package-private in a different classloader:
package stackoverflow.june2012.classloader;
public class TestRun {
public static void run() {
System.out.println("ran.");
OtherClass.runAlso();
}
}
class OtherClass {
public static void runAlso() {
System.out.println("wish this would run in my custom classloader");
}
}
One solution is to use the parent classloader (or the one that already knows where it is) to read the bytes of the class, then define it in the custom class loader.
So, something like this:
public static byte[] loadBytesForClass(ClassLoader loader, String fqn) throws IOException {
InputStream input = loader.getResourceAsStream(fqn.replace(".", "/") + ".class");
if (input == null) {
System.out.println("Could not load bytes for class: " + fqn);
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
StringUtil.copy(input, output);
return output.toByteArray();
}
Then you can call defineClass(name, bytes, 0, bytes.length) with those bytes to define it in the custom class loader.
Related
I have the following class which uses a simple customer ClassLoader to load a class and then call a static method directly on it:
public class App {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyCustClassLoader loader = new MyCustClassLoader();
Class<?> c = loader.findClass("classloading.FooObservable");
Object ob = c.newInstance();
Method md = c.getDeclaredMethod("addListener", Listener.class);
FooListener fooListener = new FooListener("app class loader");
// md.invoke(ob, fooListener); <<< works it I uncomment this.
FooObservable.addListener(fooListener);
md = c.getMethod("fooDidSomething");
md.invoke(ob);
md.invoke(ob);
md.invoke(ob);
}
}
My custom class loader looks like this:
package classloading;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyCustClassLoader extends ClassLoader {
#Override
public Class<?> findClass(String name) {
byte[] bt = loadClassData(name);
return defineClass(name, bt, 0, bt.length);
}
private byte[] loadClassData(String className) {
//read class
InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+".class");
ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
//write into byte
int len =0;
try {
while((len=is.read())!=-1){
byteSt.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
//convert into byte array
return byteSt.toByteArray();
}
}
The static method in question is defined in class FooObservable which contains a list of listeners and a notify method that can be called to notify listeners that something has happened:
package classloading;
import java.util.ArrayList;
import java.util.List;
public class FooObservable {
private static List<Listener> listeners = new ArrayList<Listener>();
int x = 0;
public static void addListener(Listener l) {
listeners.add(l);
}
public void fooDidSomething() {
x++;
for (Listener l : listeners) {
l.notify(x);
}
}
}
It doesn't seem to work, in that nothing is printed. However, when I uncomment the line md.invoke class App above and comment out the static call FooObservable.addListener, it works and I see:
foo listener (app class loader) notified: 1
foo listener (app class loader) notified: 2
foo listener (app class loader) notified: 3
Why is this happening? Is it down to the fact that when the line is uncommented, the static call addListener is invoked against FooObservable which is loaded using the Java AppClassLoader and as such is reloaded with an empty list of listeners?
This line:
FooObservable.addListener(fooListener);
is creating a different instance of FooObservable, created by the default classloader. Because it's a different classloader, it's a totally different object. Calls against the md.<> are against the FooObservable from your class loader. Those are two different instances of the same static class (that's part of the point of classloaders - you can have different instances of the same class).
If instead of calling md.invoke(ob) you called FooObservable.fooDidSomething(), you would see a successful result (from the parent classloader instance).
Without calling md.addListener (the commented out line), the version loaded by your classloader has no listeners.
I have a method in my utilities:
public void createDirectories(final Path root, final Scaffolding scaffolding) throws FileAlreadyExistsException {
if (Files.exists(root)) {
throw new FileAlreadyExistsException("Root directory " + root.toString() + " already exists.");
} else {
Files.createDirectories(root);
// Create directories from the scaffolding object
}
}
I want to mock Files so I can test to make sure Files.createDirectories with the expected fields are called or not.
Can I do this with Mockito? Or do I need to actually create the directories and check for their existence in some tmp folder?
When you write something with tdd and have troubles consider it as signal of bad design. You don't need to mock static fields or find some tricky lib for do it. Instead of doing this make entity that represent filesystem and place all methods related to file operations to this class. With this refactor your code will be like that:
class UtilClass { //util classes are bad don't do it
private final FileSystem fileSystem;
public UtilClass(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public void createDirectories(final Path root, final Scaffolding scaffolding) throws FileAlreadyExistsException {
if (fileSystem.exists(root)) {
throw new FileAlreadyExistsException("Root directory " + root.toString() + " already exists.");
} else {
fileSystem.createDirectories(root);
// Create directories from the scaffolding object
}
interface FileSystem {
boolean exists(Path path);
void createDirectories(Path path);
}
and test class
class UtilClassTest {
#Test(expected = FileAlreadyExistsException.class)
public void shouldThrowExceptionWhenRootPathExists() {
FileSystem mockFileSystem = Mockito.mock(FileSystem.class);
Mockito.when(mockFileSystem.exists(anyPath())).return(true);
UtilClass util = new UtilClass(mockFileSystem);
util.createDirectories(mock(Path.class), mock(Scaffolding.class))
}
}
In your code outside tests replace mock to implementation.
class FileSystemImpl implements FileSystem {
boolean exists(Path path){
return Files.exists(path);
}
createDirectories(Path path){
return Files.createDirectories(path);
}
}
and you don't need to touch file system in tests or mock static fields.
I'm sending you an example, which allows you to use the FileSystems Mock in a transparent way
#Test
#ExtendWith(MockfsExtension.class)
void testExecute_MockFSNew(FileSystem fileSystem) throws Exception {
Path master = Files.createDirectories(Paths.get("/fakedir/conf/java/rest/master"));
Path homologacao = Files.createDirectories(fileSystem.getPath("/fakedir/conf/java/rest/homologacao"));
Path dev = Files.createDirectories(fileSystem.getPath("/fakedir/conf/java/rest/dev"));
}
Paths.get return a mock fs, this is done using Mockito.mockStatic
It's also injecting FileSystem in paramter
using
package br.com.pulse.starter.builder.steps.impl;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder;
public class MockfsExtension implements BeforeEachCallback, AfterEachCallback , ParameterResolver {
MockedStatic<FileSystems> mock;
private FileSystem fileSystem;
public FileSystem getFileSystem() {
return this.fileSystem;
}
#Override
public void beforeEach( ExtensionContext context ) throws Exception {
this.fileSystem = MemoryFileSystemBuilder.newEmpty().build();
mock = Mockito.mockStatic(FileSystems.class);
mock.when(() -> FileSystems.getDefault()).then(invocation -> {
System.out.println("Requested FakeFs >>>");
return fileSystem;
});
}
#Override
public void afterEach( ExtensionContext context ) throws Exception {
if (this.fileSystem != null) {
this.fileSystem.close();
}
mock.close();
}
#Override
public boolean supportsParameter( ParameterContext parameterContext , ExtensionContext extensionContext ) throws ParameterResolutionException {
return true;
}
#Override
public Object resolveParameter( ParameterContext parameterContext , ExtensionContext extensionContext ) throws ParameterResolutionException {
return this.fileSystem;
}
}
See:
https://github.com/marschall/memoryfilesystem
https://github.com/marschall/memoryfilesystem/issues/130
You cannot mock static methods with base Mockito, nor, in most cases, should you be, especially one provided by Java. You can mock static methods with PowerMock, but this is a Global core library.
The piece of code you are testing here is whether or not you get an exception if the directory exists. If Files.exists() is returning bad results, your program is toast either way. So what you should ultimately be testing is that the flow is being properly followed while depending on a fully implemented Files global.
#Test
public void testCreateDirectories() {
File tempFile = new File("test");
tempFile.delete(); //delete test directory if it exists
tempFile.deleteOnExit(); //and again when the test finishes
Path testPath = tempFile.getPath();
foo.createDirectories(testPath, mock(Scaffolding.class)); //should create path
Assert.assertTrue(tempFile.exists());
try {
foo.createDirectories(testPath, mock(Scaffolding.class)); //should FAE
Assert.fail(Should have thrown an FAE exception at this point!);
}
catch(FileAlreadyExistsException faee) {
logger.debug("Expected FAE exception thrown", faee);
}
}
This execution will go through both the true and false path and clean up after itself.
This double execution of the logic tests both paths and is designed to cover the following scenarios if your if statement manages to get mucked up.
boolean works as intended: both calls work, PASS
boolean is accidentally inverted: 1st call throws FAE, FAIL
boolean always returns false: 2nd call doesn't throw FAE, FAIL.
boolean always returns true: 1st call throws FAE, FAIL.
I am attempting to change some third party class definitions, before each test, to simulate different results. I have to use something like javassist because extending the classes, sometimes, is just not possible due to the access modifiers. Here is an example of what I am attempting to do with javassist and junit combined:
public class SimulatedSession extends SomeThirdParty {
private boolean isJoe = false;
public SimulatedSession(final boolean isJoe) {
this.isJoe = isJoe;
}
#Override
public void performThis() {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("some.package.Class");
final CtMethod callMethod = internalClass.getDeclaredMethod("doThis");
if (isJoe) {
callMethod.setBody("{System.out.println(\"Joe\");}");
} else {
callMethod.setBody("{System.out.println(\"mik\");}");
}
internalClass.toClass();
}
}
#Test
public void firstTest() {
SimulatedSession toUse = new SimulatedSession(false);
// do something with this object and this flow
}
#Test
public void nextTest() {
SimulatedSession toUse = new SimulatedSession(true);
// do something with this object and this flow
}
if I run each test individually, I can run the code just fine. When I run them using the unit suite, one test after the other, I get a "frozen class issue". To get around this, I am looking at this post, however, I must admit I am unsure as to how one can use a different class pool to solve the issue.
Your current code will try to load twice the same class into the same ClassLoader which is forbidden, you can only load once a class for a given ClassLoader.
To make your unit tests pass, I had to:
Create my own temporary ClassLoader that will be able to load some.package.Class (that I replaced by javassist.MyClass for testing purpose) and that will be implemented in such way that it will first try to load the class from it before the parent's CL.
Set my own ClassLoader as context ClassLoader.
Change the code of SimulatedSession#performThis() to be able to get the class instance created by this method and to call internalClass.defrost() to prevent the "frozen class issue".
Invoke by reflection the method doThis() to make sure that I have different output by using the class instance returned by SimulatedSession#performThis() to make sure that the class used has been loaded with my ClassLoader.
So assuming that my class javassist.MyClass is:
package javassist;
public class MyClass {
public void doThis() {
}
}
The method SimulatedSession#performThis() with the modifications:
public Class<?> performThis() throws Exception {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("javassist.MyClass");
// Prevent the "frozen class issue"
internalClass.defrost();
...
return internalClass.toClass();
}
The unit tests:
// The custom CL
private URLClassLoader cl;
// The previous context CL
private ClassLoader old;
#Before
public void init() throws Exception {
// Provide the URL corresponding to the folder that contains the class
// `javassist.MyClass`
this.cl = new URLClassLoader(new URL[]{new File("target/classes").toURI().toURL()}){
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
// Try to find the class for this CL
return findClass(name);
} catch( ClassNotFoundException e ) {
// Could not find the class so load it from the parent
return super.loadClass(name, resolve);
}
}
};
// Get the current context CL and store it into old
this.old = Thread.currentThread().getContextClassLoader();
// Set the custom CL as new context CL
Thread.currentThread().setContextClassLoader(cl);
}
#After
public void restore() throws Exception {
// Restore the context CL
Thread.currentThread().setContextClassLoader(old);
// Close the custom CL
cl.close();
}
#Test
public void firstTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(false);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
#Test
public void nextTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(true);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
Output:
mik
Joe
Take a look at retransformer. It's a Javassist based lib I wrote for running tests just like this. It's a bit more terse than using raw Javassist.
Maybe another approach. We had a similar problem as we once mocked a dependency - we could not reset it. So we did the following: Before each test we replace the 'live' instance with our mock. After the tests, we restore the live instance. So I propose that you replace the modified instance of your third party code for each test.
#Before
public void setup()
{
this.liveBeanImpl = (LiveBean) ReflectionTools.getFieldValue(this.beanToTest, "liveBean");
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", new TestStub());
}
#After
public void cleanup()
{
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", his.liveBeanImpl);
}
The setFieldValue looks like this:
public static void setFieldValue(Object instanceToModify, String fieldName, Object valueToSet)
{
try
{
Field declaredFieldToSet = instanceToModify.getClass().getDeclaredField(fieldName);
declaredFieldToSet.setAccessible(true);
declaredFieldToSet.set(instanceToModify, valueToSet);
declaredFieldToSet.setAccessible(false);
}
catch (Exception exception)
{
String className = exception.getClass().getCanonicalName();
String message = exception.getMessage();
String errorFormat = "\n\t'%s' caught when setting value of field '%s': %s";
String error = String.format(errorFormat, className, fieldName, message);
Assert.fail(error);
}
}
So maybe your tests will pass if you reset your implementation for each test. Do you get the idea?
public class MyClassLoader extends URLClassLoader {
public MyClassLoader() throws Exception{
super(new URL[]{new URL("file:///home/ubuntu/java/hello.jar")});
}
#Override
public Class loadClass(String name) throws ClassNotFoundException {
if (name.equals("hello.example.org.Foo") || name.equals("hello.example.org.Bar"))
{
System.out.println("Foo or Bar is loaded");
}
return super.loadClass(name);
}
}
I just want to execute a block of code prior to loading certain classes and the above code doesn't quite work and I am not sure where the error is?
once I have all my classes in jar file I would run it as follows
java -Djava.system.class.loader=MyClassLoader -jar hello.jar
If you set the system class loader via -Djava.system.class.loader=MyClassLoader you need to provide a constructor with a classloader parameter:
public MyClassLoader(ClassLoader parent) throws Exception{
super(new URL[]{new URL("file:///home/ubuntu/java/hello.jar")}, parent);
}
You should have seen a java.lang.NoSuchMethodException if you run without such a constructor.
You should override findClass(String) instead of loadClass.
i want to instantiate two TCP server applications within the same main method. Those server classes use lots of static and thread local fields. Is there a chance to load classes like in a different application domain?
this is my test case:
Tester class has simple getter and setter methods for setting global static object.
public class Tester {
public Tester() {
System.out.println(getClass().getClassLoader());
}
public void setText(String text) {
GlobalObject.globalText = text;
}
public String getText() {
return GlobalObject.globalText;
}
}
This is global object that is accessible from every where. I want to limit access to this object.
public class GlobalObject {
public static String globalText;
}
This is my test program.
public class Main {
public static void main(String[] args) {
// Default class loader;
Tester ta1 = new Tester();
ta1.setText("test");
System.out.println(ta1.getText());
Tester ta2 = new Tester();
System.out.println(ta2.getText());
// Custom class loader;
CustomClassLoader ccl = new CustomClassLoader();
try {
Tester tb = (Tester) ccl.loadClass("Tester").newInstance();
System.out.println(tb.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
}
The ouput is:
sun.misc.Launcher$AppClassLoader#11b86e7
test
sun.misc.Launcher$AppClassLoader#11b86e7
test
sun.misc.Launcher$AppClassLoader#11b86e7
test
The output that i want:
sun.misc.Launcher$AppClassLoader#11b86e7
test
sun.misc.Launcher$AppClassLoader#11b86e7
test
sun.misc.Launcher$AppClassLoader#1234567
null
You don't tell us what CustomClassLoader is.
But in general, the default behaviour of class loaders is to delegate to their parent, so by default all class loaders eventually delegate to the actual system class loader.
Try creating a class loader without a parent. This is how it would look like with a standard classloader:
URL[] urls = new URL[] {new File("build/classes/").toURL()};
ClassLoader loader = new URLClassLoader(urls, null);
The second constructor parameter is the parent.