2014-09-08 9 views
7

Ho usato Maven per creare un jar con un'aggiunta di classpath esterna che utilizza addClasspath.Come fa un classloader a caricare le classi nel percorso di classe manifest?

Quando eseguo quel jar utilizzando java -jar artifact.jar è in grado di caricare le classi dal jar principale e da tutti i jar nella directory libs.

Tuttavia, se si chiede la proprietà di sistema java.class.path, verrà elencato solo il contenitore principale. Se chiedo al caricatore di classe di sistema i suoi URL (ClassLoader.getSystemClassLoader().getURLs()) restituirà anche solo il contenitore principale. Se chiedo qualsiasi classe contenuta in qualche libreria per il suo programma di caricamento classi restituirà il caricatore di classe del sistema.

In che modo il caricatore di classe di sistema è in grado di caricare tali classi?

Deve avere una certa conoscenza di queste librerie per caricare le classi da quelle. C'è un modo per chiedergli questo tipo di percorso di classe "esteso"?

+0

Puoi aprire il barattolo e dare un'occhiata al manifest generato per vedere cosa sta succedendo? – gandaliter

+0

Il manifest ha una voce del percorso di classe che elenca tutti i jar nella directory libs. - Esattamente come previsto. – michas

risposta

5

La risposta breve è che l'implementazione fa parte del funzionamento interno di Sun e non è disponibile attraverso i mezzi pubblici. getURLs() restituirà sempre solo gli URL che sono passati. C'è una risposta più lunga, ma è solo per l'audacia.

Passare attraverso Oracle JVM 8 con il debugger mi ha portato attraverso una struttura quasi identica a OpenJDK6 e puoi vedere dove carica il percorso di classe here.

In sostanza, il caricatore di classi conserva una serie di URL che non ha ancora analizzato in memoria. Quando viene richiesto di caricare una classe, esso espelle gli URL dallo stack, li carica come file di classe o jar, e se sono file jar legge il manifest e spinge le voci del percorso di classe nello stack. Ogni volta che elabora un file, aggiunge il "loader" che ha caricato quel file su una mappa del caricatore (se non altro, per assicurarsi che non elabori più volte lo stesso file).

È possibile accedere a questa mappa se si è veramente motivato a fare (non lo consiglio) con:

 Field secretField = URLClassLoader.class.getDeclaredField("ucp"); 
     secretField.setAccessible(true); 
     Object ucp = secretField.get(loader); 
     secretField = ucp.getClass().getDeclaredField("lmap"); 
     secretField.setAccessible(true); 
     return secretField.get(ucp); 

esecuzione che in una configurazione fittizio in cui ho dummy-plugin.jar che fa riferimento external.jar (nel manifesto di dummy-plugin.jar) ottengo il seguente:

1) Immediatamente dopo aver creato il caricatore di classe (prima di caricare qualsiasi classe):

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[file:.../dummy-plugin.jar] 
getSecretLmapField={} 

2) dopo il caricamento di una classe da manichino-plugin.jar:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[file:.../external.jar] 
getSecretLmapField={file:.../[email protected]} 

3) Dopo aver caricato una classe da external.jar:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[] 
getSecretLmapField={file:.../[email protected], file:.../[email protected]} 

Stranamente questo sembra volare a fronte del JDK for URLClassLoader:

Le classi caricate sono per impostazione predefinita autorizzate solo a accedere agli URL specificati quando è stato creato URLClassLoader.

+0

bel trucco! ma può presentare problemi in presenza di un responsabile della sicurezza – idelvall

2

utilizzando la riflessione per accedere a un campo privato nella istanza di classe sistema di caricamento presenta diversi problemi:

  • L'adesione può essere vietato da un responsabile della sicurezza
  • La soluzione dipende dall'implementazione

Un'altra soluzione meno "intrusiva" è:

  1. Per un classloader specificato, enumera tutto il manifest disponibile cl.getResources("META-INF/MANIFEST.MF"). Questi manifesti possono essere di vasi gestiti dall'attuale caricatore di classe o dai suoi caricatori di classe ascendenti.
  2. fare lo stesso per il suo programma di caricamento classe genitore
  3. ritorno l'insieme di barattoli di coloro che manifestano in (1), ma non in (2)

L'unico requisito per questo metodo di lavorare è che vasetti in classpath deve avere un manifest per essere restituito (non c'è molto da chiedere).

/** 
* Returns the search path of URLs for loading classes and resources for the 
* specified class loader, including those referenced in the 
* {@code Class-path} header of the manifest of a executable jar, in the 
* case of class loader being the system class loader. 
* <p> 
* Note: These last jars are not returned by 
* {@link java.net.URLClassLoader#getURLs()}. 
* </p> 
* @param cl 
* @return 
*/ 
public static URL[] getURLs(URLClassLoader cl) { 
    if (cl.getParent() == null || !(cl.getParent() 
      instanceof URLClassLoader)) { 
     return cl.getURLs(); 
    } 
    Set<URL> urlSet = new LinkedHashSet(); 
    URL[] urLs = cl.getURLs(); 
    URL[] urlsFromManifest = getJarUrlsFromManifests(cl); 
    URLClassLoader parentCl = (URLClassLoader) cl.getParent(); 
    URL[] ancestorUrls = getJarUrlsFromManifests(parentCl); 

    for (int i = 0; i < urlsFromManifest.length; i++) { 
     urlSet.add(urlsFromManifest[i]); 
    } 
    for (int i = 0; i < ancestorUrls.length; i++) { 
     urlSet.remove(ancestorUrls[i]); 
    } 
    for (int i = 0; i < urLs.length; i++) { 
     urlSet.add(urLs[i]); 
    } 
    return urlSet.toArray(new URL[urlSet.size()]); 
} 

/** 
* Returns the URLs of those jar managed by this classloader (or its 
* ascendant classloaders) that have a manifest 
* @param cl 
* @return 
*/ 
private static URL[] getJarUrlsFromManifests(ClassLoader cl) { 
    try { 
     Set<URL> urlSet = new LinkedHashSet(); 
     Enumeration<URL> manifestUrls = 
       cl.getResources("META-INF/MANIFEST.MF"); 
     while (manifestUrls.hasMoreElements()) { 
      try { 
       URL manifestUrl = manifestUrls.nextElement(); 
       if(manifestUrl.getProtocol().equals("jar")) { 
        urlSet.add(new URL(manifestUrl.getFile().substring(0, 
          manifestUrl.getFile().lastIndexOf("!")))); 
       } 
      } catch (MalformedURLException ex) { 
       throw new AssertionError(); 
      } 
     } 
     return urlSet.toArray(new URL[urlSet.size()]); 
    } catch (IOException ex) { 
     throw new RuntimeException(ex); 
    } 
}