Hago un nuevo ClassLoader
y lo hago definir un nuevo Class
, lo que significa que la nueva clase debe estar en un nuevo espacio de nombres, que es, AFAIK. Lo extraño es que cuando llamo al Class.getPackage
en la nueva clase, devuelve el mismo objeto exacto devuelto al llamar al getPackage
en cualquier otra clase en mi espacio de nombres principal.¿Por qué Class.getPackage devuelve el mismo paquete para clases de diferentes paquetes?
Según la JVM spec:
El paquete de tiempo de ejecución de una clase o interfaz se determina por el paquete nombre y la definición de cargador de clases de la clase o interfaz.
En otras palabras, si tiene dos clases en el mismo paquete, pero las cargan diferentes cargadores de clases, se considera que están en paquetes diferentes. (Esto también puede ser "confirmado" a través de la reflexión en mi caso de prueba a continuación.)
Entonces, ¿cómo es que cuando hago esto obtengo el mismo resultado de getPackage
en ambas clases?
Aquí es mi prueba:
package pkg;
import java.io.*;
// Yes, you can try commenting this class, you'll get the same result.
class LoadedClass {
LoadedClass() {
System.out.println("LoadedClass init");
}
}
class MyClassLoader extends ClassLoader {
Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
class Main {
public static void main(String[] args) throws Exception {
MyClassLoader mcl = new MyClassLoader();
// load compiled class from file
FileInputStream fileinputstream = new FileInputStream(
"/home/me/test/pkg/LoadedClass.class" /* <- point to whever these classes
* are being compiled to. */
);
int numberBytes = fileinputstream.available();
byte classBytes[] = new byte[numberBytes];
fileinputstream.read(classBytes);
fileinputstream.close();
Class<?> lc = mcl.defineClass("pkg.LoadedClass", classBytes);
Package myPackage = Main.class.getPackage();
Package lcPackage = lc.getPackage();
System.out.println("lc package: " + lcPackage);
System.out.println("my package: " + myPackage);
System.out.println("lc ClassLoader: " + lc.getClassLoader());
System.out.println("lc ClassLoader parent: " +
lc.getClassLoader().getParent());
System.out.println("my ClassLoader: " + Main.class.getClassLoader());
System.out.println("are they equal? " + (lcPackage == myPackage));
if (lcPackage == myPackage) {
System.out.println("okay... we should be able to instantiate " +
"the package if that's true, lets try");
lc.newInstance(); // boom as expected
}
}
}
Genera:
lc package: package pkg
my package: package pkg
lc ClassLoader: [email protected]
lc ClassLoader parent: [email protected]
my ClassLoader: [email protected]
are they equal? true
okay... we should be able to instantiate the package if that's true, lets try
Exception in thread "main" java.lang.IllegalAccessException: Class pkg.Main can not access a member of class pkg.LoadedClass with modifiers ""
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.Class.newInstance0(Class.java:349)
at java.lang.Class.newInstance(Class.java:308)
at pkg.Main.main(Main.java:42)
Como era de esperar, normalmente no se puede crear una instancia de esta clase cargada a través de la reflexión, porque el paquete-privada y se encuentra en un diferente paquete (mismo nombre, diferente espacio de nombres), que es correcto AFAIK, porque está aplicando seguridad de tipo.
Solo me pregunto porque he estado estudiando la JVM y la arquitectura de seguridad en los últimos días y sigo encontrando pequeñas sutilezas como esta, así que es difícil razonar.
Sí, se estaba llamando al ClassLoader principal. "Pero, ¿realmente importa si hay un objeto de paquete o varios objetos de paquete?"Supongo que es un caso de uso raro, pero la implementación de openjdk que examiné comprueba el paquete en la reflexión, por supuesto, y resulta que compara el ClassLoader y el nombre completamente calificado, en lugar de llamar a cualquiera de los' getPackage's. –
I Supongo que esta es una de las razones por las que las personas de OSGi odian los "paquetes divididos" (paquetes que se extienden a través de archivos jar, paquetes, cargadores de clases). – Thilo
@Longpoke - mi * "¿realmente hace alguna diferencia ..." * punto es que una vez comprenda esta anomalía que puede programar a su alrededor, como lo hace el código que encontró. –