How to associate package-info with class in Byte-buddy? - java

I create package-info of package foo.bar and class foo.bar.BarCl in next code
public static void main(String[] args) throws ClassNotFoundException, IOException {
DynamicType.Unloaded<?> make = new ByteBuddy().makePackage("foo.bar").make();
DynamicType.Loaded<Object> load = new ByteBuddy()
.subclass(Object.class)
.name("foo.bar.BarCl")
.make()
.include(make)
.load(Main2.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
load.saveIn(new File("folder"));
Class<?> loaded = load.getLoaded();
System.out.println(loaded.getPackage());
}
Class and package info correctly writing in folder:
package foo.bar;
interface $$package-info /* Real name is 'package-info' */ {
}
package foo.bar;
public class BarCl {
public BarCl() {
}
}
But in runtime after injecting this classes i get loaded.getPackage()==null
How i can associate package-info with generated class?
P.S. In real task i need to generate package-info with JAXB annotation #XmlSchema, that specify xml namespace. Without it, classes have naming collisions

Packages are class loader respobsibility and not defined by a package-info class. You can define them using the loader DSL.

Related

ByteBuddy - Read class annotations in a java agent

I am trying to access annotations of a class before applying some transformation in a java agent implemented with ByteBuddy.
To access the annotations, I am trying to load the Class object, but it seems that this creates a duplicate class definition.
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class SimpleTestAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.isAnnotatedWith(SomeAnnotationType.class))
.transform((builder, type, classLoader, javaModule) -> {
try {
Class loadedClass = Class.forName(type.getName(), true, classLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return builder;
})
.installOn(inst);
}
}
A simple class that just creates an instance of the class TestClass that is annotated with the expected annotation, throws the following exception:
Exception in thread "main" java.lang.LinkageError: loader (instance of
sun/misc/Launcher$AppClassLoader): attempted duplicate class
definition for name: "TestClass"
public class AgentTest {
public static void main (String[] args) {
TestClass pet = new TestClass();
}
}
I am trying to implement the agent like in the example described in: Easily-Create-Java-Agents-with-ByteBuddy
Is there a way to load the Class object without causing this issue or a way to access the annotations using the arguments passed to the transformation() method?
The transformation should be able to implement new interfaces and not only override methods this is why I think I cannot use "ForAdvice".
UPDATE
The following loop finds the TestClass only after the Class.forName() is executed. This means that the class has not been loaded and so probably there is no hope to use Class.forName to get the annotations.
for (Class<?> t : inst.getAllLoadedClasses()) {
System.out.println("Class name: " + t.getName());
}
It is possible to get the annotations and the full information about a class using the net.bytebuddy.description.type.TypeDescription instance passed to the transform() method.
The problem is that, for example, I need the Method objects that can be called using reflection. It would be easier if I could somehow access the Class object for the class that is being transformed.
Byte Buddy exposes all information on annotations of a class via the TypeDescription API already, you should not load classes as a class that is loaded during a transformation loads this class before the transformation is applied and aborts class loading with the error you observe. Instead, implement your own matcher:
.type(type -> check(type.getDeclaredAnnotations().ofType(SomeAnnotation.class).load())
Byte Buddy will represent the annotation for you without loading the carrier class itself but rather represent it using its own class file processor.
You should make sure the annotation class is loaded before installing your agent builder to avoid a circularity for this exact annotation.

JAXB Serialization Failure with Generic Class

I am trying to write a class that can serailize and deserailize settings to XML using Java. I have this code successfully written in C# and it is very useful so I would like something similar in my java app.
I have the following base class that every class I want t serialize to XML must implement.
package serializers;
import java.lang.reflect.ParameterizedType;
abstract class XmlSerializableObject<T> {
abstract T getDefault();
abstract String getSerializedFilePath();
String getGenericName() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();
}
ClassLoader getClassLoader() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getClassLoader();
}
}
where the getGenericName and getClassLoader are for use with instantiating the JAXBContext. I then have a basic implementation of this as a settings provider
public class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
private Settings settings;
#Override
public SettingsProvider getDefault() {
return null;
}
#Override
public String getSerializedFilePath() {
return "C:\\Data\\__tmp.settings";
}
public Settings getSettings() {
return settings;
};
public void setSettings(Settings settings) {
this.settings = settings;
}
}
class Settings {
private String tmp;
public String getTmp() {
return tmp;
}
public void setTmp(String tmp) {
this.tmp = tmp;
}
}
Now I have the following serializer class
package serializers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class XmlSerializer {
private static final Logger logger = LoggerFactory.getLogger(XmlSerializer.class);
public static <T extends XmlSerializableObject> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
String name = o.getGenericName();
ClassLoader classLoader = o.getClassLoader();
// THE FOLLOWING LINE throws.
JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); // also tried JAXBContext.newInstance(name);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
// Deserialize below.
}
I then have the following test to check the results of serialization
package serializers;
import org.junit.Before;
import org.junit.Test;
public class XmlSerializerTest {
private Settings settings = new Settings();
private SettingsProvider provider;
#Before
public void setUp() throws Exception {
settings.setTmp("testing");
provider = new SettingsProvider();
provider.setSettings(settings);
}
#Test
public void serialize() throws Exception {
XmlSerializer.Serialize(provider);
}
}
The problem is the call to JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); which throws
javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index
- with linked exception:
[javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index]
I have tried with and without the ClassLoader object to no avail. How can I serialize a generic type in this way?
Thanks for your time.
Let us look at the line of code that is throwing the exception:
JAXBContext jaxbContext = JAXBContext.newInstance(name);
In the above line of code, the argument name that you are passing is the name of the class that is to be deserialized and is determined at runtime (viz., serializers.SettingsProvider in the given sample). This may not be sufficient for JAXB to determine all the classes that constitutes the JAXB context. So instead, try passing the name of the package(s) that contain all the classes that this instance of JAXBContext should be deserializing -- all the classes in that package(s) is your JAXB context. This is something that will be known at compile time. So, try the following line of code instead:
JAXBContext jaxbContext = JAXBContext.newInstance("serializers");
Here, "serializers" is the name of the package that contains all the classes that you want to be deserializing, i.e., the JAXB context for the given sample.
You may like to refer the Oracle JAXB tutorial and note the following lines of code:
import primer.po.*;
...
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
Please refer this Javadoc and note that in case the classes to be deserialized are spread over multiple packages, then a list of colon separated package names should be passed, e.g.,--
JAXBContext.newInstance( "com.acme.foo:com.acme.bar" )
In case you must pass class names instead of package names, then first read this Javadoc very carefully. Note that the JAXBContext instance will be initialized only with classes passed as parameter and the classes that are statically reachable from these classes. Prefer to write your program in such a way that class names being passed are known at compile time.
Also, it may be helpful for you to note that generics in Java are different (especially w.r.t type erasure) than those in C# -- please see What is the concept of erasure in generics in Java?.
Also, given the class declaration:
class XmlSerializableObject<T> {
}
which states that the class XmlSerializableObject deals with type T, the following class declaration:
class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
}
which states that the class SettingsProvider deals with its own type sounds convoluted.
Or did you instead mean it to declare like as follows:
class SettingsProvider extends XmlSerializableObject<Settings> {
}
which states that the class SettingsProvider deals with type Settings?
That looks like it should be JAXBContext.newInstance(SettingsProvider.class) .
The JAXBContext.newInstance(String ...) versions of the method are expecting a package name, which as the error message says should then contain an ObjectFactory class, or jaxb.index list to guide it to the classes.
You are using this newInstance method :
Parameters:
contextPath - list of java package names that contain schema derived class and/or java to schema (JAXB-annotated) mapped classes
classLoader - This class loader will be used to locate the implementation classes.
So df778899 is right, you should not use this signature as getGenericName returns a fully qualified class name and not a package. And even if it was a package, you will still miss ObjectFactory.class or jaxb.index
But JAXBContext.newInstance(SettingsProvider.class) won't work either. You will get a MarshalException indicating that #XmlRootElement is missing
Annotate SettingsProvider like this :
#XmlRootElement(name = "root")
static class SettingsProvider extends XmlSerializableObject<SettingsProvider>
{
private Settings settings;
// [...]
And finally you will get :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<settings>
<tmp>testing</tmp>
</settings>
</root>
This was done by using the following interfaces
public interface IXmlSerializableObject {
String getSerializedFilePath();
}
The crucial one being
public interface IPersistanceProvider<T> extends IXmlSerializableObject {
void save();
void restoreDefaults();
Class<T> getTypeParameterClass();
}
The crucial property is Class<T> getTypeParameterClass(). This is then used in
public static <T extends PersistanceProviderBase> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
JAXBContext jaxbContext = JAXBContext.newInstance(o.getTypeParameterClass());
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
where PersistanceProviderBase implements the IPersistanceProvider interface.

How to use Custom ClassLoader to new Object in Java

I want to create a custom ClassLoader to load all jar files in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object, it will search class in all jar files in that path, then search the class path defined by parameter (-cp).
Is it possible?
for Example, there is a jar file in /home/custom/lib/a.jar
in Main Class
public class Main {
public static void main(String[] args) {
// do something here to use custom ClassLoader
// here will search Car in /home/custom/lib/a.jar first then in java class path
Car car = new Car();
}
}
A class loader cannot do exactly what you seem to expect.
Quoting another answer of a relevant Q&A:
Java will always use the classloader that loaded the code that is executing.
So with your example:
public static void main(String[] args) {
// whatever you do here...
Car car = new Car(); // ← this code is already bound to system class loader
}
The closest you can get would be to use a child-first (parent-last) class loader such as this one, instanciate it with your jar, then use reflection to create an instance of Car from that jar.
Car class within a.jar:
package com.acme;
public class Car {
public String honk() {
return "Honk honk!";
}
}
Your main application:
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Object someCar = carClass.newInstance();
Object result = carClass.getMethod("honk").invoke(someCar);
System.out.println(result); // Honk honk!
}
To note: if you also have a com.acme.Car class in your class path, that's not the same class, because a class is identified by its full name and class loader.
To illustrate this, imagine I'd used my local Car class as below with the carClass loaded as above by my custom class loader:
Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car
Might be confusing, but this is because the name alone does not identify the class. That cast is invalid because the 2 classes are different. They might have different members, or they might have same members but different implementations, or they might be byte-for-byte identical: they are not the same class.
Now, that's not a very useful thing to have.
Such things become useful when the custom classes in your jar implement a common API, that the main program knows how to use.
For example, let's say interface Vehicle (which has method String honk()) is in common class path, and your Car is in a.jar and implements Vehicle.
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Vehicle someCar = (Vehicle) carClass.newInstance(); // Now more useful
String result = someCar.honk(); // can use methods as normal
System.out.println(result); // Honk honk!
That's similar to what servlet containers do:
your application implements the servlet API (e.g. a class that implements javax.servlet.Servlet)
it is packaged into a war file, that the servlet container can load with a custom class loader
the deployment descriptor (web.xml file) tells the servlet container the names of the servlets (classes) that it needs to instanciate (as we did above)
those classes being Servlets, the servlet container can use them as such
In your case, you do not need to write a new ClassLoader as the only thing you wanna do is extend your classpath at runtime.
For that you get your current SystemClassLoader instance and you add the classpath entry to it using URLClassLoader.
working example with JDK 8:
Car class compiled and located in C:\Users\xxxx\Documents\sources\test\target\classes
public class Car {
public String prout() {
return "Test test!";
}
}
Main class
public static void main(String args[]) throws Exception {
addPath("C:\\Users\\xxxx\\Documents\\sources\\test\\target\\classes");
Class clazz = ClassLoader.getSystemClassLoader().loadClass("Car");
Object car = clazz.newInstance();
System.out.println(clazz.getMethod("prout").invoke(car));
}
public static void addPath(String s) throws Exception {
File f=new File(s);
URL u=f.toURI().toURL();
URLClassLoader urlClassLoader=(URLClassLoader)ClassLoader.getSystemClassLoader();
Class urlClass=URLClassLoader.class;
Method method=urlClass.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader,new Object[]{u});
}
note that we need to use reflection because method addURL(URL u) is protected
also note that since we add the classpath entry to the SystemClassloader, you do not need to add the classpath entry everytime you need it, only once is enough and then use ClassLoader.getSystemClassLoader().loadClass(String name) to load the class from previously added classpath entry.
If you do not need that classpath entry for later use, you can instantiate your own URLClassLoader instance and load the classes accordingly, instead of setting the classpath entry on the SystemClassLoader.
i.e:
public static void main(String[] args) {
try {
File file = new File("c:\\other_classes\\");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{ url };
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
//load the Address class in 'c:\\other_classes\\'
Class cls = cl.loadClass("com.mkyong.io.Address");
} catch (Exception ex) {
ex.printStackTrace();
}
}
source:
https://www.mkyong.com/java/how-to-load-classes-which-are-not-in-your-classpath/
Question: I want to create a custom ClassLoader to load all jar files
in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object,
it will search class in all jar files in that path, then search the
class path defined by parameter (-cp).
Is it possible?
If you want to be able to use new keyword, you need to amend the classpath of the compiler javac -classpath path
otherwise at compile-time it will not know from where to load the class.
The compiler is loading classes for type checking.
(more infos here: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching)
It is not possible to use new keyword for classes loaded by a custom ClassLoader at runtime due to the compiler internal implementation of new keyword.
The compiler and JVM (runtime) have their own ClassLoaders, you cannot customize the javac classloader, the only part that can be customized from the compiler is the annotation processing as far as I know.

