Validazione con Java puro

L'idea di proprietà vincolate in Java non è nuova. A partire da JDK 1.1, una legione di sviluppatori JavaBeans ha utilizzato un potente framework trovato nel java.beanspacchetto per applicare le regole di convalida dei dati. Sfortunatamente, il resto di noi - che non ha il compito di creare "componenti software riutilizzabili che possono essere manipolati visivamente in uno strumento di creazione" - abbastanza spesso tentano di reinventare la ruota abbandonando l'approccio core Java a favore di vari proprietari soluzioni. L'esternalizzazione delle regole di convalida dei dati è l'area in cui è possibile vedere la massima creatività. Un interessante approccio basato su XML è stato recentemente proposto nella serie "Validation with Java and XML Schema" di JavaWorld. Pur ammirando la tecnologia XML, credo che Java abbia tutto il necessario per risolvere il problema nel modo più elegante. Per mostrarvelo vi invito a riscoprire alcune delle vere chicche trovate nella java.beansconfezione. Ti aiuteranno a costruire alcune lezioni utili e ad apprendere un paio di trucchi interessanti.

La logica alla base delle proprietà vincolate in Java è abbastanza semplice. Prima di accettare un nuovo valore di dati, l'oggetto (proprietario della proprietà vincolata) si assicura che sia accettato da tutte le parti interessate che possono porre il veto. Tale "richiesta di approvazione" viene consegnata a ogni registrato java.beans.VetoableChangeListenersotto forma di java.beans.PropertyChangeEventoggetto. Se sono stati emessi uno o più veti, il valore dei dati proposto viene rifiutato. Un veto è presentato da a java.beans.PropertyVetoException. In generale, la complessità delle regole imposte da quegli ascoltatori non ha limiti e dipende solo dalle esigenze del progetto e dalla creatività dello sviluppatore. L'obiettivo di questo esercizio è imparare a gestire i criteri di convalida dei dati più generici che è possibile applicare alla maggior parte degli oggetti.

Per prima cosa, creiamo una classe chiamata BusinessObjectcon una semplice proprietà numerica. Successivamente, mostrerò come modificare la proprietà in una vincolata per vedere come differisce da quella semplice.

public class BusinessObject {private int numericValue; public void setNumericValue (int newNumericValue) {numericValue = newNumericValue; } public int getNumericValue () {return numericValue; }}

Si noti che l'unica proprietà di questa classe accetterà silenziosamente qualsiasi valore del tipo appropriato. Per renderlo vincolato, è necessario produrre e distribuire ai consumatori un evento con diritto di veto. Una classe di utilità chiamata è java.beans.VetoableChangeSupportstata progettata specificamente per soddisfare questa serie di responsabilità. Una nuova classe chiamata ConstrainedObjectgestirà tutti gli accordi con questa classe di utilità, diventando così un buon genitore amorevole per il BusinessObject. Parlando di consumatori, dobbiamo creare un'altra classe personalizzata chiamataValidatorche ospita algoritmi di convalida dei dati generici. Sarà l'unico consumatore degli eventi soggetti al veto nel nostro quadro. Questo approccio semplificato devia dalle regole del gioco JavaBeans (controllare le specifiche API JavaBeans per i dettagli), che richiede la presentazione di ogni proprietà vincolata come vincolata e il supporto per la gestione di più listener. Questa deviazione è perfettamente accettabile, poiché non stai creando un JavaBean qui, ma vale comunque la pena menzionarla.

importa java.beans. *; / ** * La responsabilità del ConstrainedObject è delegare le responsabilità di convalida dell'inserimento di dati generici * al {@link Validator} e fornire * sottoclassi con un'interfaccia trasparente ad esso. * / public class ConstrainedObject {private VetoableChangeSupport vetoableSupport = new VetoableChangeSupport (this); / ** * Crea un nuovo oggetto con un validatore di proprietà generico * / public ConstrainedObject () {vetoableSupport.addVetoableChangeListener (new Validator ()); } / ** * Questo metodo verrà utilizzato dalle sottoclassi per convalidare un nuovo valore * nelle proprietà vincolate del tipo int. Può essere facilmente sovraccaricato * per gestire anche altri tipi primitivi. * @param propertyName Il nome programmatico della proprietà che sta per cambiare. * @param oldValue Il vecchio valore della proprietà.* @param newValue Il nuovo valore della proprietà. * / protected void validate (String propertyName, int oldValue, int newValue) restituisce PropertyVetoException {vetoableSupport.fireVetoableChange (propertyName, new Integer (oldValue), new Integer (newValue)); }}

Per mantenere il codice compatto, ConstrainedObjectfornisce un metodo che convalida intsolo le proprietà. Puoi facilmente sovraccaricare questo metodo anche per gli altri tipi. Se lo fai, assicurati di avvolgere tutti i tipi primitivi nei rispettivi wrapper poiché PropertyChangeEventfornisce valori vecchi e nuovi come tipi di riferimento. Ora sei pronto per emettere una seconda revisione di BusinessObject:

importa java.beans. *; / ** * Questa classe di esempio utilizza effettivamente i servizi di convalida dei dati definiti in questo framework. * / public class BusinessObject estende ConstrainedObject {private int numericValue; public void setNumericValue (int newNumericValue) genera PropertyVetoException {// convalida il valore proposto validate ("numericValue", numericValue, newNumericValue); // il nuovo valore è approvato, poiché non sono state generate eccezioni numericValue = newNumericValue; } public int getNumericValue () {return numericValue; }}

Come vedi, il metodo setter ora sembra un po 'più complicato di prima. È un piccolo prezzo da pagare per avere una proprietà vincolata. Ora devi costruire un Validator, che è la parte più interessante di questo esercizio. Qui è dove impari come esternalizzare le regole di convalida dei dati utilizzando nient'altro che Java puro.

importa java.beans. *; public class Validator implementa VetoableChangeListener {public void vetoableChange (PropertyChangeEvent evt) throws PropertyVetoException {// do the validation here}}

Lo scheletro del codice è solo un punto di partenza, ma dimostra ciò che è disponibile all'inizio del processo di convalida. Sorprendentemente, contiene molte informazioni. Da PropertyChangeEventè possibile conoscere l'origine dell'oggetto della proprietà vincolata, il nome della proprietà stessa e i suoi valori esistenti e proposti. Con queste informazioni a portata di mano, puoi utilizzare il potere dell'introspezione per chiedere informazioni aggiuntive sulla proprietà. Quali informazioni dovresti cercare? Regole di convalida, ovviamente! Una classe è java.beans.Introspectorstata progettata esclusivamente allo scopo di raccogliere informazioni aggiuntive su una classe target. Le informazioni sono fornite sotto forma di un java.beans.BeanInfooggetto, che in questo esempio puoi richiedere con una semplice chiamata come questa:

BeanInfo info = Introspector.getBeanInfo (evt.getSource (). GetClass ()); 

La si Introspectorpuò raccogliere BeanInfoin due modi. Innanzitutto cerca di ottenere informazioni esplicite sulla classe di destinazione cercando una classe con lo stesso nome tranne il suo BeanInfosuffisso. In questo caso, cercherà una classe denominata BusinessObjectBeanInfo. Se per qualsiasi motivo tale classe non è disponibile, allora fa del Introspectorsuo meglio per costruire dinamicamente un BeanInfooggetto utilizzando la riflessione di basso livello.

Sebbene sia un argomento affascinante, l'introspezione implicita ovviamente non è ciò su cui fare affidamento nella ricerca delle regole di convalida. Una cosa grandiosa della BeanInfoclasse è la sua relazione con la classe target, che può essere definita come un "accoppiamento sciolto solido come una roccia". L'assenza di riferimenti dalla classe target rende l'accoppiamento molto lento. Se le regole di convalida cambiano, è sufficiente aggiornare e ricompilare la BeanInfoclasse senza possibilmente influenzare la classe di destinazione. Le regole di denominazione chiaramente definite e documentate nell'API Java principale rendono la compilazione solida come una roccia. È qui che l'approccio di esternalizzazione delle regole BeanInfobatte le sue controparti implementate in modo proprietario.

Prima di iniziare a costruire la BeanInfoclasse esplicita , devo menzionare che puoi caricarla con una varietà di descrittori riguardanti la classe stessa, oi suoi metodi ed eventi, non solo le proprietà. Grazie ai creatori di Java, non devi fornire tutte queste informazioni. La Introspectorraccoglie ciò che è disponibile in modo esplicito e quindi esegue la sua magia analisi implicita. Pertanto, è possibile concentrarsi solo sui descrittori di proprietà.

Per descrivere le regole di convalida dei dati di una proprietà, si utilizza la java.beans.PropertyDescriptorclasse. Guardando la documentazione, scoprirai che questa classe ha le strutture per fornire ogni informazione immaginabile sulla proprietà, comprese le regole di convalida dei dati! Il setValuemetodo viene utilizzato per definire le regole come un insieme di attributi denominati. Ecco come puoi vincolare i limiti per la proprietà numerica numericValue:

PropertyDescriptor _numericValue = new PropertyDescriptor ("numericValue", targetClass, "getNumericValue", "setNumericValue"); _numericValue.setValue ("maxVal", new Integer (100)); _numericValue.setValue ("minVal", new Integer (-100));

L'unico punto debole qui è che non puoi fare affidamento sulla potenza del compilatore Java per controllare l'ortografia dei nomi degli attributi. L'uso di un insieme predefinito di costanti può facilmente risolverlo. La classe Validatorè un buon candidato per ospitare tali costanti:

stringa finale statica pubblica MAX_VALUE = "maxValue"; stringa finale statica pubblica MIN_VALUE = "minValue";

Quindi ora puoi riscrivere le stesse regole in modo più sicuro. Con tutto questo in mente, costruiamo una revisione finale della BeanInfoclasse:

importa java.beans. *; public class BusinessObjectBeanInfo estende SimpleBeanInfo {Class targetClass = BusinessObject.class; public PropertyDescriptor [] getPropertyDescriptors () {try {PropertyDescriptor numericValue = new PropertyDescriptor ("numericValue", targetClass, "getNumericValue", "setNumericValue"); numericValue.setValue (Validator.MAX_VALUE, new Integer (100)); numericValue.setValue (Validator.MIN_VALUE, new Integer (-100)); PropertyDescriptor [] pds = new PropertyDescriptor [] {numericValue}; return pds; } catch (IntrospectionException ex) {ex.printStackTrace (); return null; }}}

Infine, completa il in Validatormodo che possa recuperare e analizzare le informazioni. Utilizzerà un metodo pratico per recuperare un PropertyDescriptordal BeanInfonome programmatico della proprietà. Nel caso in cui la proprietà non sia stata trovata per qualsiasi motivo, il metodo avviserà il chiamante con un'eccezione autoesplicativa.

importa java.beans. *; / ** * Questa classe implementa un meccanismo generico di convalida dei dati basato sulle regole esterne * definite nella classe BeanInfo. * / public class Validator implementa VetoableChangeListener {public static final String MAX_VALUE = "maxValue"; stringa finale statica pubblica MIN_VALUE = "minValue"; / ** * L'unico metodo richiesto dall'interfaccia {@link VetoableChangeListener}. * / public void vetoableChange (PropertyChangeEvent evt) genera PropertyVetoException {try {// qui speriamo di recuperare informazioni aggiuntive esplicitamente definite // sull'origine dell'oggetto della proprietà vincolata BeanInfo info = Introspector.getBeanInfo (evt.getSource (). getClass ()); // trova un descrittore di proprietà in base al nome di proprietà specificato PropertyDescriptor descriptor = getDescriptor (evt.getPropertyName (), info);Numero intero max = (Integer) descriptor.getValue (MAX_VALUE); // controlla se il nuovo valore è maggiore di quello consentito if (max! = null && max.compareTo (evt.getNewValue ()) 0) {// lamentarsi! lancia una nuova PropertyVetoException ("Value" + evt.getNewValue () + "è minore del minimo consentito" + min, evt); }} catch (IntrospectionException ex) {ex.printStackTrace (); }} / ** * Questo metodo di utilità tenta di recuperare un PropertyDescriptor dall'oggetto BeanInfo in base al nome della proprietà * fornita. * @param name il nome programmatico della proprietà * @param info l'oggetto info del bean che verrà cercato per il descrittore di proprietà. * @throws IllegalArgumentException se una proprietà con il nome specificato non esiste nell'oggetto * BeanInfo specificato. * / private PropertyDescriptor getDescriptor (nome stringa,BeanInfo info) genera IllegalArgumentException {PropertyDescriptor [] pds = info.getPropertyDescriptors (); for (int i = 0; i
   
    

Beh, che ci crediate o no, è tutto! Ora hai tutti i pezzi a posto e sei pronto per testare la convalida dei dati. Questo semplice driver fa proprio questo: