How can I use byte-buddy generated classes with "org.reflections"?
Example:
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.name("de.testing.SomeClass")
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
Now I want to use org.reflections to find all subtypes of Object inside a specific Package:
Reflections reflections = new Reflections("de.testing");
Set<Class<? extends Object>> objs = reflections.getSubTypesOf(Object.class);
for (Class clazz : objs ) {
log.info("{}",clazz.getName());
}
Any ideas?
As suggested in the comments, reflections scans the class path by querying class loaders for its resources. This does normally only work for standard class loaders whereas Byte Buddy creates classes in memory where they are not found using resource scanning.
You can work around this by storing Byte Buddy's classes in a jar file and loading this jar file manually using a URLClassLoader. Byte Buddy allows you to create a jar by .make().toJar( ... ). You can then provide this class loader to reflections which by default only scans the system class loader.
All this does however seem like quite a complex solution to a problem that could be easily solved by registering your types somewhere explicitly.
Related
I have a jar:
/home/cole/lib/a.jar
And in this jar I have the following interface/classes (horrible names for illustration purposes only!):
CreatorInterface.java
Base.java (implements CreatorInterface.java)
AbstractBase.java (extends Base.java)
Implementation.java (extends AbstractBase.java)
In a separate project I have the following code:
final URL[] jars = new URL[] {
new File("/home/cole/lib/a.jar").toURL();
}
final URLClassLoader classLoader = new URLClassLoader(jars, null);
final Class<?> implementation = classLoader.loadClass("Implementation");
final CreatorInterface object = (CreatorInterface)implementation.newInstance();
However when I run the above, I get the following:
java.lang.ClassCastException: Implementation cannot be cast to CreatorInterface
Given Implementation is ultimately an instance of a class that implements CreatorInterface, why do I get the ClassCastException?
Update 1
This isn't a question about using URLClassLoader, the class is found ok, the problem appears to be in the instantiation. For example, the following code works fine:
final Object object = implementation.newInstance();
Update 2
As #davidxxx answered, I have the interface class twice (once in the jar and once in the project using it). Although the interface was the same, this was the cause of the issue.
However to make it work, I needed to fix my URLClassLoader like this, to avoid a ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
This exception :
java.lang.ClassCastException: Implementation cannot be cast to
CreatorInterface
makes me think that you have very probably two distinct CreatorInterface classes : one included in the jar and another other coming from the client program that tries to load it.
Even if the two classes have the same name (qualified names), these are different classes for each classloader as here you use two unassociated classloaders.
You have the current classloader of the program that you run and this other classloader as you specified null as parent classloader :
final URLClassLoader classLoader = new URLClassLoader(jars, null);
So as you try to assign the object created by reflection to the CreatorInterface variable, the cast fails because two distinct CreatorInterface were loaded by each classloader and are used : one coming from the classloader of your client code and another coming from the the instantiated classloader.
Using a single classloader would solve the issue but a best practice would be including the jar in the classpath of the project and to ensure to have a single version of the classes provided in the jar.
To decouple things you should probably split the jar into 2 jars : an API jar that contains only the interface and an implementation jar that depends on the API jar and that contains other classes.
In the client code, add in the classpath only the API jar to be able to assign to a variable with the interface declared type.
About your second point :
This isn't a question about using URLClassLoader, the class is found ok, the problem appears to be in the instantiation. For example, the
following code works fine:
final Object object = implementation.newInstance();
In this case you don't refer the interface type.
You indeed assign the Implementation object to an Object and not to a CreatorInterface variable.
The correct/consistent interfaces and subclasses are loaded by the classloader but here you never give a chance to provoke a ClassCastException as you never assign it to a type of a duplicate class but Object that is defined a single time.
So the problem previously encountered cannot occur.
About the third point :
However to make it work, I needed to fix my URLClassLoader like this,
to avoid a ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
It works because here you create a classloader associated to the parent classloader.
In fact if you did :
final URLClassLoader classLoader = new URLClassLoader(jars);
It would produce the same result as the URLClassLoader object created would use by default the delegation to the parent classloader (here the classloader that started your application).
I am currently loading Java classes using Class.forName() to load it.
clazz = Class.forName("interfaces.MyClass");
But now I want to load classes from different directory, I have tried to set classpath by
clazz = Class.forName("-cp \"C:/dir\" distantinterfaces.DistantClass");
With no success and ClassNotFoundException. Full path to distant class is:
C:/dir/distantinterfaces/DistantClass.class
Use an URLClassLoader for this. The code might be something along the lines of:
File f = new File("C:/dir");
URL[] cp = {f.toURI().toURL()};
URLClassLoader urlcl = new URLClassLoader(cp);
Class clazz = urlcl.loadClass("distantinterfaces.DistantClass");
Either the directory is in the classpath, and you can use Class.forName() (which only accepts fuly qualified name classes, and not -cp command line options), or it's not in the classpath and you should then use a custom class loader.
You're not saying what you really want to do (why are you loading classes dynamically), but your best bet is to have the directory in the classpath.
You have to create an instance of ClassLoader which is aware of the directory with classes. See stackoverflow questions tagged urlclassloader.
I have the following code that works nicely if the project is in my classpath locally and I do a getClass().getClassLoader(). But when I try to load dynamically from an external jar it fails.
It fails to load MyType, which is the type on the method parameter like this:
#MyAnnotation("FOO.BAR")
public Object echo(#Valid MyType param) throws Exception {
return...;
}
This code will fail with "org.reflections.ReflectionsException: could not get type for name MyType":
myLoader = new URLClassLoader(new URL[]{new URL("file:///"+jarfile)});
myLoaders.Add(myLoader);
ConfigurationBuilder builder = new ConfigurationBuilder()
.addClassLoaders(loaders)
.setScanners(new MethodAnnotationsScanner())
.setUrls(ClasspathHelper.forPackage(myPackage, myLoaders));
Reflections reflections = new Reflections(builder);
return reflections.getMethodsAnnotatedWith(MyAnnotation.class);
I believe the problem lies in the class loader. Is there another way to load a jar such that it picks up all the types?
I think I just solved it by using forClassLoader instead of forPackage.
I am using a third party library called Reflections (not to be mistaken with Java reflection) to search another jar for Classes that extend Foo using the following code:
Reflections reflections = new Reflections("com.example");
for(Class<? extends Foo> e : reflections.getSubTypesOf(Foo.class)) {
doSomething()
}
When I do this Reflections throws the following error:
org.reflections.ReflectionsException: could not get type for name com.example.ExtendsFoo
Does anyone know how to fix this cause I'm stumped?
Thanks in advance!
The problem may be due to not having a class loader that can resolve the name (even though it can resolve the subtype). This sounds contradictory, but I had the error message when I was building a Configuration and using ClasspathHelper.forClassLoader on an application- instantiated URLClassloader to figure out what to scan on the classpath, but not passing in said URLClassLoader into the Reflections configuration so that it could instantiate things correctly.
So you may want to try something along the lines of the following:
URLClassLoader urlcl = new URLClassLoader(urls);
Reflections reflections = new Reflections(
new ConfigurationBuilder().setUrls(
ClasspathHelper.forClassLoader(urlcl)
).addClassLoader(urlcl)
);
where urls is an array of URLS to the jars containing the classes you want to load. I was getting the same error as you if I did not have the final addClassLoader(...) call to the ConfigurationBuilder.
If this doesn't work, or is not applicable, it may be worth just setting a breakpoint in ReflectionsUtil.forName(String typeName, ClassLoader... classLoaders)) to see what is going on.
Take a look: https://code.google.com/p/reflections/issues/detail?id=163
Reflections (in its current version 0.9.9-RC1) doesn't re-throw exception correctly. That's why you may miss the true cause of the problem. In my case it was a broken .class file, which my default class loader failed to load and threw an exception. So, first of all, try to make sure that your class is truly loadable.
Scanning for classes is not easy with pure Java.
The spring framework offers a class called ClassPathScanningCandidateComponentProvider that can do what you need. The following example would find all subclasses of MyClass in the package org.example.package
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class));
// scan in org.example.package
Set<BeanDefinition> components = provider.findCandidateComponents("org/example/package");
for (BeanDefinition component : components)
{
This method has the additional benefit of using a bytecode analyzer to find the candidates which means it will not load all classes it scans.
Class cls = Class.forName(component.getBeanClassName());
// use class cls found
}
Fore more info read the link
I am uploading a jar dynamically through servlet and saving it in my WEB-INF/lib directory.
I want to get all the classes annotated with my #annotation,
have used reflections code below without any luck.. the manifest of the jar is readble but the classes are not.. the list of classes is 0
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
ConfigurationBuilder builder = new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner(),
new TypeAnnotationsScanner());
Set<URL> set = ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]));
FilterBuilder filterBuilder = new FilterBuilder().include(FilterBuilder.prefix(exportPackage));
Reflections reflections = new Reflections(builder.setUrls(set).filterInputsBy(filterBuilder));
Set<Class<? extends Object>> classSet = reflections.getTypesAnnotatedWith(MyAnnotation.class);
What changes to the configuration will help get the classes from the jar that is dynamically uploaded..
Since you are updating your own WEB-INF/lib directory it is not necessarily caught by your context class loader. BTW I think it is a bad practice: the behavior depends on the application server and this directory is probably not writable and even probably does not exist if you are running from war...
So, I'd put the jar to other directory and use my custom class loader. It is not so hard. You can use regular UrlClassLoader. Just configure it to read classes from correct path. Once this is done pass this class loader when you are creating instance of Reflections. Take a look on its javadcoc. The constructor can except various types of parameters including class loader.
from your listener class (or from wherever servletContext is available), try using:
new Reflections(ClasspathHelper.forWebInfClasses(servletContext))
or
new Reflections(ClasspathHelper.forWebInfLib(servletContext))