Is it possible to register all classes within a package as Spring beans

I'm familiar with Springs Java based configuration options, including the usage of #Component and #Configuration in conjunction with #Bean annotations to register Spring beans.
However, when converting a decent size project to Spring, it can be very labor intensive to systematically touch all classes in the project and update with #Configuration #Beans or annotating each class with #Component. We have a large Groovy project to be converted and I would like to simplify the process.
My question: Is there a facility provided in Spring that allows you to tell Spring to auto-configure all valid bean candidate classes within a specific package?
If not, what other options are available?
ClassPathBeanDefinitionScanner is all you need.
public class Main {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
scanner.scan("net.company.name");
context.refresh();
A a = context.getBean(A.class);
System.out.println(a.toString());
}
}
You can pass custom logic in include filter if you want. In current version every class in the provided package will be included as a bean.
But it is impossible to build a right dependency structure on your classes automagically, it really depends on the scope you want. You need to do it by your hands.
I'd do pretty much the same thing that Roman did, only I'd do it at build time, not at runtime, using code generation. The rationale here is that I strongly prefer magic to happen at build time to magic that happens at deploy time.
In the simplest version, write a main method that scans the package (instead of reflections api, I'm using Guava's ClassPath scanner) and creates a #Bean method for every class it finds.
For the Code generation, I'd use JCodeModel:
public class PackageBeanGenerator {
public static void main(String[] args) throws Exception {
String packageName = args[0];
JCodeModel codeModel = new JCodeModel();
// create class definition
JDefinedClass springConfig = codeModel._package(packageName)._class("SpringConfig");
springConfig.annotate(Configuration.class);
for (ClassPath.ClassInfo classInfo : ClassPath.from(
PackageBeanGenerator.class.getClassLoader()
).getTopLevelClasses(packageName)) {
Class<?> type = classInfo.load();
String beanName = CaseFormat.UPPER_CAMEL.to(
CaseFormat.LOWER_CAMEL,
type.getSimpleName());
JMethod beanMethod = springConfig.method(JMod.PUBLIC, type, beanName);
beanMethod.annotate(Bean.class);
beanMethod.body()._return(JExpr._new(codeModel._ref(type)));
}
// write class to file
codeModel.build(new File("/path/to/output/folder"));
}
}
You can try to use your own BeanDefinitionRegistryPostProcessor
#Component
public class CustomBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Reflections reflections = new Reflections("my.package.prefix", new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class);
for (Class clazz : allClasses) {
GenericBeanDefinition gbd = new GenericBeanDefinition();
gbd.setBeanClass(clazz);
gbd.setAttribute("attributeName", "attributeValue");
registry.registerBeanDefinition(clazz.getSimpleName() + "_Bean", gbd);
}
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// Custom post process the existing bean definitions
}
}
See sample project at https://github.com/sandarkin/so-q37548350
At the risk of sounding primitive, why not just do a simple find and replace in your IDE (e.g. search for "public class" in a package and replace with "#Component public class") ? That should be much quicker than trying to do anything programatically.

