domenica 18 aprile 2010

Introduzione a Struts2

Ben ritrovati! E' da un annetto che non metto giù qualche post ora mi sono deciso di scrivere qualcosa....(diciamo che ho un paio di giorni liberi e mi sto rompendo le beep.)

La mia intenzione è di scrivere una serie di post che danno le basi per creare applicazioni web in java utilizzando il framework MVC Struts 2 e la sua integrazione con l'altro noto framework Spring ...


Per chi viene da Struts (uno) e non ha mai utilizzato Struts 2 potrebbe pensare che cambia poco invece si sbaglia di grosso sono completamente diversi, sì mantengono la stessa interpretazione del pattern MVC ma la implementano in modo diverso... Il tutto perchè struts 2 non estende struts ma un progetto della opensymphony 'WebWORK'

Struts 2 ha eliminato alcune feature da Struts e ne ha introdotte delle nuove e a mio avviso ha fatto un buon lavoro eliminando ciò che è superfluo e inserendo ciò che è + utile.

Provo a fare un elenco delle differenze più evidenti senza scendere troppo nei dettagli (ricordo che i miei post vogliono sempre essere dei tuorials 'su come fare' e non 'perchè lo fa' per la seconda vi rimando alle documentazioni ufficiali):

La servlet di struts, che faceva da dispatcher ora non esiste più, ora viene utilizzato al suo posto un filtro.
  • Non esistono piu strutture come gli ActionFormBean e suoi simili. In Struts 2 Action e ActionFormBean sono la stessa cosa.
  • Ci sta una sola tag library di base che ha tutti gli strumenti necessari per il rendering dei contenuti html nelle jsp...
  • Viene sfruttato maggiornamente il linguaggio EL (tramite OGNL)in modo da facilitare e potenziare l'utilizzo dei tag nelle jsp per le operazioni sui dati.
  • Restando sul linguaggio EL Struts 2 lo mette a disposizione pure nel personalizzare la sua tag library tramite gli .ftl e nelle validazioni
  • Ha introdotto una nuova struttura come 'Interceptor' che lo si può vedere come un filtro per Action e avvicina tutto alla programmazone AOP
  • Si integra facilmente con Spring per instanziare le Action in modo da sfruttare al massimo la sua interpretazione del Dependency-Injection.
  • Ha reso più facile la validazione dei dati.
  • Supporta Ajax tramite dojo e/o dwr.

Vi consiglio di fare una ricerca su alcuni termini che ho utilizzato nel caso non sapete a cosa mi riferisco.

In questo post non voglio farvi vedere come utilizzare tt ciò che ho elencato ma solo come tirare su un progetto web con Struts 2 basilare.

Allora abbiamo due strade lo tiriamo su con maven o lo facciamo a manina... La prima ovviamente facilita tt ma nasconde alcune cose basilari le quali per chi si avvicina per la prima volta a struts 2 è meglio che lo faccia da solo.. Quindi seguirò la seconda strada :D

Da premettere che nell'esempio come IDE utilizzo Eclipse Galileo ma la cosa può essere replicata su altri tool di sviluppo se ne capite i concetti base.

Il progetto di esempio che voglio utilizzare non fa altro che postare due parole ad una action la quale restituisce il loro concatenamento visulizzandolo sulla stessa pagina.

Bene andiamo a creare allora, con Eclipse, un nuovo progetto web di tipo dinamico 'New > Web > Dynamic Web Project' che lo chiamo 'struts2intro'

Fatto cio scarichiamo Struts 2 da Apache e scarichiamo l'ultima versione (al momento Struts 2.1.8.1) + precisamente quella con la descrizione 'Essential Dependencies Only' in modo da avere solo l'essenziale.

Una volta scompattata potete copiare l'intero contenuto della cartella lib nella carte del progetto WebContent>WEB-INF>lib... Io a dire la verità dato che in questo progetto non utilizzo tutte le potenzialità non copio tt ma solo quelle che mi serve (in modo da esser + veloce nel deploy, a voi la scelta) quindi prendo solo:

  • commons-logging-1.0.4.jar
  • freemarker-2.3.15.jar
  • ognl-2.7.3.jar
  • struts2-core-2.1.8.1.jar
  • xwork-core-2.1.6.jar

che sono quelle veramente essenziali e aggiungo (lo capirete + avanti):

  • struts2-tiles-plugin-2.1.8.1.jar
  • tiles-api-2.0.6.jar
  • tiles-core-2.0.6.jar
  • tiles-jsp-2.0.6.jar
  • commons-digester-2.0.jar
  • commons-beanutils-1.7.0.jar
  • commons-fileupload-1.2.1.jar
Ora aggiungiamo il filtro di Struts 2 nel WebContent > WEB-INF > web.xml:

...

<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>

...

e lo mappiamo così:

...
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
.....

in modo da far intervenire struts2 su tutte le richieste http che terminano per .action

PS:Non vi preoccupate alla fine fornisco i files completi.

Dato che voglio utilizzare tiles per comporre le pagine jsp (lo uso perchè è molto elegante permete una facile manutenzione del codice nelle jsp) aggiungiamo sempre nel web xml anche il suo listener per l'integrazione con struts2:

...
<listener>
<listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
</listener>
....

Il filtro di Struts 2 di defautl va a cercare il suo file di configurazione con il nome 'struts.xml' nella cartella base delle classi (ossia WEB-INF > classes una volta creato il pacchetto) quindi noi nel progetto lo mettiamo in src ed ecco il suo contenuto iniziale:

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="/default" extends="struts-default">
<result-types>
<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult" />
</result-types>
<!-- La action seguente non viene usata nell'applicazione e solo per spiegare il file
<action name="hello" class="com.blogspot.piergiuseppe82.struts2intro.HelloWorld">
<result name="SUCCESS" type="tiles"> success </result>
<result name="FAILURE"> errore.jsp </result>
</action> -->
</package>

</struts>

che dite lo commentiamo... Allora il file si deve aprire e chiudere con i tag <struts> e </struts>. Il tag <package> ha due attributi 'name' e 'extends' e conterrà tt le nostre action.

Il tag 'name' indica il nome dello 'namespace'.. Proprio così struts 2 introduce il concetto di spazio dei nomi... ed è molto importante da capire e utile da utilizzare.


L'importanza ve la faccio capire con un esempio:

Facciamo finta di mettere al posto di '/default' '/intro' e vogliamo chiamare la action 'hello' tramite una richiesta http utilizzando l'url:

http:\\<HOST>:<PORT>\struts2intro\hello.action

ci va in errore... L'url corretto è:

http:\\<HOST>:<PORT>\struts2intro\intro\hello.action

Definendola nel nostro caso come /default non è necessario indicare il namespace e quindi possiamo utilizzare la prima.

Tale approccio è utile per separare logicamente le action all'interno di una web application. Ad esempio abbiamo una action che può essere eseguita sia diciamo in chiaro quindi senza l'autenticazione da parte dell'utente e sia in caso di autenticazione... e diciamo che nel secondo caso prima di essere eseguita va autenticato l'utente con interceptor di tipo custom (ne parlerò in un futuro post), allora si può definire un package '/secure' che prima di invocare le action in esso contenute passa da un interceptor di autenticazione. Figo no? Se ne volete sapere subito di più su come lavorano package e interceptor vi do questo link http://www.vaannila.com/struts-2/struts-2-tutorial/struts-2-framework-tutorial-1.html

Il tag extends indica quale file di configurazione estendiamo nel nostro caso quello di default contenuto nella libreria core di struts2 in modo da ereditare i suoi stack (ne parlerò sempre in un altro post) ecc..

All' inerno di <package> ho definito un tipo di risultato nei tag <result-types> che si può utilizzare nelle action indicandone nome e classe che lo implementa ... nel nostro caso 'tiles' perchè voglio utilizzare tiles come ho già detto. Ma possiamo definirne dei custom.

E poi sempre nel <package> ho messo un esempio di definizione essenziale di action con due tipi di resuts. Il tag action contiene 'name' ossia il nome della action e class la classe che la implementa.

All'interno del tag action abbiamo due result con nome e tipo. Il primo e di tipo tiles defnito prima se ricordate (se no rileggete) e contiene il nome della risorsa definita in tiles.xml (+ avanti) da richiamare. Il secondo result è quello di default che va a chiamare la risorsa indicata.

Detto ciò continuamo a mettere su il progetto quindi di seguito lo struts completo per struts2intro:

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="/default" extends="struts-default">
<result-types>
<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult" />
</result-types>
<action name="concatena" class="com.blogspot.piergiuseppe82.struts2intro.action.ConcatenaAction">
<result name="success" type="tiles">concatena</result>
</action>
<action name="concatena_submit" method="submit" class="com.blogspot.piergiuseppe82.struts2intro.action.ConcatenaAction">
<result name="success" type="tiles">concatena</result>
</action>
</package>

</struts>

Se notate a differenza di prima ci stanno due definizione di action che puntano alla stessa classe ma ne vengono eseguiti metodi diversi. Di seguito subito la classe che implementa le due action (concatena, concatena_submit), poi spiego:

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

import com.opensymphony.xwork2.ActionSupport;

