Come condividere le classi dipendenti tra un'app principale e plugin in Java?

4

Supponiamo che abbia un'applicazione relativamente grande e complessa (100% di file .war ) con dipendenze multiple. Alcune parti del codice definiscono una fabbrica di oggetti, in cui oggetti simili (tutti ereditati dalla stessa classe base) vengono istanziati.

Il mio obiettivo è convertire questi oggetti hard coded in plugin che possono essere caricati dinamicamente in fase di esecuzione.

Quindi, ho scritto un caricatore di plugin:

String localPath = "...";
String pluginName = "...";

File jarFile = new File(localPath);
ClassLoader pluginLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURL()});

pluginLoader.loadClass(pluginName).newInstance();

Poi ho iniziato a scrivere realmente i plug-in, che è il momento in cui mi sono reso conto che ogni potenziale plugin ha un gran numero di dipendenze e la creazione di un file jar per ciascuno di essi porterebbe a plug-in di circa 50 MB.

Sono preoccupato per i requisiti di memoria che questo comporterebbe, quando carico 100 di questi plugin.

C'è un modo per caricare plugin che dipendono da classi all'interno dell'app principale senza raggrupparli nel plugin stesso?

O come potrei condividere meglio tali risorse comuni?

[UPDATE] Per ora ho pensato di esporre le classi già caricate alla ClassLoader , che mi è stata detta funziona in questo modo:

ClassLoader mainLoader = PluginManager.class.getClassLoader();'
ClassLoader pluginLoader = URLClassLoader.newInstance(
                                 new URL[]{jarFile.toURL()},
                                 mainLoader);

Il lato negativo di questo è ovviamente che qualsiasi modifica nell'applicazione principale può far fallire i plugin. Questo, tuttavia, aiuterà con i miei problemi di memoria e con la corretta gestione degli errori sarà facile trovare plugin non più funzionanti.

    
posta Chris 03.05.2017 - 05:47
fonte

4 risposte

2

Per prima cosa, vorrei sottolineare che le dipendenze esterne (non dal codice sorgente del tuo progetto o dai suoi sottoprogetti, in termini Gradle) non sono incluse nei vasi, a meno che tu non crei specificamente un jar "grasso" (o "uberjar").

Probabilmente stai cercando compileOnly dipendenze (in termini Gradle) o provided dipendenze (in termini di Maven). Tali dipendenze vengono utilizzate solo per compilare il progetto, ma non sono incluse nella distribuzione risultante.

L'idea è che, in un codice plug-in, tu specifichi il tuo progetto principale come una dipendenza compileOnly , in modo che tu possa implementare le interfacce dall'interno di esso.

Confronta compileOnly con runtime :

  • runtime : "questi devono essere presenti quando viene eseguito questo programma, in modo che il mio codice possa utilizzarne le istanze"
  • compileOnly : "quelli sono solo necessari per il controllo dei tipi per compilare questo codice, ma non importa se esistono in runtime"

Poiché hai bisogno solo di alcune interfacce API da implementare, il secondo è il tuo caso.

    
risposta data 07.05.2017 - 22:22
fonte
0

Il ritardo nel caricamento delle dipendenze non risolverà il problema della memoria; lo rimanderà.

Il file di guerra da 100 MB non è così grande (dipende dal tuo server) e non tutto viene caricato in memoria all'avvio. Le classi vengono caricate in memoria solo se vengono create istanze / statiche. Se c'è troppa memoria richiesta, ci sono troppe istanze, non troppe classi. Suggerirei di cercare una soluzione di pooling per quelle fabbriche di oggetti.

    
risposta data 08.05.2017 - 16:59
fonte
0
  1. Identificare le dipendenze interne / esterne del plugin, rivederle e creare un barattolo dalle dipendenze, spero che dia un'opportunità anche al refactoring. Deve esserci qualcosa che è coeso a riguardo. Questo deve essere gestito un progetto separato con la sua versione. (Necessario per rintracciarli con versioni separate.)
  2. Includi una compilazione solo in caso di plug-in e includi il jar nell'applicazione principale.
  3. Caricamento dinamico del plugin nell'applicazione principale come fatto nell'applicazione.
risposta data 09.05.2017 - 04:20
fonte
0

Quello che stai descrivendo è già gestito da Java e i contenitori web spesso aggiungono un caricamento di classi più complesso (stai usando un file di guerra).

Iniziamo con il processo di caricamento di classi di base (semplificato). In Java, se provi a caricare una classe o ad usare una dipendenza, cercherà il percorso di classe per quella dipendenza. Qualunque istanza di tale dipendenza viene trovata per prima, viene tirato dentro. Quindi, se ho due classi in file separati che dipendono entrambi dalla stessa classe Logger da Log4J, non ho bisogno di due copie di Log4J nel mio classpath. Solo uno sarà mai abituato. Ma c'è un problema qui. Cosa succede se uno dei vostri vasi richiede la versione 1 di com.foo.bar.Snafu e un altro ha bisogno della versione 2 di com.foo.bar.Snafu? Questo semplice modello non funzionerà per te.

Perché l'idea alla base di JEE è che tu hai un sacco di cose diverse che corrono tutte insieme con una dipendenza da miliardi di dollari, questo risulta molto. Quindi, nella mia esperienza (che non è recente - ne parleremo più avanti) la maggior parte dei contenitori offre un modello di classloading più complesso. Il primo passo è cercare (in modo ricorsivo) attraverso tutti i programmi di caricamento della classe genitore per una classe. Se non viene trovato, verrà caricato dal percorso di classe locale delle app Web. Quindi se vuoi avere una copia di sof Log4J per tutti, la metti sul classpath principale dei contenitori. Se tutti possono usare quella versione, hai finito. Le tue guerre o le tue orecchie non hanno bisogno di fornire binari per quella dipendenza.

Tuttavia, se per questo modello sono necessarie versioni diverse per diverse app Web, ciò significa che è necessario estrarlo dal classpath principale e aggiungere le diverse versioni a diversi livelli della gerarchia del classpath. Questo è complesso e complesso, quindi i contenitori offrono la possibilità di capovolgere quel modello sulla sua testa nella tua app web. Cioè, guarderà prima nel suo classpath locale prima di cercare nei classpath genitore.

Quindi, indipendentemente dal modo in cui andate, se volete condividere queste dipendenze, basta metterle sul classpath al livello appropriato. Se sono utilizzati da più app, prendi in considerazione la possibilità di inserirli nella directory principale.

Un'altra cosa da considerare è il fatto che tutto questo tipo di assurdità fa schifo con cui lavorare. Incoraggio caldamente chiunque stia facendo sviluppo web in Java per considerare approcci embedded come embedding jetty o incorporamento di Tomcat . Renderà la tua vita molto più semplice ed è molto più compatibile con containerization .

P.S. Uno dei commenti menziona OSGi. Questo, in teoria, affronta molti di questi problemi. Tuttavia, non lo consiglierei a meno che tu non sia un masochista. Rende le cose molto più complicate e non funzionerà necessariamente per te.

    
risposta data 09.05.2017 - 17:42
fonte

Leggi altre domande sui tag