lunedì 19 aprile 2010

Integrazione Struts2 con Spring

Dopo il primo post continuo con questo a parlarvi di Struts2 e consiglio, a chi non lo ha già fatto, di leggere il primo dato che è una sua continuazione.

Tema di questo post è l'integrazione di Struts2 con Spring, che come vedremo è molto ma molto semplice e darò un esempio di come sfruttare il Dependency-Injection di Spring.

Tale pattern è stato reso acora + facile da Spring, poichè ,nel richiamare le dipendenze, viene introdotto l'uso di annotation e autoscan a partire dalla versione di Spring 2.5.


Il progetto web di esempio nelle funzionalità offerte all'utente è del tutto identico al quello del post precedente, ho cambiato solo il nome del progetto e il contextroot da struts2intro a struts2tutorial e ho solo aggiunto come dicevo Spring. Quindi potete continuare a usare il vecchio che avete scaricato o crearne uno nuovo a voi la scelta.

Innazitutto aggiungiamo le librerie di Spring (le trovate nel file scaricato di struts2 che ne ho parlato nel primo post) nella cartella WebContent > WEB-INF > lib:

  • spring-core-2.5.6.jar
  • spring-context-2.5.6.jar
  • spring-web-2.5.6.jar
  • spring-beans-2.5.6.jar
  • struts2-spring-plugin-2.1.8.1.jar

Aggiungiamo i listeners di Spring necessari nel web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>struts2tutorial</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

Abbiamo aggiunto ContextLoaderListener che serve a caricare il context di Spring nelle applicazioni web e RequestContextListener che serve come vedremo a Spring per dare lo scope che vogliamo alle action.

Mettiamo l'applicationContext.xml di Spring nella cartella WebContent > WEB-INF con il seguente contenuto:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.5.xsd"
default-autowire="byName"
default-lazy-init="true">


<context:component-scan base-package="com.blogspot.piergiuseppe82.struts2intro" annotation-config="true" scoped-proxy="no" />
</beans>

Che c'è? E' un po corto? Ma vi assicuro che è completo! Ve l'ho già detto a partire da spring 2.5 non è + necessario definire <bean.... e poi <bean... ecc. a meno che non bisogna instanziare particolari bean e o usare la factory e o passargli parametri di configurazione al bean.

In pratica tramite la dicitura '<context:component-scan.... />' e le annotation nelle classi (come vedremo) spring rende ancora + facile la gestione delle dipendenze. L'autoscan non fà altro che cercare i 'componenti' nel package e nei sotto package definito. Se volete approfondire la cosa vi rimando alle guide ufficiali comunque + avanti capirete.


Ora integriamo Spring con Struts2. Aprite lo struts.xml e prima del tag <package> inserite la seguente dicitura:

<constant name="struts.objectFactory" value="spring" />

Abbiamo detto a struts che vogliamo utilizzare Spring come object factory.

Finiti i passi di configurazione utilizziamola nel codice. L'idea è questa: far instanziare a spring la nostra ConcatenaAction e spostare la logica dell'applicazione(bella logica la concatenazione di due stringhe :D) in una classe che fa da Business-Logic che chiameremo ConcatenaBl che sarà poi utilizzata dalla action. Cominciamo dalla bl.

Create un nuovo package in 'src' con il nome "com.blogspot.piergiuseppe82.struts2intro.bl", all'interno inserite una nuova classe ConcatenaBl con il seguente contenuto:


package com.blogspot.piergiuseppe82.struts2intro.bl;

import org.springframework.stereotype.Component;


@Component("concatenaBl")
public class ConcatenaBl {
public String concatena(String primaParola, String secondaParola){
return primaParola+secondaParola;
}
}

Come vedete abbiamo la ConcatenaBl che implementa un solo metodo ed è inutile che vi dica cosa fà...
Da notare invece la annotation di classe @Component("concatenaBl") con questa annotation abbiamo detto che la classe è uno componente di spring e che quindi, nel nostro caso, può essere utilizzato da spring durante l'autoscan se rischiesto come dipendenza (come ve lo dico + avanti). L'unica accortezza (lo capirete meglio continuando a leggere), è usare come valore della annotation una stringa che inizia con il nome della classe come quella che ho utilizzato o "concatenaBlQualcosa" usando caratteri maiuscoli e minuscoli come volete tanto è case-unsensitive per spring. Comunque è bene usare una regola.... quindi per convenzione utilizzo il nome della classe con la lettera iniziale in minuscolo.

