Come posso creare un ClassLoader genitore-ultimo / figlio-prima in Java o Come sovrascrivere una vecchia versione di Xerces già caricata nel CL padre?

Vorrei creare un caricatore di class genitore-ultimo / figlio-prima, ad esempio un programma di caricamento class che cercherà prima le classi nella class figlio loder e solo successivamente delegherà al suo ClassLoader padre per cercare le classi.

Una precisazione:

Ora so che per ottenere la separazione completa di ClassLoading ho bisogno di usare qualcosa come un URLClassLoader che passa null come genitore, grazie a questa risposta alla mia domanda precedente

Tuttavia, la domanda attuale viene per aiutarmi a risolvere questo problema:

  1. I miei codici + giare dipendenti vengono caricati in un sistema esistente, utilizzando un ClassLoader che imposta ClassLoader di System come genitore (URLClassLoader)

  2. Quel Sistema usa alcune librerie di una versione non compatibile con quella di cui ho bisogno (ad esempio una versione precedente di Xerces, che non mi permette di eseguire il mio codice)

  3. Il mio codice funziona perfettamente bene se eseguito stand alone, ma fallisce se viene eseguito da quel ClassLoader

  4. Howerver Ho bisogno di accedere a molte altre classi all’interno del ClassLoader padre

  5. Quindi voglio consentirmi di eseguire l’override, il classloader genitore “jar” con il mio: se una class chiamata viene trovata nel caricatore di classi figlio (ad esempio, ho fornito una versione più recente di Xerces con i miei barattoli, al posto di quella degli utenti dal ClassLoader che caricava il mio codice e i miei barattoli.

Ecco il codice del sistema che carica il mio codice + jar (non posso cambiarlo)

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

Ecco il codice “mio” (completamente preso dalla demo del codice “Hello World” di Flying Sauser):

 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(); } } 

Funziona in modo autonomo (principale in esecuzione) ma fallisce con questo errore quando viene caricato tramite il CL padre:

org.w3c.dom.DOMException: NAMESPACE_ERR: viene effettuato un tentativo di creare o modificare un object in un modo non corretto rispetto agli spazi dei nomi.

probabilmente perché il sistema genitore usa Xerces di una versione precedente, e anche se fornisco il jar Xerces giusto nella cartella / addOns, dato che le sue classi sono già state caricate e utilizzate dal sistema principale, non consente il mio codice da usare il mio barattolo a causa della direzione della delegazione. Spero che questo chiarisca la mia domanda, e sono sicuro che sia stato chiesto prima. (Forse non faccio la domanda giusta)

Oggi è il tuo giorno fortunato, poiché ho dovuto risolvere questo problema esatto. Ti avverto però, le viscere del caricamento di class sono un posto spaventoso. Questo mi fa pensare che i progettisti di Java non abbiano mai immaginato di voler avere un genitore-ultimo classloader.

Per utilizzare basta fornire un elenco di URL contenenti classi o jar per essere disponibili nel classloader figlio.

 /** * 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); } } } 

EDIT : Sergio e ɹoƃ hanno sottolineato che se chiami .loadClass con lo stesso nome di class, otterrai un LinkageError. Anche se questo è vero, il caso d’uso normale per questo classloader è impostarlo come classloader del thread Thread.currentThread().setContextClassLoader() o tramite Class.forName() , e che funziona così com’è.

Tuttavia, se fosse necessario direttamente .loadClass() , questo codice potrebbe essere aggiunto al metodo findClass ChildURLClassLoader nella parte superiore.

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

Il seguente codice è quello che uso. Ha il vantaggio rispetto all’altra risposta che non infrange la catena principale (puoi seguire getClassLoader().getParent() ).

Ha anche un vantaggio rispetto a WebappClassLoader di tomcat, non reinventando la ruota e non dipendendo da altri oggetti. Riutilizza il codice da URLClassLoader il più ansible.

(Non ancora onora il loader della class di sistema, ma quando avrò risolto aggiorno la risposta)

Rispetta il loader della class di sistema (per le classi java. *, La directory approvata, ecc.). Funziona anche quando la sicurezza è triggers e il classloader non ha accesso ai suoi genitori (sì, questa situazione è strana, ma ansible).

 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 classs, 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; } } 

Leggendo il codice sorgente di Jetty o Tomcat, entrambi forniscono caricatori di ultima generazione genitore per implementare la semantica webapp.

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

findClass a dire, sovrascrivendo il metodo findClass nella class ClassLoader . Ma perché reinventare la ruota quando puoi rubarla?

Leggendo i tuoi vari aggiornamenti, vedo che hai incontrato alcuni problemi classici con il sistema SPI XML.

Il problema generale è questo: se si crea un programma di caricamento classi completamente isolato, è difficile utilizzare gli oggetti restituiti. Se consenti la condivisione, puoi avere problemi quando il genitore contiene le versioni sbagliate delle cose.

È per affrontare tutta questa follia che OSGi è stato inventato, ma questa è una grande pillola da ingoiare.

Anche nelle applicazioni web, i programmi di caricamento class esentano alcuni pacchetti dall’elaborazione ‘local-first’ partendo dal presupposto che il contenitore e l’app web devono concordare tra loro l’API.

(vedi in fondo per un aggiornamento su una soluzione che ho trovato)

Sembra che AntClassLoader abbia un supporto per genitore prima / ultima (non ancora testato)

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

Ecco un frammento

 /** * 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 classs. * 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(); } 

Aggiornare:

Ho trovato anche questo , quando, come ultima risorsa, ho iniziato a indovinare i nomi delle classi su google (questo è quello che ha prodotto ChildFirstURLClassLoader) – ma sembra non essere corretto

Aggiornamento 2:

La prima opzione (AntClassLoader) è molto accoppiata a Ant (richiede un contesto di progetto e non è facile passare un URL[] ad esso

La seconda opzione (da un progetto OSGI in google code ) non era esattamente ciò di cui avevo bisogno, in quanto cercava il classloader genitore prima del classloader di sistema (loader della class Ant lo fa correttamente). Il problema, a mio avviso, è che il tuo programma di caricamento classi genitore include un jar (che non dovrebbe avere) di una funzionalità che non era su JDK 1.4 ma è stato aggiunto in 1.5, questo non ha alcun problema come loader genitore dell’ultima class ( il modello di delega regolare, ad es. URLClassLoader, caricherà sempre prima le classi del JDK, ma qui la prima implementazione ingenua del bambino sembra svelare il vecchio jar ridondante nel caricatore della class genitore, ombreggiato dall’implementazione JDK / JRE.

Devo ancora trovare un’implementazione corretta di Parent Last / Child First certificata, completamente testata e matura che non sia accoppiata a una soluzione specifica (Ant, Catalina / Tomcat)

Aggiornamento 3: l’ho trovato! Stavo cercando nel posto sbagliato,

Tutto ciò che ho fatto è stato aggiungere META-INF/services/javax.xml.transform.TransformsrFactory e ripristinato il com.sun.org.apache.xalan.internal.xsltc.trax.TransformsrFactoryImpl di JDK invece del vecchio org.apache.xalan.processor.TransformsrFactoryImpl di Xalan org.apache.xalan.processor.TransformsrFactoryImpl

L’unica ragione per cui non “accetto la mia risposta” ancora, è che non so se l’approccio META-INF/services abbia la stessa delega del classloader delle classi regolari (es. È genitore-primo / figlio-ultimo o genitore-ultimo / figlio-primo?)

Puoi eseguire l’override di findClass() e loadClass() per implementare un caricatore di prima class figlio:

 /** * 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; } }