Ostatnio prace nad pewną aplikacją w końcu wyszły na prostą i można w miarę normalny sposób odnaleźć się w kodzie, ale żeby nie było zbyt prosto uciążliwe do znalezienia błedy jeszcze sie zdarzają. Ostatnio zdarzyło mi się poświęcić 3 godziny na znalezienie jednego – niby wszystko działa cacy a program nic nie wypluwa 🙁 Zaczęło się żmudne przeszukiwanie, porównywanie wersji i ostatnich zmian, aż nagle okazało się, że w pewnej strukturze 3-poziomowej na najniższym poziomie znajdują się elementy zawierające referencję do kolekcji, której zadaniem jest przechowywanie pewnych id’ków i udostępniać je dla poziomu 1-go. Wszystko ładnie i pięknie, ale podczas prac, ktoś wpadł na pomysł, że podczas czyszczenia elementu, korzystając z metody clean metody clear, wyczyści także i tę kolekcję, co niestety powodowało brak danych 2 pooziomy wyżej, a to już niestety skutki miało opłakane.

Zacząłem się jednak zastanawiać co zrobić, żeby uniknąć tego typu „niespodzianki” w przyszłośći, gdyż jestem pewien, że za miesiąc, no góra 2, nikt z zespołu włącznie ze mną nie będzie pamiętał dlaczego akurat ta „wyjątkowa” kolekcja nie jest czyszczona i znowu doda wywołanie „clean”‚a „clear”‚a, bo przecież wszystko trzeba wyczyścić.

Pierwszy pomysł jaki przyszedł mi do głowy to udostępnić każdemu elementowi na najniższym poziomie swoją kopię tejże kolekcji. Jednak szybko go odrzuciłem, bo jak sobie pomyślałem o synchronizacji wszystkich tych małych kolekcji z główną, to aż mnie głowa rozbolała.

No dobrze, a co gdyby tak zablokować dostęp do metody clean w kolekcji na najniższym poziomie, ew jakoś tak ją opakować, żeby „clean” „clear” był niedostępny – łatwo powiedzieć ale to oznaczało, ze trzeba zaimplementować cały interfejs, jedynie po to, żeby z metody clean clear wyrzucić UnsupportedOperationException a resztę wywołań przekazać do obiektu zawiniętego, ot takie sobie proxy…

Jednak tutaj Java przychodzi z pomocą, bo oddaje do dyspozycji programiście dynamiczne proxy (jeżeli ktoś się w tym momencie zastanawia nad wyjdajnościa, to przejdę do tego za chwilę). Sprawa jest banalnie prosta:

  1. Implementujemy interfejs InvocationTarget
  2. Sprawdzamy nazwę metody.
  3. Jeżeli nazwa metody to ‚clear’ wyrzucamy wyjątek.
  4. Jeżeli to inna nazwa, to przekazujemy wywołanie dalej.

Prościzna – zamiast przepisywania 10-20 metod (zależnie od interfejsu), mamy tylko jedną, która robi co od niej chcemy. Reszta kodu jest zupełnie nieświadoma, że coś wogóle zaszło po drodze, a jak trzeba sypie wyjątkiem po oczach aż miło 🙂

Zanim przejde do przykładu, to chciałbym wrócić na chwilę do problemu z wydajnością – otóż dynamic proxy jest realizowane przez refleksję i o ile w Javie do wersji 1.3 koszt takiego wywołania był spory, bo były one 30-40 razy wolniejsze niż zwykłe wywołanie statycznie skompilowanej metody, to teraz ponoć całe rozwiązanie zostało udoskonalone i działa tylko 2 razy wolniej, co może być dalej za wolno dla niektórych, ale trzeba przyznać, ze postęp jest ogromny – jak na moje potrzeby w zupełności wystarcza.

No dobrze ale przejdzmy do przykładu:

// this code contains a but - as the method is called CLEAR
class StructElement3 {

private class SetNoClear implements java.lang.reflect.InvocationHandler{

private Set<String> proxy;

public SetNoClear(Set<String> proxy) {

this.proxy = proxy;

}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
try {

if( m.getName().equals( "clean" ) ) {

throw new UnsupportedOperationException("Cannot call clear");

}
return m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " +
e.getMessage());
}

}

}

public StructElement3(Set<String> obj){

proxy = Proxy.newInstance( obj.getClass().getClassLoader(), new Class[]{Set.class}, new SetNoClear(obj));

}

}

Jak widać większość wywołań przechodzi bez problemu do Set’a pod spodem poza clean’em, który zawiedzie, a wywołujący od razu będzie wiedział, że tak nie można.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;

class StructElement3 {

	private class SetNoClear implements InvocationHandler {

		private Set<String> obj;

		public SetNoClear(Set<String> proxy) {
			this.obj = proxy;
		}

		public Object invoke(Object proxy, Method m, Object[] args)
				throws Throwable {
			try {
				if (m.getName().equals("clear")) {
					throw new UnsupportedOperationException("Cannot call clear");
				}
				return m.invoke(obj, args);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			} catch (Exception e) {
				throw new RuntimeException("unexpected invocation exception: "
						+ e.getMessage());
			}

		}
	}

	public StructElement3(Set<String> obj) {
		Set<String> proxy = (Set<String>) Proxy.newProxyInstance(
			obj.getClass().getClassLoader(),
			new Class[] { Set.class },
			new SetNoClear(obj)
		);
	}

}

One Comment

  1. 2lips.pl – pasja programowania » Blog Archive » Google collections wrappers/forwardingobjects says:

    […] blog is a contitunation of my previous entry (polish only sorry) in which I presented a solution for hiding certain operations on a collection […]