Fatta la bl modifichiamo la ConcatenaAction così:

/**
*
*/
package com.blogspot.piergiuseppe82.struts2intro.action;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.blogspot.piergiuseppe82.struts2intro.bl.ConcatenaBl;
import com.opensymphony.xwork2.ActionSupport;

/**
* @author placava
*
*/
@SuppressWarnings("serial")
@Component("concatenaAction")
@Scope("request")
public class ConcatenaAction extends ActionSupport {
@Autowired
private ConcatenaBl concatenaBl;
private String primaParola;
private String secondaParola;
private String risultato;
public ConcatenaBl getConcatenaBl() {
return concatenaBl;
}

public void setConcatenaBl(ConcatenaBl concatenaBl) {
this.concatenaBl = concatenaBl;
}

@Override
public String execute() throws Exception {
addActionMessage("Inserisci due parole e io le concateno");
return SUCCESS;
}
public String submit() throws Exception {
risultato = concatenaBl.concatena(primaParola, secondaParola);
addActionMessage("Risultato eseguito con successo. Prova ancora.");
return SUCCESS;
}

public String getPrimaParola() {
return primaParola;
}

public void setPrimaParola(String primaParola) {
this.primaParola = primaParola;
}

public String getSecondaParola() {
return secondaParola;
}

public void setSecondaParola(String secondaParola) {
this.secondaParola = secondaParola;
}

public String getRisultato() {
return risultato;
}

public void setRisultato(String risultato) {
this.risultato = risultato;
}

}

Innanzi tutto anche la action deve avere la annotation @Component se vogliamo che sia instanziata da spring e fornita a struts. In aggiunta troviamo un'altra annotation di classe: @Scope("request"). No ho fatto altro che dire a spring di generare la classe con scope request in modo tale che la action ad ogni richiesta (del browser) venga instanziata. Potevamo usare @Scope("session") in questo caso veniva instanziata alla prima richiesta e tenuta in sessione finche non scade la sessione utente o la si invalida... Semplice e utile vero?

Ora parliamo della dipendenza della action dalla bl. Per chì ha utilizzato spring sa benissimo che affinchè avvenga l'injection da parte di spring di una dipendenza in una classe, tale classe deve avere il metodo set della dipendenza. Quindi ho aggiunto come attributo privato concatenaBl e i suoi metodi accessori.
Per dire a spring che è una dipendenza ho usato sull'attributo la annotation @Autowired in modo che spring vada a cercare un componente di nome 'concatenaBl' (e lo abbiamo definito con @Component) per settarlo nella classe. Figo no?

PS: E' possibile che per casi particolari sia stato necessario dare al @Component della bl un nome ben diverso da quello convenzionale, allora vi consiglio di dare una occhiata all'utilizzo dell'annotation @Resource (in sotituzione di @Autowired) che fa parte del package javax.annotation contenuto nei jar dell'application server. Se così non fosse ne trovate un bel po di jar che lo contengono. Io uso geronimo-annotation.XXX.jar quando non presente.
Detto ciò vedete il metodo submit che può utilizzare la bl per sfruttarne la logica di business sui dati.

Non ci rimane che dire a struts2 quali componenti di spring sono le actions che ci servono quindi ecco il contenuto dello struts.xml:

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.objectFactory" value="spring" />
<package name="/default" extends="struts-default">
<result-types> la costante
<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult" />
</result-types>
<action name="concatena" class="concatenaAction">
<result name="success" type="tiles">concatena</result>
</action>
<action name="concatena_submit" method="submit" class="concatenaAction">
<result name="success" type="tiles">concatena</result>
</action>
</package>

</struts>

come vedete è uguale allo struts.xml del precedente post tranne per il fatto che ci stà in più la costante (il perchè ve l'ho spiegato prima) e sono cambiati i valori dell'attributo class dei tag action. Ora contiene il nome definito nella annotation @Component nella ConcatenaAction anziche il suo nome completo (package.nomeClasse). Molto più semplice, che ne dite ?!

Abbiamo finito! Impacchettate, deployate e vedete che funziona tutto come la precedente navigazione. Solo che ora utilizziamo l'integrazione di Struts2 e Spring. E per chi ha già avuto a che fare con spring sa gìà che si apre un mondo grazie ad esso e che tt è più facile dalla comunicazione jdbc alla gestione degli EJB..

Alla prossima su Struts2! ;)

Por.., dimenticavo il progetto eclipse dell'esempio che trovate QUI :D

1 commento: