Appunti su Spring e Java

Programmazione - Java Visite: 13282

Emmegi Arts news java

Appunti sul framework Spring e la programmazione in Java

Volete sapere tutto sul framework Spring? Siete nel posto giusto!
In questo articolo elencherò una serie di link e risorse e consigli utili per la programazione in Spring: da dove iniziare; come creare un servizio; come mappare gli errori nelle pagine JSP; ...

 

AGGIORNAMENTI

10/12/2011: rivisitazione della parte dedicata ai JOB e a QUARTZ

 

 

 

Introduzione

Se siete neofiti e volete iniziare a capirci qualcosa in SPRING allora iniziate dai Link Utili e dagli articoli, ottimi e ben fatti, di Mokabyte.

Creazione e Configurazione di un servizio SPRING

Vediamo come si crea un servizio e dove e come utilizzarlo.

<package>.service: qui ci vanno le classi astratte dei servizi
<package>.service.impl: qui ci vanno le implementazioni dei servizi

Il servizio ad esempio viene usato in un controller spring:

@Controller -->TAG che specifica che la classe è un controller, nel caso di autoscan dei controller
public class MioController {

/**
* Dichiarazione del Servizio
* Si utilizza il serivzio astratto e non l'implementazione vera e propria.
*/
@Autowired
private MioService mioService;

L'attributo del controller viene dichiarato Autowired, in questa maniera si utilizza l'Inversion Of Control.

Il servizio deve essere dichiarato nel file spring-service.xml così fatto:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="mioService"
class="<package>.MioServiceImpl" />

</beans>

All'intera applicazione l'esistenza di questo file di configurazione glielo si dice nel web.xml:

....
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-service.xml
</param-value>
</context-param>

Se si vuole evitare di creare il file spring-service.xml si può configurare l'applicazione per l'autoscan dei servizi.
In questo caso la classe del servizio deve essere taggata come @Service.

Pagine JSP e BEAN


Nella pagina JSP la form diventa

<form:form method="POST" commandName="mioBean">
<form:errors path="*" cssClass="error"/>
</form:form>

nel commandName si mette il nome del bean da usare per riempire la form.
Scrivendo <form:errors path="*" cssClass="error"/> si fa in modo da mostrare gli errori che si generano.
Con path="*" si mostrano tutti gli erorri.
Se scrivo <form:errors path="pippo" cssClass="error"/> mostrerò solo gli errori del path pippo del bean.

Nel controller bisogna ovviamente istanziare il bean e meterlo nel modello altrimenti nella pagina JSP non arriva.

MioBean mioBean = new MioBean();
model.addAttribute("mioBean", mioBean);

Così facendo si mette nel modello il Bean e lo si rende disponibile nella pagina JSP.

Pagine JSP, SELECT e TypeEditor

Questo il codice che va nella JSP:

<form:select id="provincia" path="campo" items="${campi}"
itemValue="id" itemLabel="name" />

Così facendo si crea il codice per la select.

Dall'esempio si evince che nel nostro bean abbiamo un attributo campo che avrà un valore "name" ed in id "id".
Supponiamo che l'attributo campo sia un oggetto chiamato State più o meno così fatto

class State
{
protected String id = "";
protected String name = "";
}

Questo il metodo del controller che si occupa di riempire la select:

@ModelAttribute("campi") --> questo deve corrispondere al valore nella pagina JSP items
public List<State> popolaCampi() {
return mioServizio.getAllCampi();
}

Tramite il servizio si estraggono tutti i valori dei Campi.

A questo punto facendo la submit della form si avrà un errore perché il valore della select sarà una stringa (il nome/valore del campo scelto, nel nostro esempio si fa la submit dell'id dell'oggetto State) invece nel bean c'è un oggetto State con id e name.

Allora nel controllore si deve mettere:
@InitBinder
public void initBinder(WebDataBinder binder) throws Exception {
binder.registerCustomEditor(String.class, "campo",
new MyCampoTypeEditor());

}
}

In questa maniera si registra nel sistema un TypeEditor associato al campo "campo" e al controller dove viene dichiarato.
Questo oggetto, MyCampoTypeEditor dovrà convertire la stringa del campo che viene dal submit della form in un oggetto State da mettere nel bean.
Il TypeEditor sarà così fatto:
public class MyCampoTypeEditor extends PropertyEditorSupport {

public MyCampoTypeEditor() { }

public void setAsText(String text) throws IllegalArgumentException {
setValue(new State(0, value));
}
}

