¿Cómo creo un ClassLoader primario / anterior / secundario en Java, o cómo sobrescribir una versión anterior de Xerces que ya estaba cargada en el CL padre?

Me gustaría crear un cargador de clase parent-last / child-first, por ejemplo, un cargador de clases que busque primero las clases en el lector de clases hijo y solo luego delegue en su ClassLoader padre para buscar clases.

Aclaración:

Ahora sé que para completar la separación de ClassLoading necesito usar algo así como un URLClassLoader pasando null como padre, gracias a esta respuesta a mi pregunta anterior

Sin embargo, la pregunta actual viene a ayudarme a resolver este problema:

  1. Mi código + tarros dependientes se cargan en un sistema existente, utilizando un ClassLoader que establece ese ClassLoader del sistema como su padre (URLClassLoader)

  2. Ese sistema usa algunas bibliotecas de una versión no compatible con la que necesito (por ejemplo, una versión anterior de Xerces, que no me permite ejecutar mi código)

  3. Mi código funciona perfectamente bien si se ejecuta solo, pero falla si se ejecuta desde ese ClassLoader

  4. Howerver, necesito acceso a muchas otras clases dentro del ClassLoader padre

  5. Por lo tanto, quiero permitirme Anular, el cargador de clases padre “crea” el mío: si se encuentra una llamada de clase I en el cargador de clases hijo (por ejemplo, proporcioné una versión más nueva de Xerces con mis propios archivos jar, en lugar de los usuarios únicos) por el ClassLoader que cargó mi código y tarros.

Aquí está el código del sistema que carga mi código + tarros (no puedo cambiar este)

File addOnFolder = new File("/addOns"); URL url = addOnFolder.toURL(); URL[] urls = new URL[]{url}; ClassLoader parent = getClass().getClassLoader(); cl = URLClassLoader.newInstance(urls, parent); 