/**
* @author placava
*
*/
@SuppressWarnings("serial")
public class ConcatenaAction extends ActionSupport {
private String primaParola;
private String secondaParola;
private String risultato;


@Override
public String execute() throws Exception {
addActionMessage("Inserisci due parole e io le concateno");
return SUCCESS;
}
public String submit() throws Exception {
risultato = 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;
}
}

nello struts.xml abbiamo <action name="concatena"..... e <action name="concatena_submit" method="submit"......... Dov'è la differenza? La prima non ha l'attributo method la seconda invece si con valore "submit". La conseguenza è questa: nel primo caso struts2 per default va ad eseguire sulla classe indicata, dopo averla instanziata, il metodo execute ereditato dalla superclasse ActionSupport; nel secondo caso va a richiamare il metodo indicato come attributo (nel nostro caso "sumbmit").

Come vedete le action di struts2 devono estendere la classe ActionSupport e seguire la regola dei pojo (classe con costruttore senza argomenti o non definito, definizione privata degli attributi della classe con metodi accessori). Quest'ultima cosa è molto importante, perchè, come vedremo in seguito e in altri post, gli attributi vengono valorizzati automaticaticamente da struts chiamando i metodi set, e letti con i metodi get. Tali accessi li compie sia nella jsp e sia in ambiti di validazione e in altri casi ancora...

Ora dovete tener conto di quello che sto per dire: l'instanza di una classe di tipo Action dopo essere stata eseguita e prima di renderizzare la jsp, struts2 la passa come attributo della request e diventa a tal motivo parte del context della jsp e quindi accessibile nella sua completezza dai tag e scriptlet utilizzati in quest'ultima.
In parole più semplice possiamo accedere a tutto il contenuto della action e eseguirne suoi metodi direttamente dalla jsp (anche se non è una buona regola di programmazione, di solito nella jsp vanno chiamati solo i metodi accesori dei suoi attributi)..

Ora andiamo a definire il cntenuto del file tiles.xml contenuto nella cartella WEB-INF del progetto:

<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">

<tiles-definitions>

<!-- INIZIO BASE FRAME -->

<definition name="baseFrame" template="/baseFrame/baseFrame.jsp">
<put-attribute name="header" value="/blocks/header.jsp" />
<put-attribute name="footer" value="/blocks/footer.jsp" />
</definition>
<definition name="concatena" extends="baseFrame">
<put-attribute name="frame" value="/frames/concatenaFrame.jsp" />
</definition>
</tiles-definitions>

di tiles non vi dico nulla anche perchè il post si occupa di struts2, comunque per chi viene da tiles versione 1 cambia qualcosa. Struts 2 usa tiles 2 ed alcuni tag xml di tiles cambiano. Comunque tiles se ci perdete 2 secondi a guardare con attenzione l'xml e le jsp fornite è molto semplice da capire.
Dico solo che ho definito il template base nella prima definition e nella seconda "concatena" la jsp da utilizzare come "frame" (gurdate baseframe.jsp) nel caso del risultato "concatena" di tipo "tiles" da parte delle action definite nello struts.xml.

Elenco i contenuti delle jsp. Nella cartella WebContent>baseFrame mettiamo baseFrame.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Pergiuseppe 82 - Java e Oltre</title>
<link rel="stylesheet" type="text/css" href="css/intro.css" media="all">
</head>

<body>
<tiles:insertAttribute name="header" flush="false"/>
<tiles:insertAttribute name="frame" flush="false"/>
<tiles:insertAttribute name="footer" flush="false"/>
</body>
</html>

Nella cartella WebContent>blocks mettiamo header.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<div class="header">
Introduzione a Struts 2
<p>by Pergiuseppe La Cava</p>
</div>


e footer.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<div class="footer">
<br>
Piergiuseppe82 - Java e Oltre
<p>Web: www.smartlab.net</p>
<p>Blog: piergiuseppe82.blogspot.com</p>
<br>
</div>

poi infine mettiamo nella cartella WebContent > frames il file concatenaFrame.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>

<%@ taglib prefix="s" uri="/struts-tags"%>

<div class="main">
<s:actionmessage/>
<br/>
<s:form action="concatena_submit" method="post">

<s:textfield name="primaParola" label="Prima Parola"></s:textfield>
<br/>
<s:textfield name="secondaParola" label="Seconda parola"></s:textfield>

<s:submit name="submit" value="Concatena"/>

</s:form>


<s:if test="risultato != empity and risultato != '' ">

Il risultato &egrave; <strong></strng><s:property value="risultato"/></strong>
</s:if>
</div>

Spezzettiamola e commentiamola.

<%@ taglib prefix="s" uri="/struts-tags"%> questo è l'import della tag library di struts2 la quale è molto fornita.

Tralasciando i tag html abbiamo:

