Spring utilizza un sistema di Dependency Injection, per recuperare le risorse.
La dependency injection si basa sul modello dell’inversion of control, cioè il container è delegato nel recuperare le risorse necessarie al funzionamento a runtime dell’applicazione, piuttosto che lasciare quest’onere di effettuare il lookup sul servizio di discovery all’applicazione stessa.
La risoluzione delle dipendenze in Spring è effettuata mediante l’oggetto BeanFactory, trattandosi di una factory, ci sono numerosi vantaggi a livello di flessibilità, ad esempio in fase di testing, è possibile configurare la factory per restituire delle risorse fittizie (un DB locale, anzichè quello remoto), e poter comunque testare le funzionalità dell’applicazione
Al fine di illustrare il funzionamento della dipendency injection, vediamo un esempio semplice in cui step by step arricchiamo la nostra factory.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
Il semplice codice dell’HelloWorld, mostra già due dipendenze implicitamente risolte:
- Il testo da stampare,
Possiamo usare un MessageProvider per fornirlo separatamente, ad esempio, vorremmo poter decidere di acquisirlo in diversi modi, prelevandolo da riga di comando, o da un file etc…
- Lo stream su cui fare il rendering del messaggio,
Che potrebbe essere diverso, ad esempio un file, quindi occorre specificarlo in separata sede, attraverso un MessageRenderer. Sviluppando queste due classi, otteniamo un certo disaccoppiamento tra la logica di acquisizione del messaggio, o del rendering, e il resto del codice.
public class HelloWorldMessageProvider
{
public String getMessage() {
return "Hello World!";
}
}
public class StandardOutMessageRenderer {
private HelloWorldMessageProvider messageProvider = null;
public void render() {
if (messageProvider == null) {
throw new RuntimeException("You must set the property
messageProvider of class:“ + StandardOutMessageRenderer.class.
getName());
}
System.out.println(messageProvider.getMessage());
}
// dependency injection tramite metodo setter
public void setMessageProvider(HelloWorldMessageProvider provider) {
this.messageProvider = provider;
}
public HelloWorldMessageProvider getMessageProvider() {
return this.messageProvider;
}
}
public class HelloWorldDecoupled {
public static void main(String[] args) {
StandardOutMessageRenderer mr =
new StandardOutMessageRenderer();
HelloWorldMessageProvider mp =
new HelloWorldMessageProvider();
mr.setMessageProvider(mp);
mr.render();
}
}
Nel codice del main, ci accorgiamo che c’è ancora una forte limitazione: siamo costretti a instanziare manualmente degli oggetti per il Renderer e il Provider nel launcher (main), inoltre siamo costretti a scegliere staticamente delle specifiche implementazioni di Renderer e Provider, (StandardOut ed HelloWorld rispettivamente).
Ci viene incontro il pattern Factory, e l’uso delle interfacce, queste ultime pongono un livello di astrazione rispetto all’implementazione di Renderer e Provider che abbiamo intenzione di scegliere. La factory riesce a generare delle instanze specifiche del Renderer e del Provider, prelevandole da un file di testo su cui elenchiamo tali proprietà:
private MessageSupportFactory() {
props = new Properties();
try {
props.load(new FileInputStream("msf.properties"));
// ottiene i nomi delle classi per le interfacce
String rendererClass = props.getProperty("renderer.class");
String providerClass = props.getProperty("provider.class");
renderer = (MessageRenderer) Class.forName(rendererClass).
newInstance();
provider = (MessageProvider) Class.forName(providerClass).
newInstance();
} catch (Exception ex) { ex.printStackTrace(); }
}
static { instance = new MessageSupportFactory(); }
public static MessageSupportFactory getInstance() {
return instance;
}
public MessageRenderer getMessageRenderer() {
return renderer;
}
public MessageProvider getMessageProvider() {
return provider;
}
}
Ed ecco il launcher che fa uso della Factory.
public class HelloWorldDecoupledWithFactory {
public static void main(String[] args) {
MessageRenderer mr = MessageSupportFactory.getInstance().
getMessageRenderer();
MessageProvider mp = MessageSupportFactory.getInstance().
getMessageProvider();
mr.setMessageProvider(mp);
mr.render();
}
}
Spring ci offre già una Factory per generare i Bean di cui abbiamo bisogno, ecco un launcher che fa uso della BeanFactory di Spring.
public class HelloWorldSpring {
public static void main(String[] args) throws Exception {
// ottiene il riferimento a bean factory
BeanFactory factory = getBeanFactory();
MessageRenderer mr = (MessageRenderer) factory.
getBean("renderer");
MessageProvider mp = (MessageProvider) factory.
getBean("provider");
mr.setMessageProvider(mp);
mr.render();
}
// Possibilità di scrivere il proprio metodo getBeanFactory()
// a partire da Spring DefaultListableBeanFactoryclass
private static BeanFactory getBeanFactory() throws Exception {
DefaultListableBeanFactory factory = new
DefaultListableBeanFactory();
// creare un proprio lettore delle definizioni
PropertiesBeanDefinitionReader rdr = new
PropertiesBeanDefinitionReader(factory);
// caricare le opzioni di configurazione
Properties props = new Properties();
props.load(new FileInputStream("beans.properties"));
rdr.registerBeanDefinitions(props);
return factory;
}
}
Notiamo che in questo modo abbiamo un metodo getBean completamente disaccoppiato dalla logica che tale Bean deve implementare, tali informazioni vengono passate per parametro. Inoltre non dobbiamo far uso di “glue-code” per usare una Factory che già è messa a disposizione dal sistema.
Ci viene in mente però che c’è un’ultimo accorgimento da applicare per evitare di risolvere manualmente le dipendenze:
mr.setMessageProvider(mp);
Con questa riga andiamo a fornire noi l’istanza del MessageProvider al MessageRenderer, vorremmo che Spring lo facesse al posto nostro, magari prelevando tale informazione da un file di configurazione Xml: questo è di fatto il comportamento standard della Dipendency Injection di Spring.
public class HelloWorldSpringWithDIXMLFile {
public static void main(String[] args) throws Exception {
BeanFactory factory = getBeanFactory();
MessageRenderer mr = (MessageRenderer) factory.
getBean("renderer");
mr.render();
}
private static BeanFactory getBeanFactory() throws Exception {
BeanFactory factory = new XmlBeanFactory(new
FileSystemResource("beans.xml"));
return factory;
}
}
Come è configurato il file beans.xml ?
Prima di vederlo dobbiamo sapere che in Springs, la Dependency Injection, può essere effettuata in due modi:
- A livello di metodo
- A livello di costruttore
<beans> <bean id="renderer" class="StandardOutMessageRenderer"> <property name="messageProvider"> <ref local="provider"/> </property> </bean> <bean id="provider" class="HelloWorldMessageProvider"/> </beans>
Nel primo caso la classe che richiama la dipendenza ha un metodo setter, come lo è mr.setMessageProvider(mp); nel file xml , col tag <ref local =”provider”> andiamo a specificare che tale metodo si ritroverà automaticamente risolta la dipendenza mp (il MessageProvider), ricevendo come parametro un’instanza del bean “provider”, cioè della classe HelloWorldMessageProvider.
Possiamo inoltre usare una DI a livello di costruttore
<bean id="provider"> <constructor-arg> <value>Questo è il messaggio configurabile</value> </constructor-arg> </bean>
In questo modo ad esempio, andiamo ad iniettare il messaggio al MessageProvider, attraverso il file beans.xml piuttosto che doverlo cablare nel codice come prima.
Poichè è una iniezione a livello di costruttore, dobbiamo implementare il MessageProvider in questo modo
public class ConfigurableMessageProvider
implements MessageProvider {
private String message;
public ConfigurableMessageProvider(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
Ci penserà Spring a passare, dietro le quinte, il messaggio specificato nel beans.xml al costruttore.
Codesnippets gentilmente concessi dal prof. Bellavista ed estratti dalle slides inerenti al corso di Sistemi Distribuiti M di Ingegneria Informatica, Università di Bologna, tutti i diritti riservati














[...] funzionalità importante di Spring è l’AutoWiring. Nell’esempio precedente abbiamo visto come è organizzato il descrittore xml dove andiamo a definire le dipendenze, tramite [...]
[...] funzionalità importante di Spring è l’AutoWiring. Nell’esempio precedente abbiamo visto come è organizzato il descrittore xml dove andiamo a definire le dipendenze, tramite [...]