Aquí está el código “my” (tomado completamente de la demostración del código de Flying Sauser “Hello World”):

 package flyingsaucerpdf; import java.io.*; import com.lowagie.text.DocumentException; import org.xhtmlrenderer.pdf.ITextRenderer; public class FirstDoc { public static void main(String[] args) throws IOException, DocumentException { String f = new File("sample.xhtml").getAbsolutePath(); System.out.println(f); //if(true) return; String inputFile = "sample.html"; String url = new File(inputFile).toURI().toURL().toString(); String outputFile = "firstdoc.pdf"; OutputStream os = new FileOutputStream(outputFile); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(url); renderer.layout(); renderer.createPDF(os); os.close(); } } 

Esto funciona de forma autónoma (se ejecuta en main) pero falla con este error cuando se carga a través de CL CL:

org.w3c.dom.DOMException: NAMESPACE_ERR: se intenta crear o cambiar un objeto de forma incorrecta con respecto a los espacios de nombres.

probablemente porque el sistema principal usa Xerces de una versión anterior, y aunque proporciono el jar de Xerces correcto en la carpeta / addOns, dado que sus clases ya fueron cargadas y utilizadas por el sistema principal, no permite que use mi propio código mi propio flask debido a la dirección de la delegación. Espero que esto aclare mi pregunta, y estoy seguro de que ya se me preguntó. (Quizás no hago la pregunta correcta)

Hoy es tu día de suerte, ya que tuve que resolver este problema exacto. Aunque te advierto, las entrañas de la carga de clases son un lugar aterrador. Hacer esto me hace pensar que los diseñadores de Java nunca imaginaron que es posible que desee tener un último cargador de clases principal.

Para usar simplemente proporcione una lista de URL que contengan clases o jarras para estar disponibles en el cargador de clases hijo.

 /** * A parent-last classloader that will try the child classloader first and then the parent. * This takes a fair bit of doing because java really prefers parent-first. * * For those not familiar with class loading trickery, be wary */ private static class ParentLastURLClassLoader extends ClassLoader { private ChildURLClassLoader childClassLoader; /** * This class allows me to call findClass on a classloader */ private static class FindClassClassLoader extends ClassLoader { public FindClassClassLoader(ClassLoader parent) { super(parent); } @Override public Class< ?> findClass(String name) throws ClassNotFoundException { return super.findClass(name); } } /** * This class delegates (child then parent) for the findClass method for a URLClassLoader. * We need this because findClass is protected in URLClassLoader */ private static class ChildURLClassLoader extends URLClassLoader { private FindClassClassLoader realParent; public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent ) { super(urls, null); this.realParent = realParent; } @Override public Class< ?> findClass(String name) throws ClassNotFoundException { try { // first try to use the URLClassLoader findClass return super.findClass(name); } catch( ClassNotFoundException e ) { // if that fails, we ask our real parent classloader to load the class (we give up) return realParent.loadClass(name); } } } public ParentLastURLClassLoader(List classpath) { super(Thread.currentThread().getContextClassLoader()); URL[] urls = classpath.toArray(new URL[classpath.size()]); childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) ); } @Override protected synchronized Class< ?> loadClass(String name, boolean resolve) throws ClassNotFoundException { try { // first we try to find a class inside the child classloader return childClassLoader.findClass(name); } catch( ClassNotFoundException e ) { // didn't find it, try the parent return super.loadClass(name, resolve); } } } 

EDITAR : Sergio y ɹoƃı han señalado que si llamas a .loadClass con el mismo nombre de clase, obtendrás un LinkageError. Si bien esto es cierto, el caso de uso normal para este cargador de clases es establecerlo como el cargador de clases del hilo Thread.currentThread().setContextClassLoader() o vía Class.forName() , y que funciona tal cual.

Sin embargo, si se necesita directamente .loadClass() , este código podría agregarse en el método ChildURLClassLoader findClass en la parte superior.

  Class< ?> loaded = super.findLoadedClass(name); if( loaded != null ) return loaded; 

El siguiente código es lo que uso. Tiene la ventaja sobre la otra respuesta que no rompe la cadena principal (puede seguir getClassLoader().getParent() ).

También tiene una ventaja sobre WebappClassLoader de tomcat al no reinventar la rueda y no depender de otros objetos. Reutiliza el código de URLClassLoader tanto como sea posible.

(todavía no respeta el cargador de clases del sistema, pero cuando lo solucione, actualizaré la respuesta)

Hace honor al cargador de clases del sistema (para clases java. *, Directorio endorsed, etc.). También funciona cuando la seguridad está activada y el cargador de clases no tiene acceso a su padre (sí, esta situación es extraña, pero posible).

 public class ChildFirstURLClassLoader extends URLClassLoader { private ClassLoader system; public ChildFirstURLClassLoader(URL[] classpath, ClassLoader parent) { super(classpath, parent); system = getSystemClassLoader(); } @Override protected synchronized Class< ?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class< ?> c = findLoadedClass(name); if (c == null) { if (system != null) { try { // checking system: jvm classes, endorsed, cmd classpath, etc. c = system.loadClass(name); } catch (ClassNotFoundException ignored) { } } if (c == null) { try { // checking local c = findClass(name); } catch (ClassNotFoundException e) { // checking parent // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything. c = super.loadClass(name, resolve); } } } if (resolve) { resolveClass(c); } return c; } @Override public URL getResource(String name) { URL url = null; if (system != null) { url = system.getResource(name); } if (url == null) { url = findResource(name); if (url == null) { // This call to getResource may eventually call findResource again, in case the parent doesn't find anything. url = super.getResource(name); } } return url; } @Override public Enumeration getResources(String name) throws IOException { /** * Similar to super, but local resources are enumerated before parent resources */ Enumeration systemUrls = null; if (system != null) { systemUrls = system.getResources(name); } Enumeration localUrls = findResources(name); Enumeration parentUrls = null; if (getParent() != null) { parentUrls = getParent().getResources(name); } final List urls = new ArrayList(); if (systemUrls != null) { while(systemUrls.hasMoreElements()) { urls.add(systemUrls.nextElement()); } } if (localUrls != null) { while (localUrls.hasMoreElements()) { urls.add(localUrls.nextElement()); } } if (parentUrls != null) { while (parentUrls.hasMoreElements()) { urls.add(parentUrls.nextElement()); } } return new Enumeration() { Iterator iter = urls.iterator(); public boolean hasMoreElements() { return iter.hasNext(); } public URL nextElement() { return iter.next(); } }; } @Override public InputStream getResourceAsStream(String name) { URL url = getResource(name); try { return url != null ? url.openStream() : null; } catch (IOException e) { } return null; } } 

Al leer el código fuente de Jetty o Tomcat, ambos proporcionan cargadores de última generación para implementar la semántica de aplicaciones web.

http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_0/java/org/apache/catalina/loader/WebappClassLoader.java

Lo que quiere decir, anulando el método findClass en tu clase ClassLoader . Pero, ¿por qué reinventar la rueda cuando puedes robarla?

Al leer sus diversas actualizaciones, veo que se encontró con algunos problemas clásicos con el sistema XML SPI.

El problema general es este: si crea un cargador de clases completamente aislado, entonces es difícil usar los objetos que devuelve. Si permite compartir, puede tener problemas cuando el padre contiene las versiones incorrectas de las cosas.

Es para tratar con toda esta locura que OSGi fue inventado, pero eso es una gran píldora para tragar.

Incluso en webapps, los cargadores de clases eximen algunos paquetes del procesamiento “local primero” en el supuesto de que el contenedor y la aplicación web deban acordar la API entre ellos.

(ver en la parte inferior una actualización de una solución que encontré)

Parece que AntClassLoader tiene soporte para parent first / last, (aún no lo ha probado)

http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/AntClassLoader.java

Aquí hay un fragmento

 /** * Creates a classloader for the given project using the classpath given. * * @param parent The parent classloader to which unsatisfied loading * attempts are delegated. May be null, * in which case the classloader which loaded this * class is used as the parent. * @param project The project to which this classloader is to belong. * Must not be null. * @param classpath the classpath to use to load the classes. * May be null, in which case no path * elements are set up to start with. * @param parentFirst If true, indicates that the parent * classloader should be consulted before trying to * load the a class through this loader. */ public AntClassLoader( ClassLoader parent, Project project, Path classpath, boolean parentFirst) { this(project, classpath); if (parent != null) { setParent(parent); } setParentFirst(parentFirst); addJavaLibraries(); } 

Actualizar:

Encontré esto también, cuando como último recurso comencé a adivinar nombres de clase en google (esto es lo que produjo ChildFirstURLClassLoader) – pero parece ser incorrecto

Actualización 2:

La primera opción (AntClassLoader) está muy acoplada a Ant (requiere un contexto de proyecto y no es fácil pasar una URL[] a ella

La segunda opción (de un proyecto OSGI en el código de google ) no era exactamente lo que necesitaba ya que buscó el cargador de clases padre antes del cargador de clases del sistema (el cargador de clases Ant lo hace correctamente por cierto). El problema tal como lo veo, creo que el cargador de clases padre incluye un jar (que no debería tener) de una funcionalidad que no estaba en JDK 1.4 pero se agregó en 1.5, esto no tiene ningún daño como el cargador de última generación padre ( modelo de delegación regular, por ejemplo, URLClassLoader) siempre cargará primero las clases del JDK, pero aquí la primera implementación ingenua del niño parece revelar el antiguo y redundante jar en el cargador de clases padre, ocultando la propia implementación de JDK / JRE.

Aún no he encontrado una implementación correcta certificada, completamente probada, madura, parental / infantil primera, que no esté acoplada a una solución específica (Ant, Catalina / Tomcat)

Actualización 3 – ¡Lo encontré! ESTABA buscando en el lugar equivocado,

Todo lo que hice fue agregar META-INF/services/javax.xml.transform.TransformerFactory y restaurar el com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl del JDK en lugar del viejo Xalan’s org.apache.xalan.processor.TransformerFactoryImpl

La única razón por la que no “acepto mi propia respuesta”, es que no sé si el enfoque META-INF/services tiene la misma delegación de cargador de clases que las clases regulares (por ejemplo, es parent-first / child-last o parent-last / child-first?)

Puede anular findClass() y loadClass() para implementar un cargador de primera clase hijo:

 /** * Always throws {@link ClassNotFoundException}. Is called if parent class loader * did not find class. */ @Override protected final Class findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(); } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)){ /* * Check if we have already loaded this class. */ Class c = findLoadedClass(name); if (c == null){ try { /* * We haven't previously loaded this class, try load it now * from SUPER.findClass() */ c = super.findClass(name); }catch (ClassNotFoundException ignore){ /* * Child did not find class, try parent. */ return super.loadClass(name, resolve); } } if (resolve){ resolveClass(c); } return c; } }