I would like to test if a particular package is known to the current classloader or system. Package.getPackage(String name) seems to return null thus im stuck. ClassLoader does not contain any method that does the same thus im stuck...
I d rather not load a class in the package to test if it is present, surely theres a better way.
Any pointers would be appreciated.
Package.getPackage(String name) is the right tool for the job. Make sure you've
Typed the name of the package correctly
Try Package.getPackages() to see what's available
Make sure the classloaders match - test for instance getClass().getPackage() and then Package.getPackage(String name)
The method searches for the package in the current classloader only (or System classloader if current classloader is null). If no class in the package is loaded in the current classloader the package will no be present.
There's no easy way to get information on if a package exist, unless a class has been loaded from that package. Package.getPackage does only work if a class from the named package has been loaded.
Your options are:
Load a class, or instantiate a class, from the package.
Search the classpath, and check all jars/dirs in the classpath.
The 2nd option can fail if the code also is using url classloaders.
Related
What I understand is that if I use:
Instrumentation#getAllLoadedClasses()
I do get a selection of all loaded classes by the target JVM. But If I do:
Class.forName("my.class.name")
This will not be the same class as the class loaded by VM. Yes, I can add this particular class as a jar in the agent MANIFEST.MF Class-Path - but that does not look the same to me as getAllLoadedClasses().
Could someone please confirm whether this is correct i.e. I would not be able to find a specific class using Class.forName() when instrumenting? My objective was not to iterate over all loaded classes using getAllLoadedClasses() - But if there is no alternative, I guess that's okay for now.
** UPDATE
What I made a mistake in writing is the Boot-Class-Path which I have now corrected in my manifest. Using -verbose:class logging I managed to see that my jars are being loaded as
[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]
But I don't see any corresponding loading information. I tried adding a Class.forName("a.package.in.someother.jar.classname") and got NoClassDefFoundError. As soon as I jump into the agent jar, I cannot use Class.forName() to check if the class is loaded by the target VM. I am getting a NoClassDefFoundError.
FURTHER UPDATE
Okay I have "Fattened" the manifest to look up all classes in my WEB-INF/lib and tomcat's lib directory. What I can see is below:
1) When my custom class MyClass is loaded for the first time. -verbose shows:
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
2) If I try to load the class again, it is correctly showing the above order.
3) My agent jar is manifested with all classes for my tomcat lib and my web-inf/lib directory. And I can also confirm that the loader sees the jars correctly.
4) Now I inject the agent, and call Class.forName("my.pkg.MyClass") from within the agent class. I get the below results.
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
I acknowledge that it's system class loader loding it inside my agent code as #RafaelWinterhalter pointed out in one of his answers. Is there any way I can force a "Delegation" so that the a different classloader loads the agent class and therefore, correctly redefines a class.
Any help is appreciated.
As it is stated in the javadoc:
Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of
the current class.
You can also see from the source code that the method is marked #CallerSensitive which means that you get a different result based on the class loader that invokes the method.
When calling Instrumentation::getAllLoadedClasses, the returned array contains classes of any class loader and not only of the current class loader which is the system class loader when running a Java agent. Therefore:
for (Class<?> type : instrumentation.getAllLoadedClasses()) {
assert type == Class.forName(type.getName());
}
is not generally true.
After a bit of run around, and Thanks to #Holger who reminded me what the problem was - incorrect class loader.
before I inject the agent, I have done the following:
// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();
// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());
// Now inject agent
try {
vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
vm.detach();
// Put back the classLoader linkage
sc.set(null, original);
}
How I have confirmed
When it goes in my Agent Class - Thread.currentThread().getContextClassLoader() becomes my application extn loader. But the system classloader now becomes `ParallelWebappClassLoader".
I am assuming this is how it works, but could be totally worng:
i) When I say Class.forName("my.pkg") it will check the system class loader which is pointing to my loader now. If the class is not found (i.e. not loaded), it will go to parents etc. I believe this is more or less the delegation model is.
ii) In this way, the class is loaded in VM by the same class loader which would also load the class in my webapp under normal circumstances.
iii) I will not instrument anything else apart from my own classes so the classloader will always be the same.
So far I have not seen any LinkageError happening. But I still feel this is too risky and if I break the link I am screwed.
Using Class.forName in a java profiler must be avoided to escape from the NoClassDef error. JVM Loads the class files in the different level of class loaders based on their classpath setting and the class file demand.
Java Cre libraries + Boot path defended libraries will be loaded in bootstrap Level
Java Agent will be loaded in system level and it goes on. Class.forName() will look the class files from the parent loaders, the current loader will not check the child loader (Until unless we implement our own loaders)
Java core classes will be accessible from your application code but our application code will not be accessible by the Java core classes. Its called class loader hierarchy.
You have three options.
Lookup the class files from the Instrumentation.GetLoadedClassFiles()
Through Transformers you can get all the loaders classes and you can track them and look for your class in every loader until you find.
Have the Class.forname implementation in the lowest level of the hierarchy so that it can internally access all the path.
Maintain the hierarchy properly to avoid too many weird errors.
Assuming you are looking for the Class<?> in order to re-transform a class, it seems to me that you could save the ClassLoader passed to your transformer, and later use ClassLoader.loadClass(String). Something like:
class MyTransformer implements ClassFileTransformer {
Map<String, ClassLoader> _name2loader = new ...;
...
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd,
byte[] classfileBuffer) throws ... {
...
_name2loader.put(className.replace("/","."), classLoader);
...
}
...
Class<?> getClass(String name) throws ClassNotFoundException {
ClassLoader cl = _name2loader.get(name);
if (cl == null) {
throw ClassNotFoundException("No loader for class " + name);
}
return cl.loadClass(name);
}
}
Note that the className passed to transform uses slashes, not dots... A better alternative than the String.replace may be to actually read the class name from the classfileBuffer using your bytecode library (such as javaassist or ASM, but if you're transforming bytecode, you're likely already using such a library).
Note: I'm not sure if you'd see the same class being passed for transformation with different ClassLoaders, but it would be good to look out for that (or research it).
In my main I have the following statement
Class booki = Class.forName("Book");
which throws a java.lang.ClassNotFoundException exception
when I use the full path like Class booki = Class.forName("javatests.Book"); it is ok.
The main class and the Book class are in the same package, I also tried using import static javatests.Book.*; but still it throws the exception if I don't set the full path javatests.Book. Can someone explain to me why?
Class.forName resolves a fully qualified class name to the class. Since a method does not know where it is called from neither the package of the calling class nor imports in the calling class play any role.
From docs Class#forName
public static Class<?> forName(String className)
throws ClassNotFoundException
Parameters:
className - the fully qualified name of the desired class.
So this will not throw ClassNotFoundException
Class booki = Class.forName("javatests.Book");
For example, it is not needed to import java.lang.* package in java program but to load class Thread from java.lang package you need to write
Class t = Class.forName("java.lang.Thread");
the above code fragment returns the runtime Class descriptor for the class named java.lang.Thread
You always need a qualified class name unless it's inside the same package. If i define a class foo in my package i can call a method Class testClass = Class.forName("foo") , but i can't call Class testClass = Class.forName("SecureRandom"); even if I import SecureRandom. That's just how the function works. It probably has a shortcut where it tries to find things inside local packages, but doesn't do much behind that.
Firstly the Book class must be in the package javatests.
the JVM load the class by name,through the classpath.
There is no class named "Book" in the classpath.
So JVM give you a ClassNotFoundException when excuse Class.forName("Book").
But 'Class.forName("javatests.Book")' tells JVM the class named 'Book' is in package 'javatests'.
So the JVM can find it and load it.
I hope my answer is helpful :)
JLS provides the following description:
Class lookup is always on behalf of a referencing class and is done through an instance of ClassLoader. Given the fully qualified name of a class, this method attempts to locate, load, and link the class.
The JDK uses one instance of ClassLoader that searches the set of directory tree roots specified by the CLASSPATH environment variable; and obviously it is not aware of the place (package) it has been called. That is why it needs fully qualified name.
In my current project I would like to store some configuration data in a package annotation and to access it by some CDI producers. If the annotation is not found in the current package the producers will search for it upward in the package hierarchy.
So far so good. Unfortunately it seems so that I can access an existing package via Package.getPackage("my.package") only after the first access to one of its classes or interfaces.
The following example illustrates this behaviour:
Class in package a.b
package a.b;
public class ClassInMitte {
}
Example programm to access the package oben.mitte
package other;
public class Refl {
public static void main(String[] args)
{
Package viaName = Package.getPackage("a.b");
System.out.println(viaName.getName());
System.out.println(viaName.hashCode());
}
}
Running Refl results in a NullPointerException. But if I add new ClassInMitte() as first statement I can access the package information. Somehow I must access the content of a package before I can access the package information itself. This makes sense since otherwise the classloaders must scan the whole classpath while starting.
But netherless is there an easy way to access package information without accessing the content of the package before? I know I could use frameworks like reflections but a 'lightweight' solution would be my prefered solution.
Package.getPackage only returns packages that are already known to the current class loader, and the only way to do that is by loading a class from that package. It's basically a wrapper for ClassLoader.getPackage.
How can I mention the path of a class as in following code?
Class cl=Class.forName("SomeClass");
This works well if the "SomeClass" is located in the same directory of the calling class. But how to check for a class from a different directory, I saw the syntax for that is like xxxx.yyyy.class, but could not make out what those 'x's and'y's stand for. please help.
Thanks in advance.
Use the fully-qualified class name. For instance, to do this with the Java SE String class, which is in the java.lang package:
Class clazz = Class.forName("java.lang.String");
Those xxx and yyy stands for package names. Packages are normally represented by directories on disk with the same name as the package. When you create a class file you can specify which package the class goes by stating package xxx.yyy at the top of the file.
Referring to "SomeClass" without a package name will try to load a class named SomeClass in the default package.
Use Class.forName although make sure you deal with a possible ClassNotFoundException exception. This is a runtime exception so it does not mean you need to catch it. This is how you will know if you path to the class is correct. The problem is that if it cannot find the class funny things can happen with your code.
Class.forName(com.my.package.ClassName)
I am trying to debug a very strange class error by looking at the ClassLoaders for some dynamically created components. ClassLoaders are something I've never played much with - and im surprised that standard JDK classes have null Class loader instances.
Can somebody explain the output of this simple main method in terms of the classes whose loaders I am attempting to print, and also more generally:
the way ClassLoaders work on the JVM and
how we can debug missing classes using ClassLoaders.
public class MyClass {
/**
* #param args
*/
public static void main(String[] args) {
System.out.println(relfect.MyClass.class.getClassLoader());
System.out.println(String.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
System.out.println(JButton.class.getClassLoader());
System.out.println(System.class.getClassLoader());
Boolean b = new Boolean(true);
System.out.println(b.getClass().getClassLoader());
}
}
Output
sun.misc.Launcher$AppClassLoader#1f7182c1
null
null
null
null
null
The javadoc for getClassLoader() says
Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.
So, that at least explains why you get that result. But it does not explain why the implementors decided to do it that way.
EDIT:
After testing adding my own classes to the bootclasspath then they also show up as null class loader.
Classloader of the bootstrap classes is null, it's not a java class.
Do not mistake the classes found of the classpath and the ones loaded by the bootstrap loader. The latter is responsible for the core JDK classes usually found in rt.jar. It's a native classloader, hence no reference towards.
The classes on the classpath are loaded by the System classloader, and the class of it can be specified via property.
Morealso the null classloader is considered a security issue and there are checks based on the caller class having null classloader.
This is how it works . Whenever JVM try to load any class it's checks below conditions.
If Class is loaded from Bootstrap ClassPath i.e; jdk\jre\lib\rt.jar , BootStrap ClassLoader will be called.
If Class is loaded from Extension Classpath i.e; jdk\jre\lib\ext*.jar , Extension ClassLoader will be called.
If Class is loaded from Application ClassPath i.e; as specified in Environment Variable , Application ClassLoader is called .
Since Bootstrap ClassLoader is not implemented in java , it's either implemented in c or c++ so there is no reference for it that's why it returns null . But Extension and Application class Loader is written in java so you will get the reference as sun.misc.Launcher$ExtClassLoader#someHexValue and sun.misc.Launcher$AppClassLoader#someHexValue .
So, if you do something like this System.out.println(String.class.getClassLoader()) you will get null since this class is been called by BootStrap ClassLoader, On the other hand if you do the same thing for a class in Ext or App Class path you will get $ExtClassLoader#someHexValue and sun.misc.Launcher$AppClassLoader#someHexValue respectively .