I am working on a Maven project, and I am trying to perform a TestNG unit test with the following structure:
import org.testng.*;
#PrepareForTest(GeneralDAO.class)
#PowerMockIgnore({"javax.management.*"})
public class TestClass extends PowerMockTestCase {
#Test(expectedExceptions = BusinessException.class) // custom exception from another project
public void testFailToGetBusiness() {
// method that shoud throw a BusinessException
}
}
But when I am running the test, I find out the following exception:
java.lang.LinkageError: loader constraint violation: when resolving method "com.company.exception.BusinessException.<init>(Ljava/lang/Exception;Lcom/company/exception/EExceptionCodes;)V"
the class loader (instance of org/powermock/core/classloader/MockClassLoader)
of the current class, com/company/core/BusinessHandler,
and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, com/company/exception/BusinessException,
have different Class objects for the type com/company/exception/EExceptionCodes used in the signature
As far as I understand, powermock loads the Object with MockClassLoader for the testing environment, and Sun uses his AppClassLoader to load the same Object. As the classloaders are different, in runtime the objects also are (beyond they have the same name), and that implies a LinkageError.
Anyway, I have tried several ways to avoid that, for example by removing the expectedExceptions tag and adding a try and catch clause, but with no success.
How could I set an unique class loader for that Exception? Is that the correct approach to solve it? Or should I try with something else? Could be something wrong with my Maven configuration? Any ideas or comments are welcome :)
Related
I have a main method that creates custom classloader and instantiates a class, called Test, with it.
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mycompany.app2.Test2"); // We ensure that Test2 is not part of current classpath
System.err.println("Should have thrown ClassNotFound");
System.exit(1);
} catch (ClassNotFoundException e) {
// ignore
}
String jar = "C:\\experiments\\classloader-test2\\target\\classloader-test2-1.0-SNAPSHOT.jar"; // Contains Test2
URL[] classPaths = new URL[] { new File(jar).toURI().toURL() };
ClassLoader classLoader = new URLClassLoader(classPaths, App.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("com.mycompany.app2.Test2", true, classLoader); // Check that custom class loader can find the wanted class
Test test = (Test) Class.forName("com.mycompany.app.Test", true, classLoader).getDeclaredConstructor().newInstance();
test.ex(); // This throws ClassNotFound for Test2
}
}
This class then itself instantiates another class that is not part of the original classpath, but is part of the custom one.
public class Test {
public void ex() {
new Test2().test();
}
}
In my understanding of classloader, since Test was created with the custom classloader any class loadings within should be done with the same loader. But this does not seem to be the case.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/app2/Test2
at com.mycompany.app.Test.ex(Test.java:7)
at com.mycompany.app.App.main(App.java:28)
What do I need to do in the main method to make Test#ex work, without changing Test?
I'm using Java 17.
You create the URLClassLoader using App.class.getClassLoader() as the parent class loader. Hence, the request to load Test through the custom class loader is resolved through the parent loader, ending up at exactly the same class you’d get with Test.class in your main method.
You could pass a different parent loader, e.g. null to denote the bootstrap loader, to forbid resolving the Test class though the parent loader but this would result in either of two unhelpful scenarios
If the custom class loader has no com.mycompany.app.Test class on its own, the loading attempt would simply fail.
If the custom class loader has a com.mycompany.app.Test class, i.e. inside classloader-test2-1.0-SNAPSHOT.jar, it would be a different class than the Test class referenced in your main method, loaded by the application class loader. In this case, the type cast (Test) would fail.
In other words, the Test class referenced by you main method can not be affected by another, unrelated class loader at all.
There is an entirely different approach which may work in some scenarios. Do not create a new class loader, when all you want to do, is to inject a new class.
byte[] code;
try(var is = new URL("jar:file:C:\\experiments\\classloader-test2\\target\\" +
"classloader-test2-1.0-SNAPSHOT.jar!/com/mycompany/app2/Test2.class").openStream())
{
code = is.readAllBytes();
}
MethodHandles.lookup().defineClass(code);
Test test = new Test();
test.ex();
This adds the class to the current class loading context, so subsequent linking will succeed. With the following catches:
No previous attempt to link this class must have been made so far
It only works for a classes without dependencies to other absent classes (as there’s no class loader resolving those to the jar file outside the class path).
In some cases, when the dependencies are non-circular or resolved lazily, you could add all the classes with multiple define calls, if you know which you need and in which order.
The class must be in the same package, otherwise, you’d have to move the lookup context to the right package, with the documented restrictions
An entirely different approach to add the classes to the existing environment, would be via Java Agents, as they can add jar files to the class path.
I'm currently working on a maintenance for an undocumented code and have stumbled upon a weird error when running a class in Spring Boot. The code raises an IllegalAccessError on execution, but compiles properly.
It seems that the way the original team tried to access the Kafka Streams' private methods was the "MacGyver way", but I'm unsure about its behaviour since IntelliJ imports the code properly and handles the inheritance as it should (if I declare the class as a local package it highlights saying that the AbstractStream and KTableImpl classes do not exist).
The project is managed with Gradle and it is a micro-service. The execution is made through a Spring Boot and the error raises on startup.
One possible solution was to use Java's Reflection library, but it does not seem the correct approach to solve this error. Maybe a boot setting is wrong?
package org.apache.kafka.streams.kstream.internals;
import org.apache.kafka.streams.kstream.KTable;
public class KTableSpy {
public static <K> String getName(AbstractStream<K> stream) {
return stream.name;
}
public static <K, V> void enableSendingOldValues(KTable<K, V> table) {
((KTableImpl<K, ?, V>) table).enableSendingOldValues();
}
}
This class should've worked properly and do not interfere on the service startup. Instead, we have the following error
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AVCompanyStateProjector': Invocation of init method failed; nested exception is java.lang.IllegalAccessError: tried to access method org.apache.kafka.streams.kstream.internals.KTableImpl.enableSendingOldValues()V from class org.apache.kafka.streams.kstream.internals.KTableSpy
Not sure what you try to accomplish, but AbstractStream and KTableImpl are from package org.apache.kafka.streams.kstream.internals and not part of public API (as the name indicates). This implies that methods (or even the whole class) can be added/removed without notice.
Maybe the visibility from enableSendingOldValues() was changes from public to package-private breaking your code.
I have a unit test that attempts to create a SQLException to simulate a database error. In SQLException's constructor, there is a call to DriverManager, which has a static initialization block. I figured that I could suppress the static block with this type of setup:
#RunWith(PowerMockRunner.class)
#SuppressStaticInitializationFor({"java.sql.DriverManager"})
public class StaticTest
{
#Test
public void testStaticSuppress() throws Exception
{
SQLException ex = new SQLException();
expect(...).andThrow(ex);
}
}
When I run the test, the static block in DriverManager is still called. What am I doing wrong?
Clarifications
I am running Powermock 1.5 - I was under the impression that using v1.5 allows me to mock system classes
When DriverManager runs it's static initialization block, I get this exception:
Oct 15, 2013 1:06:24 PM oracle.jdbc.driver.OracleDriver registerMBeans
WARNING: Error while registering Oracle JDBC Diagnosability MBean.
java.lang.LinkageError: loader constraint violation: when resolving method "java.lang.management.ManagementFactory.getPlatformMBeanServer()Ljavax/management/MBeanServer;" the class loader (instance of org/powermock/core/classloader/MockClassLoader) of the current class, oracle/jdbc/driver/OracleDriver, and the class loader (instance of ) for resolved class, java/lang/management/ManagementFactory, have different Class objects for the type javax/management/MBeanServer; used in the signature
I realize that I could make a mock of SQLException and never instantiate it directly. I would rather not go that route since it would mean updating 91 different unit tests. I asked the question because it looked like my code should work just fine according to the PowerMock docs.
I suspect (but I am not certain) that Powermock is unable to prevent the static initializer from running for classes that are loaded by the system or bootstrap classloader (like the jre classes including those of the package java.sql are).
After posting to the Powermock Google Group, I got this response:
You can mock, suppress methods, stub methods etc in these classes since powermock 1.2.5 but you cannot suppress static initializers.
See this page: Google Groups PowerMock Group
You need to add to the class: #PowerMockIgnore("javax.management.*")
i'm writing a custom class loader to load some of my classes (not all).
Class loader are very simple:
public Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class cls=findLoadedClass(className);
if(cls!=null) {
return cls;
}
// Search first for encrypted classes
cls=decryptClass(className);
if(cls==null) {
// Then try with default system classloader
cls=super.loadClass(className, resolve);
}
return cls;
}
And this is how i use it:
// In my Launcher class
public static void main(String[] args) {
MyClassLoader loader=new MyClassLoader();
try {
final Class main=loader.loadClass("com.MyAppMain");
Method toInvoke=main.getMethod("main", args.getClass());
toInvoke.invoke(main, new Object[]{args});
}
catch(Exception ex) {
}
}
All seem to be fine in my small test project, but when i use this loader in my big project(client-server application that use spring+hibernate and IoC) doesn't work.
I have not a particolar exception in my classloader, but for example, new Socket instance throw a "java.net.ConnectException: Connection refused" without a real reason...
Other problems is my main form does not become visible... and other strange problems like this.
So, the question, are these problems caused by my classloader that load in different way a different kind of classes?
Edit 1
My project use spring, so i use #Autowired or sometimes
springApplicationContext.getBean(clazz);
to inject a bean.
The problem is spring cannot find my beans if these classes are encrypted(so they need to be loaded by my classloader).
There is a workaround for this mistake?
Thanks.
Edit 2
I have set my classloader in spring ClassPathXmlApplicationContext and now i notice that spring uses my classloader to load beans class, but despite this it throws an org.springframework.beans.factory.NoSuchBeanDefinitionException becouse it cannot find beans... what can i do?
Thanks
I'm not very good at class loaders, but from you code it's only possible to assume, that in case your class loader can't find a class, it will redirect to system class loader. It may work fine when you run application standalone, like in your sample, but if it's a web application that is run on application server it will fail.
Application servers usually create a big hierarchy of class loaders and has a separate class loader used to load your application classes. In this case, system class loader knows nothing about your Spring related classes and thus can't load them.
You must keep in mind, that same class may be loaded by several class loaders and if you try to compare same class from different class loaders it will fail.
In your case I would set parent class loader in MyClassLoader constructor. As a parent class loader you can use MyClassLoader.class.getClassLoader() I think.
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
super(MyClassLoader.class.getClassLoader());
}
// other code
}
Hope this may help :)
I see two things that may be worth investigating:
The code makes the class loader parent-last. There is always a risk that this causes subtle class loading issues.
Maybe it is just the thread context class loader that is not set properly.
I have an issue where NoClasDefFoundError is being thrown. It puzzles me since I am using interfaces, and no class definition should be available. I have read through some posts which point to Classpath, but I don't believe that to be the issue here (although I may be wrong). I am using NetBeans 6.9.1 IDE.
I have created a sample setup to reproduce the issue. Four projects: Interfaces, Objects, Locator and Consumer. Below you will find the implementations.
At runtime consumer coplains about missing SomeObject implementation, which it should not be aware of since it is accepting interface.
Exception in thread "main"
java.lang.NoClassDefFoundError:
objects/SomeObject
What am I missing?
package interfaces;
public interface ISomeInterface { }
package objects;
import interfaces.ISomeInterface;
public class SomeObject implements ISomeInterface{ }
package locator;
import interfaces.ISomeInterface;
import objects.SomeObject;
public class Locator { public static ISomeInterface LocateImplementation() { return new SomeObject(); }}
package consumer;
import interfaces.ISomeInterface;
import locator.Locator;
public class Main { public static void main(String[] args) { ISomeInterface object = Locator.LocateImplementation(); }}
You can get a NoClassDefFoundError exception with interfaces just as you can with classes. Consider the "Class" in the name of the exception to be the .class file that is generated from compiling a class or interface, not a Java class.
This is saying that the class/interface objects.SomeObject isn't visible on your classpath. Check the location of that .class file and ensure that it's on your classpath - if you're positive it's there, give us some screen shots or something that might help to debug the problem.
Think of NoClassDefFoundError as a runtime linkage problem. JRE loaded one class (or an interface) and it references another class (or an interface), but that referenced class isn't found.
The only way this can happen if you have packaging/classpath issues such that your runtime environment doesn't reflect how things are at build time.
If you are launching this from IDE, make sure that you aren't ignoring any errors and launching anyway. Some classes will not be generated that way.
Usually I run into these problems not when a class is missing, but when there is an error in the static initializers.
Try running your code in a debugger, and set the exception breakpoint to break when any exception is thrown, whether caught or not. I bet you have an uncaught exception in the static initializer for some reason.
In the locateImplementation() method you are returning "new SomeObject()",
JVM needs to have its definition when called. I think it is missing.
You should check if your SomeObject class is in class path because -
Well the JVM will be running the below code -
ISomeInterface object = Locator.LocateImplementation();
and when it does that it will call Locator.LocateImplementation(). This code internally tries to instantiate your SomeObject class which it does not find in the classpath.
So your below understanding
It puzzles me since I am using
interfaces, and no class definition
should be available.
Is not really valid.
Any Interface must be declared inside class
public class Calbacks {
public interface IBaseFragmentInterface {
void NotifyMainActivity();
}
}