Java è un linguaggio di programmazione strong typed, ciò permette di avere più “sicurezza” runtime in quanto tutti gli errori banali vengono evidenziati in fase di compilazione. Usando un ide evoluto (come per esempio Eclipse, Netbeans o IntelliJ) questo aspetto viene sfruttato mettendo a disposizione il supporto per il refactoring e la navigazione nel codice (per esempio per riconoscere tutte le chiamate a un particolare metodo).
L’uso della reflection in Java è in alcuni casi molto comodo (permette di scrivere codice dinamico in modo semplice), ma porta ad alcuni disagi.


In particolare:

  • si perde la sicurezze runtime tipica dei linguaggi strong typed: ci potrebbero essere errori di tipo (per esempio di metodi non trovati) non rilevabili dal compilatore
  • il codice è meno manutenibile: il refactoring degli ide non riconosce le chiamate effetuate tramite reflection

In questo post vedremo un esempio in cui, utilizzando la libreria cglib, si riesce a evitare di usare la reflection pur non perdendo di flessibilità.

Comparator con reflection

Iniziamo vedendo una implementazione di un Comparator che permette di confrontare due oggetti in base ad alcune proprietà di una classe aggiunte dinamicamente:

public class MethodComparator implements Comparator {

	private final LinkedHashMap methods = 
		new LinkedHashMap();

	public void addMethod(Method m, boolean desc) {
		methods.put(m, desc);
	}

	public int compare(T o1, T o2) {
		Set> entrySet = 
			methods.entrySet();
		for (Entry entry : entrySet) {
			Method m = entry.getKey();
			int r = compareValues(
				invoke(m, o1), invoke(m, o2));
			if (entry.getValue()) {
				r = -r;
			}
			if (r != 0) {
				return r;
			}
		}
		return 0;
	}

	private Object invoke(Method m, T obj) {
		try {
			return m.invoke(obj);
		} catch (Exception e) {
			throw new RuntimeException(
				"Error invoking method " + 
				m.getName() + " on object " + obj, e);
		}
	}

	private int compareValues(Object v1, Object v2) {
		int r;
		if (v1 instanceof Comparable) {
			@SuppressWarnings("unchecked")
			Comparable comp = 
				(Comparable) v1;
			r = comp.compareTo(v2);
		} else {
			r = v1.toString().compareTo(v2.toString());
		}
		return r;
	}
}

Un esempio di utilizzo è il seguente (la classe Persona è un bean che contiene le proprietà nome, cognome e eta):

MethodComparator c = new MethodComparator();

c.addMethod(Persona.class.getMethod("getCognome"), false);
c.addMethod(Persona.class.getMethod("getNome"), false);
c.addMethod(Persona.class.getMethod("getEta"), true);

List l = new ArrayList();

l.add(new Persona("Giuseppe", "Bianchi", 50));
l.add(new Persona("Mario", "Rossi", 35));
l.add(new Persona("Giuseppe", "Bianchi", 60));

Collections.sort(l, c);

Chiamando il metodo addMethod si specifica l’ordinamento che si vuole ottenere (in questo caso cognome, nome e eta). Il metodo da invocare è ricavato scrivendo una stringa, quindi se c’è un errore di battitura si otterrà una eccezione runtime. Inoltre anche effettuare un refactoring con un ide (per esempio per cambiare il nome di un metodo get) porterà ad un errore runtime.

Miglioriamo il comparator con reflection

Una prima modifica per semplificare e rendere più leggibile l’utilizzo di questa classe è aggiungere un metodo che accetta il nome del campo da usare in modo da non dover utilizzare ogni volta il metodo getMethod di Class. Per fare questo scriviamo una classe che estende MethodComparator:

public class ReflectionComparator extends MethodComparator {

	private final Class clazz;

	public ReflectionComparator(Class clazz) {
		this.clazz = clazz;
	}

	public void addField(String field, boolean desc) {
		BeanInfo beanInfo = getBeanInfo();
		PropertyDescriptor[] propertyDescriptors = 
			beanInfo.getPropertyDescriptors();
		for (PropertyDescriptor pd : propertyDescriptors) {
			if (pd.getName().equals(field)) {
				addMethod(pd.getReadMethod(), desc);
				return;
			}
		}
		throw new RuntimeException(MessageFormat.format(
			"Field {0} not found in class {1}",
			field, clazz.getName()));
	}

	private BeanInfo getBeanInfo() {
		try {
			return Introspector.getBeanInfo(clazz);
		} catch (IntrospectionException e) {
			throw new RuntimeException(
				MessageFormat.format(
				"Error while introspecting class {0}", 
				clazz.getName()), e);
		}
	}
}

Questa nuova classe è più semplice e intuitiva da utilizzare:

ReflectionComparator c = 
	new ReflectionComparator(Persona.class);

c.addField("cognome", false);
c.addField("nome", false);
c.addField("eta", true);

ma si hanno comunque gli stessi problemi già sottolineati in precedenza.

Aggiungiamo cglib

Iniziamo a usare la cglib: questa libreria è già presente nel classpath di tutte le applicazioni che usano spring, hibernate e molti altri framework Java. Cglib permette di creare dinamicamente proxy di classi aggiungendo un comportamento custom ai metodi della classe. Un esempio presente sul sito di cglib mostra come creare un proxy che stampa un trace dei metodi invocati su un oggetto. Cglib è alla base dell’implementazione di spring dell’aspect oriented (ma di questo parleremo in un altro post).

Vediamo adesso come usare cglib per estrarre un nome di un metodo in modo type safe. Per esempio creiamo un semplice proxy della classe Persona, quando viene chiamato un metodo del proxy viene aggiunto il nome del metodo chiamato in una lista:

