Michele Nasti

Thoughts on what I learn

Creare dinamicamente Groovy beans in Spring

Il problema che voglio provare a risolvere è questo: data una stringa in ingresso, che contiene il nome di uno script Groovy da eseguire, siamo capaci di trovarlo su db, di istanziarlo e lanciarlo?

Sembra banale alla luce di quanto già scritto nell'articolo precedente, ma voglio aggiungere un ulteriore tacca di complessità: lo script è una classe Groovy che contiene annotations di Spring. Se venisse inizializzato da zero, molto probabilmente le variabili istanza iniettate da spring sarebbero null.

Grazie a queste pochissime righe possiamo superare il problema e goderci i nostri bean _spring_ati:

<pre class="lang:java decode:true" title="istanziare un bean groovy che contiene annotations di spring">package interoperability.groovy

import interoperability.GroovyScripterInterface import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext import org.springframework.transaction.annotation.Transactional

class GroovyBeanService implements IGroovyBeanService {

@Autowired
ApplicationContext applicationContext

@Transactional
def initializeBean(className) {

    //leggi la classe da DB, file, etc.
    def classString = new File("path/to/GroovyScript.groovy")
          .collect()
          .join("\n");

    //uso la console per istanziarlo davvero
    def gcl = new GroovyClassLoader()
    def clazz = gcl.parseClass(classString)

    //now that the file is compiled, can we istantiate it?
    def instance = clazz.newInstance();

    //autowire tutti i bean di Spring
    applicationContext.getAutowireCapableBeanFactory().autowireBean(instance);
    
    //proviamo a chiamarlo!
    instance.sayHello()
    
    return instance
}

} </pre>

Il bean <span class="lang:default decode:true crayon-inline ">GroovyBeanService </span> viene esplicitamente inizializzato da Spring, motivo per cui implementa l'interfaccia <span class="lang:default decode:true crayon-inline ">IGroovyBeanService</span> .

A riga 21 leggo il contenuto dello script e lo trasformo in una grande stringa.

Attraverso il <span class="lang:default decode:true crayon-inline">GroovyClassLoader</span> (riga 24-25) possiamo istanziare la classe appena caricata e poi istanziarla a riga 28.

L'oggetto però ottenuto fino a questo passo conterrà variabili istanza null; tramite l'applicationContext riusciamo a colmare anche questa lacuna. Dopodichè si tratta solo di chiamare i suoi metodi e utilizzarlo.

Attenti ai MemoryLeak

<span class="lang:default decode:true crayon-inline ">GroovyClassLoader </span> può prendere in input sia una <span class="lang:default decode:true crayon-inline ">String</span> sia un <span class="lang:default decode:true crayon-inline ">File</span> . Quando prende una stringa, circostanza a cui noi per esigenze strutturali non possiamo rinunciare, GroovyClassLoader non riesce a _cache_are la classe e gli oggetti appena creati non saranno cancellati dalla memoria. Tutto questo non accade se invece l'input è un file. Occorre quindi implementare un meccanismo di cache artigianale (ve lo dovete fare voi!) per evitare di reistanziare milioni di volte lo stesso bean.

Buon Groovy!