Java ClassLoader: load same class twice

I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.
But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the class.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
What am I missing? like a newInstance or something?
A classloader can't replace a class that already has been loaded. loadClass will return the reference of the existing Class instance.
You'll have to instantiate a new classloader and use it to load the new class. And then, if you want to "replace" the class, you'll have to throw this classloader away and create another new one.
In response to your comment(s): do something like
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
This classloader will use the "default delegation parent ClassLoader" and you have to take care, the class (identified by it fully qualified classname) has not been loaded and can't be loaded by that parent classloader. So the "pathToClassAsUrl" shouldn't be on the classpath!
You have to load a new ClassLoader each time, or you have to give the class a different name each time and access it via an interface.
e.g.
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
As it was stated before,
Each class loader remembers (caches) the classes that is has loaded before and won't reload it again - essentially each class loader defines a namespace.
Child class loader delegates class loading to the parent class loader, i.e.
Java 8 and before
Custom Class Loader(s) -> App Class Loader -> Extension Class Loader -> Bootstrap Class Loader
Java 9+
Custom Class Loader(s) -> App Class Loader -> Platform Class Loader -> Bootstrap Class Loader.
From the above we can conclude that each Class object is identified by its fully qualified class name and the loader than defined it (also known as defined loader)
From Javadocs :
Every Class object contains a reference to the ClassLoader that
defined it.
The method defineClass converts an array of bytes into an instance of
class Class. Instances of this newly defined class can be created
using Class.newInstance.
The simple solution to reload class is to either define new (for example UrlClassLoader) or your own custom class loader.
For more complex scenario where you need to substitute class dynamic proxy mechanism can be utilized.
Please see below simple solution I used for a similar problem to reload same class by defining custom class loader.
The essence - override findClass method of the parent class loader and then load the class from bytes read from the filesystem.
MyClassLoader - overrides findClass and executed defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
SimpleClass - experiment subject -
** IMPORTANT : Compile with javac and then remove SimpleClass.java from class path (or just rename it)
Otherwise it will be loaded by System Class Loader due to class loading delegation mechanism.**
from src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
#Override
public long getCount() {
return count;
}
}
SimpleInterface - subject interface : separating interface from implementation to compile and execute output from the subject.
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
Driver - execute to test
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}
I think the problem might be more basic than what the other answers suggest. It is very possible that the class loader is loading a different file than what you think it is. To test out this theory, delete the .class file (DO NOT recompile your .java source) and run your code. You should get an exception.
If you do not get the exception, then obviously the class loader is loading a different .class file than the one you think it is. So search for the location of another .class file with the same name. Delete that .class file and try again. Keep trying until you find the .class file that is actually being loaded. Once you do that, you can recompile your code and manually put the class file in the correct directory.

Categories

Resources