final List l = new ArrayList();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Persona.class);
enhancer.setCallback(new MethodInterceptor() {
	public Object intercept(Object obj, Method m, Object[] args, 
			MethodProxy proxy) throws Throwable {
		l.add(m.getName());
		return null;
	}
});
Persona p = (Persona) enhancer.create();
p.getCognome();
p.getNome();
System.out.println(l);

Facendo così siamo riusciti a ottenere il nome di un metodo chiamando il metodo stesso, quindi senza usare la reflection e scrivendo solo codice type safe. Questo esempio può essere generalizzato in una classe che permette di aggiungere il nome di un metodo a una lista passata:

public class FieldExtractor {

	private final Class clazz;

	public FieldExtractor(Class clazz) {
		this.clazz = clazz;
	}

	public T add(final List list) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(new MethodInterceptor() {
			public Object intercept(Object obj, Method m, 
					Object[] args, MethodProxy proxy) 
					throws Throwable {
				String methodName = m.getName();
				String name;
				if (methodName.startsWith("is")) {
					name = methodName.substring(2, 3).toLowerCase()
						+ methodName.substring(3);
				} else {
					name = methodName.substring(3, 4).toLowerCase()
						+ methodName.substring(4);
				}
				list.add(name);
				return null;
			}
		});
		@SuppressWarnings("unchecked")
		T ret = (T) enhancer.create();
		return ret;
	}
}

Un esempio di utilizzo è il seguente:

List l = new ArrayList();

FieldExtractor fieldExtractor = 
	new FieldExtractor(Persona.class);

fieldExtractor.add(l).getNome();
fieldExtractor.add(l).getCognome();
fieldExtractor.add(l).getEta();

System.out.println(l);

Il codice risultante è facilmente leggibile anche se un po’ strano, il metodo add accetta come parametro una lista in cui verrà aggiunto il nome del campo corrispondente al get richiamato sul proxy creato. Il tutto è type safe (non è presente neanche un cast) grazie all’utilizzo dei generics di Java 1.5.

Comparator con cglib

Torniamo a vedere l’esempio iniziale, creiamo un Comparator che sfrutta cglib per ottenere i metodi da chiamare per eseguire i confronti:

public class CglibComparator extends MethodComparator {

	private final Class clazz;

	public CglibComparator(Class clazz) {
		this.clazz = clazz;
	}

	public T asc() {
		return createProxy(false);
	}

	public T desc() {
		return createProxy(true);
	}

	private T createProxy(final boolean desc) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(new MethodInterceptor() {
			public Object intercept(Object obj, Method m,
					Object[] args, MethodProxy proxy)
					throws Throwable {
				addMethod(m, desc);
				return null;
			}
		});
		@SuppressWarnings("unchecked")
		T p = (T) enhancer.create();
		return p;
	}
}

La chiave di tutto sono i metodi asc e desc che ritornano un proxy, chiamando un metodo sull’oggetto ritornato si definiscono i criteri di confronto:

CglibComparator c =
	new CglibComparator(Persona.class);

c.asc().getCognome();
c.asc().getNome();
c.desc().getEta();

List l = new ArrayList();

l.add(new Persona("Giuseppe", "Bianchi", 50));
l.add(new Persona("Mario", "Rossi", 35));
l.add(new Persona("Giuseppe", "Bianchi", 60));

Collections.sort(l, c);

In questo esempio abbiamo ottenuto lo stesso ordinamento scritto in precedenza, ma non abbiamo più possibili problemi runtime: un eventuale errore sul nome di un metodo da usare per il confronto sarebbe evidenziato dal compilatore Java.

Un ultimo ritocco al codice

Passare l’oggetto Class al costruttore del metodo può sembrare superfluo, ma è necessario in quanto i generic a runtime non sono disponibili. In realtà è possibile accedere alla classe impostata come generic nel caso in cui sia presente una sottoclasse che la dichiara in modo esplicito. Scriviamo un nuovo costruttore senza parametri che ricava la classe runtime:

protected CglibComparator() {
	ParameterizedType genericSuperclass =
		(ParameterizedType) getClass().getGenericSuperclass();
	this.clazz = (Class)
		genericSuperclass.getActualTypeArguments()[0];
}

Questo costruttore è definito protected per forzare lo sviluppatore a scrivere una sottoclasse, come già detto senza una sottoclasse questo codice non funziona.

La versione finale dell’esempio è questo:

public class PersonaComparator extends CglibComparator {
	public PersonaComparator() {
		asc().getCognome();
		asc().getNome();
		desc().getEta();
	}
}

Volendo si può creare solo una classe interna anonima invece che la classe PersonaComparator (scommetto un caffè che nessuno riesce a scrivere un Comparator in modo più conciso mantenendo il codice leggibile!):

CglibComparator c = new CglibComparator() {
	{
		asc().getCognome();
		asc().getNome();
		desc().getEta();
	}
};

Conclusioni

L’esempio mostrato in questo post vuole essere uno spunto per usare cglib in modi utili, un Comparator può essere scritto in due minuti anche senza cglib (introdurre questa libreria solo per scrivere un Comparator può essere come sparare a una mosca con un cannone!).

L’idea per questo post mi è venuta vedendo gli esempi di utilizzo di lambdaj, una libreria Java molto utile per manipolare le collection in modo pseudo funzionale.

60 Posts

Software Architect con esperienza su piattaforma J2EE e attualmente focalizzato principalmente in progetti di sviluppo di applicazioni Android. Attualmente sono in Nana Bianca dove mi occupo dello sviluppo di alcune app Android. Coautore della seconda edizione di Android Programmazione Avanzata e docente di corsi di sviluppo su piattaforma Android. Follow me on Twitter - LinkedIn profile