Tengo una aplicación web de Primavera de arranque que está funcionando correctamente. Me di cuenta de que dos @Repository
granos tenía mucho en común, por lo que les refactorizado utilizando una superclase abstracta y ahora mi solicitud está roto. Tengo una doble comprobación y este es el único cambio que he hecho entre el trabajo y los estados que no trabajan. ¿Puede alguien ver lo que he hecho mal?
Aquí está mi código de trabajo:
public class One { ... }
public class Two { ... }
@Repository
public class RepoOne {
private final ISource<One> sourceOne;
private ICache<One> cache;
@Value("${com.example.lifetime.one}")
private int lifetime;
public RepoOne(ISource<One> sourceOne) {
this.sourceOne = sourceOne;
}
@PostConstruct
public void createCache() {
Duration lifetime = Duration.ofMinutes(this.lifetime);
this.cache = new Cache<>(lifetime, sourceOne);
}
public One get(String key) {
return cache.get(key);
}
}
@Repository
public class RepoTwo {
private final ISource<Two> sourceTwo;
private ICache<Two> cache;
@Value("${com.example.lifetime.two}")
private int lifetime;
public RepoOne(ISource<Two> sourceTwo) {
this.sourceTwo = sourceTwo;
}
@PostConstruct
public void createCache() {
Duration lifetime = Duration.ofMinutes(this.lifetime);
this.cache = new Cache<>(lifetime, sourceTwo);
}
public Two get(String key) {
return cache.get(key);
}
}
@Service
public class RepoService {
private final RepoOne repoOne;
private final RepoTwo repoTwo;
public RepoService(RepoOne repoOne, RepoTwo repoTwo) {
this.repoOne = repoOne;
this.repoTwo = repoTwo;
}
public void doSomething(String key) {
One one = repoOne.get(key);
...
}
}
Aquí está mi re-factorizado código donde presenté una, genérica super-clase abstracta.
abstract class AbstractRepo<T> {
private final ISource<T> source;
private ICache<T> cache;
AbstractRepo (ISource<T> source) {
this.source = source;
}
@PostConstruct
private void createCache() {
Duration lifetime = Duration.ofMinutes(lifetime());
this.cache = new Cache<>(lifetime, source);
}
protected abstract int lifetime();
public final T get(String key) {
return cache.get(key);
}
}
@Repository
public class RepoOne extends AbstractRepo<One> {
@Value("${com.example.lifetime.one}")
private int lifetime;
public RepoOne(ISource<One> sourceOne) {
super(source);
}
protected int lifetime() { return lifetime; }
}
@Repository
public class RepoTwo extends AbstractRepo<Two> {
@Value("${com.example.lifetime.two}")
private int lifetime;
public RepoTwo(ISource<Two> sourceTwo) {
super(source);
}
protected int lifetime() { return lifetime; }
}
Cuando se utiliza el código de re-factorizada consigo una NullPointerException
en AbstractRepo::get()
. He confirmado a través del depurador que cache
es null
(junto con source
). Sin embargo, también confirmó a través del depurador que los casos de RepoOne y RepoTwo se crean y su createCache()
método llamado. Es como si se crean dos instancias de cada uno y sólo uno es inicializado. ¿Alguna idea?
No es el hecho de que usted introdujo una clase padre, pero el hecho de que se activó el get
método en un final
método.
Una clase anotado con @Repository
obtendrá traducción automática es una excepción. Se añade esta traducción automática excepción mediante el uso de AOP. El mecanismo por defecto para aplicar AOP en la primavera es a proxies de uso y en este caso un proxy basado en la clase.
Lo que pasa es que CGLIB crea un proxy para sus clases subclasificando, de modo que cuando se llama a un método de un consejo puede ser añadido. Sin embargo, un final
método no puede ser anulado en una subclase. Lo que conducirá a la get
método que se llama en el proxy en lugar de la instancia real.
Hay 2 maneras de fijar este
- Quitar la
final
palabra clave - Introducir una interfaz que define el contrato para sus repositorios. Esto dará lugar a un proxy dinámico JDK se está creando. JDK dinámicos La representación se basan interfaz y no necesita subclase de su clase real (que es sólo para los proxies basados en la clase).