Problem mam. Mój własny walidator zgodny z JSR-303 wymaga dostępu do usługi zewnętrznej. Oczywiście instancja walidatora jest tworzona przez silnik Bean Validation. Mogę co prawda w metodzie initialize stworzyć ręcznie usługę, ale ja lubię Guice…

Problem z Bean Validation polega na tym, że nie za bardzo idzie wpiąć się do niego w taki sposób by walidatory były tworzone i zarządzane przez kontener DI. Nie wiem czy to jest tylko moje niedopatrzenie wraz z niechęcią do czytania dokumentacji czy też jakaś głębsza myśl związana z całym JSRem, ale generalnie obiekty walidatorów są tworzone poza naszym zasięgiem. Chcąc zatem coś wstrzyknąć do takiego obiektu należy się uzbroić w cierpliwość. Najprostsza metoda polega oczywiście na wywołaniu metody injector.injectMembers(this) w metodzie initialize walidatora. Tyle tylko, że jak mamy wiele walidatorów to zaczyna się robić nieciekawie. Dziedziczenie tu niewiele zmienia, bo i tak musimy zaśmiecać kod, tym razem wywołaniem super.initialize(). Jak się zapomni to dupa blada.
Rozwiązaniem jest użycie programowania aspektowego i wkompilowanie odpowiedniego kawałka kodu. Guice udostępnia oczywiście interfejs AOP, ale…

It is not possible to use method interception on instances that aren’t constructed by Guice.

źródło

czyli jeżeli walidatory nie są tworzone przez Guice to se można do usranej śmierci zwalczać NPE. Z pomocą przychodzi nam w tym przypadku AspectJ. Wystarczy machnąć taki oto prosty aspekcik:

listing 1. Wstrzyknięcie zależności do walidatora

@Aspect
public class ValidationInitAspect {

	@Pointcut("execution( * javax.validation.ConstraintValidator+.initialize(..))")
	public void initialize(){}
	
	@Around("initialize()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("Init");
		App.injector.injectMembers(joinPoint.getThis());
		return joinPoint.proceed();
	}
	
}

e’wiola jak mawiają Francuzi. Co się dzieje? Pointcut wskazuje na metodę initialize wszystkich klas będących ConstraintValidator. Wstrzykujemy oczywiście w miejsce wykonania, a nie wywołania. Jedynym fakupem jest konieczność zapewnienia sobie dostępu do injectora, tu realizowane jest to poprzez zrobienie go publicznym, statycznym polem w klasie głównej aplikacji.

Wiem, że hak, ale działa.