<s:actionmessage/> tag che di default (come vedremo in altro post si possono personalizzare e adattare alla grafica) visulizza in elenco gli actionMessage; se rivedete nella classe ConcatenaAction ci stanno due addActionMessage e bene il tag che sto analizzando non fa altro che visualizzare tali messaggi.

<s:form action="concatena_submit" method="post"> ... </s:form> renderizza il tag html <form> </form> e prima di essere renderizzato costruisce l'url completo della action passata come valore dell'attributo name e definita nello struts.xml (nel nostro caso "concatena_submit.action").


<s:textfield name="primaParola" label="Prima Parola"></s:textfield>

<s:textfield name="secondaParola" label="Seconda parola"></s:textfield>

<s:submit name="submit" value="Concatena"/>

i primi due renderizzano campi di testo con reltiva label. Notate gli attributi name devono avere lo stesso valore degli attributi privati. Struts2 il talmodo può così eseguire i loro metodi accessori set (tramite un interceptor prima di eseguire il metodo indicato della action) in modo da trovarsi i campi valorizzati nell'esecuzione del metodo submit (capito ciò avete capito la logica di base di struts2). Stessa cosa in caso di lettura infatti nella prima esecuzione troverete i campi vuoti nel browser ma dopo aver eseguito il post al ritorno li trovate pieni; struts2 ha utilizzato i metodi get (ma non l'interceptor bensì i tag).
Ed infine abbiamo il <s:submit che renderizza il tag html <input type="submit"....
In ogni caso vi consiglio di visualizzare l'html sorgente tramite il browser per meglio capire.

Infine

<s:if test="risultato != empity and risultato != '' ">

Il risultato &egrave; <strong></strng><s:property value="risultato"/></strong>

</s:if>

questo è uno dei tag condizionali di struts2. La condizione va inserita nell'attributo 'test' utilizzando il linguaggio OGNL (una specializzazione del EL). SE si verifica la condizione il risultato va come contenuto del tag.
In questo caso la condizione è che se il campo risultato (ricordate quando dicevo che potevo accedere al contenuto della action dalla jsp) non è nullo e non è stringa vuota visualizza:
Il risultato &egrave; <strong></strng><s:property value="risultato"/></strong>
dove <s:property... stampa il valore restituito.
Sia nel test sia in <s:property i tags di struts2 non fanno altro che richiamre il metodo getRisultato() dell'instanza della classe ConcatenaAction presente nel context della jsp.

Per completezza vi do anche la pagina index.jsp che ho utilizzato e messo in WebContent:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Pergiuseppe 82 - Java e Oltre</title>
<link rel="stylesheet" type="text/css" href="css/intro.css" media="all">
</head>
<body>
<div class="header">
Introduzione a Struts 2
<p>by Pergiuseppe La Cava</p>
</div>

<div class="main">
<h1>Benvenuto</h1>
<a href="concatena.action">Prova la Action</a>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
</div>

<div class="footer">
<br>
Piergiuseppe82 - Java e Oltre
<p>Web: www.smartlab.net</p>
<p>Blog: piergiuseppe82.blogspot.com</p>
<br>
</div>


</body>
</html>

in tal modo potete anche capire meglio l'utilità di tiles (anche se per questo progetto è superfluo tiles; potevo usare 2 jsp e invece ne ho usato 5, ma è solo una chicca in più che volevo farvi conoscere).

Ora generate il war e deployate sul vostro servlet container (io ho utilizzato Tomcat 6)
Di seguito il risultato della navigazione.

Pagina iniziale:


clicco sul link (richimo la action "concatena") e mi appare:

Inserisco nei campi "Struts" e "2", clicco concatena in modo da scatenare il post a "concatena_action" ed ecco il risultato:

Cliccando QUI trovate il progetto eclipse senza librerie (questioni di dimensioni)

Spero di esser stato utile! Al prossimo post!

6 commenti:

  1. complimenti...mi è stato veramente utile; spero di poterne leggere degli altri.Saluti,
    Antonio

    RispondiElimina
  2. Ciao potresti dirmi qual'è il contributo di Eclipse nel progetto e cosa cambia se non volessi utilizzare alcun tool di sviluppo?

    RispondiElimina
  3. Nulla! L'importante è che il compilato sia una web application e pacchettizzata in .war secondo la struttura standard

    RispondiElimina
    Risposte
    1. Ciao! Potresti contattarmi a questa mail? : )
      ls.thesorcerer@gmail.com

      Elimina
  4. Questo commento è stato eliminato dall'autore.

    RispondiElimina
  5. Ciao. E' possibile cambiare il colore del testo dei sorgenti nelle pagine web perchè è veramente difficile da leggere.
    Grazie.
    Fabio.

    RispondiElimina