In questo caso si sta creando un oggetto State con id fisso a zero e name quello scelto dalla form.

Redirect dentro un controller

Ecco come fare la redirect all'interno di un controller spring:
return new ModelAndView(new RedirectView("home.do", false, false, false));

In questa maniera all'uscita della funzione viene invocata la nuova URL.

Spring e il logging con log4j

Vediamo come creare il proprio file di log utilizzando la libreria log4j in una applicazione scritta in Java e con il framework Spring?
I passi da compiere sono pochi.
Come prima cosa bisogna modificare il file web.xml inserendo:


<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/my-log4j.properties</param-value>
</context-param>

A cosa serve? Log4j ha bisogno di un suo file di configurazione (in formato XML o nel classico file .properties, noi considereremo quest'ultimo tipo).
Questo file di solito si chiama log4j.properties ma in Spring non potete chiamarlo!!!
Dovete chiamarlo in un qualunque altro modo e nel file web.xml dovete indicare dove si trova e come si chiama.

Sempre nel file web.xml bisogna aggiungere:


<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

Questa istruzione serve ad attivare il listener di Spring appunto per il logging con log4j.

Dove far scrivere il file di log?
Una scelta ottimale è la cartella dei log di jboss.
Nel file delle proprietà di log4j si deve mettere qualcosa del tipo:

log4j.appender.A1.File=${jboss.server.log.dir}/my-application.log
${jboss.server.log.dir} rappresenta una variabile globale che punta alla cartella LOG di JBOSS.

Tutto questo per configurare l'ambiente per log4j.

Vediamo ora cosa si deve fare in una classe java per scrivere sul file di log.
Come prima cosa, si devono importare le classi per il logging con log4j:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Nella classe bisogna dichiarare la variabile:

private static final Log LOG = LogFactory.getLog(myClass.class);

Dichiarando la variabile come sopra si usa il rootLogger per scrivere sul file di log.
Una alternativa è la seguente:

private static final Log LOG = LogFactory.getLog("MyLogging");

In questa maniera si invoca la scrittura sul file di log associato alla categoria chiamata MyLogging.
E' questa una soluzione utilissima per avere più file di log.

Infine per scrivere sul log è sufficiente fare:

LOG.debug("... my message ... "); --> per scrivere con livello DEBUG
LOG.error("... my message ... "); --> per scrivere con livello ERROR

Log4j ha una funzione per ogni livello di logging!

Nel caso vogliate loggare una eccezione io consiglio:
try{
...
}catch(Exception e) { LOG.error(e.getMessage(), e); }

Se avete più applicazioni Java - Spring, ognuna delle quali ha il suo file di log io consiglio il seguente file di configurazione:

log4j.rootCategory=DEBUG

# Categoria MyLogging
log4j.category.MyLogging=DEBUG, MYLOG
log4j.appender.MYLOG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MYLOG.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.MYLOG.File=${jboss.server.log.dir}/MyLogging.log
log4j.appender.MYLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.MYLOG.layout.ConversionPattern=%d %-5p [%c] %m%n
log4j.additivity.MyLogging=false
log4j.additivity.MYLOG=false

Al di là delle configurazioni spicciole, dovete far attenzione al significato delle configurazioni seguenti:

log4j.additivity.MyLogging=false
log4j.additivity.MYLOG=false

Questa configurazione evita la scrittura dello stesso msg su più appender perché blocca l'ereditarietà degli appender.

SCHEDULING IN SPRING

Vediamo prima una immagine esplicativa:

 JOB-SPRING-QUARTZ

I JOB sono oggetti che fanno un qualcosa.

Ogni JOB ha un metodo execute() che viene invocato quando il JOB parte.

Al metodo execute viene passato il parametro JobExecutionContext che contiene alcuni dati utili al JOB.

Se si vogliono passare dei dati al JOB, questi vanno messi in un JobDataMap dentro un JobDetails che viene inglobato e letto tramite

il JobExecutionContext.

Quindi un JOBDETAIL serve a dichiarare un JOB e a passargli i dati.

Un JOB viene fatto partire da un TRIGGER.

Un SchedulerFactory serve ad avere una istanza di scheduler tramite la quale registrare JOBs e TRIGGERs.

Abbiamo detto che i TRIGGER servono a far partire i JOB. Abbiamo vari tipi di TRIGGER come i SimpleTrigger come:

<bean id="idMioTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">

  <property name="jobDetail" ref="verificaDatiJobDetail" />

  <property name="startDelay" value="20" />

  <property name="repeatInterval" value="10000" />

</bean>

jobDetail: indica il JOB da invocare

startDelay: il delay alla partenza del servizio

repeatInterval: ogni quanti millisecondi deve partire.

Questo tipo di trigger farà ripartire il JOB ogni "repeatInterval" millisecondi.

Un trigger più flessibile è cronTrigger:

<bean id="idMioTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

  <property name="jobDetail" ref="verificaDatiJobDetail" />

  <!-- run every morning at 6 AM -->

  <!-- secondi[0-59] minuti[0-59] ore[0-23] giorno[1-31] mese[1-12] giornoSettimana[1-7] anno[non obbligatorio 1970-2099] -->

  <property name="cronExpression" value="0 0 16 * * ?" />

</bean>

Con questo trigger si può far partire il JOB all'orario e nei giorni che si vuole.

Come passare i dati al JOB:

<bean name="verificaDatiJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass" value="it.miopkg.verificaDatiJobDetail" />

<property name="jobDataAsMap">

<map>

<entry key="message" value="5" />

<entry key="mioBean">

<ref bean="mioBean"/>

</entry>

</map>

</property>

</bean>     

Come deve essere fatto il nostro JOB?

Qualcosa di simile a questo:

public class verificaDatiJobDetail extends QuartzJobBean implements StatefulJob { .... }

Implementando l'interfaccia StatefulJob, i job NON vengono eseguiti in concorrenza.

Ma come si fa a testare un job?

Potete usare una classe come la seguente:


public class TestJob {

    public static void main(String[] args) {       
        LookupFactory.getInstance();
    }
}

Così facendo si carica il bean factory (o l'application context) che farà partire il Quartz Scheduler e di conseguenza il nostro JOB.

Quartz può anche essere configurato per essere eseguito in un ambiente clustered.

Leggete questo articolo per avere una idea sulle cose da fare.

SPRING E LE LABEL CON PARAMETRI

Una situazione tipica è la seguente.

Vogliamo visualizzare su una determinata pagina web la seguente frase:

"L'utente Mario Rossi è loggato nel sistema."

Tipicamente queste frasi vengono memorizzate in opportuni file properties utilizzati poi dai moderni sistemi di traduzione.

In questa frase il nome e cognome dell'utente è un dato variabile.

Quindi, nel file delle label, si dovrebbe fare

msg1=L'utente

msg2=è loggato nel sistema.

E poi nella JSP concatenare il tutto con il dato variabile.

In Spring esiste la seguente e più elegante soluzione.

Nel file delle label si mette:

msg=L'utente {0} è loggato nel sistema.

Notare che al posto del dato variabile si deve mettere una sorta di segnaposto, nel nostro caso {0}.

Questo valore, {0}, sarà sostituito a runtime dal primo parametro passato.

Nella JSP quindi si avrà:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>

[...]

<spring:message code="pmsg" arguments="${nomeUtente}, ${conomeUtente}"/>

Sto supponendo che sempre nella JSP sia visibile la variabile nomeUtente.

Se invece della JSP abbiamo una classe java:

import org.springframework.context.support.MessageSourceAccessor;

[...]

protected MessageSourceAccessor messages;

[...]

String[] args = new String[]{nomeUtente};

String risultato = messages.getMessage("msg", args);

Ultima nota.

Se nel file delle label scrivete:

msg=L'utente {0} è loggato nel sistema.

non va bene.

Dovete correggere in:

msg=L''utente {0} è loggato nel sistema.

L'apice deve essere trasformato in doppio apice altrimenti avrete un errore in fase di sostituzione degli argomenti nel messaggio.

Link Utili

Di seguito alcuni link utilissimi di articoli trovati su Mokabyte. Sono articoli, scritti in italiano, che introducono benissimo il concetto di programmazione in Spring.

1) Spring - I PARTE: introduzione al framework

2) Spring - II PARTE: il core

3) Spring - III PARTE: Il supporto di Spring per l'Aspect Oriented Programming

4) Spring - IV PARTE: Accesso ai dati

5) Spring - V PARTE: Web MVC

6) Spring - VI PARTE: Portlet MVC