java - entitymanagerfactory - ¿Es posible garantizar el orden en que se invocan los métodos @PostConstruct?
entitymanager jpa (5)
En su lugar, podría tener el Juego de Animales @Inject
ed en el zoológico.
@Component
public class Zoo {
@Inject
private Set<Animal> animals = new HashSet<Animal>();
// ...
}
Luego, se debe @PostConstruct
de Zoo una vez que se hayan inyectado todos los animales. El único problema es que debe haber al menos un Animal en el sistema, pero no parece que eso deba ser un problema.
Tengo un sistema que utiliza Spring para inyección de dependencia. Yo uso autowiring basado en anotaciones. Los beans se descubren mediante la exploración de componentes, es decir, mi contexto XML contiene esto:
<context:component-scan base-package="org.example"/>
He creado un ejemplo de noddy a continuación para ilustrar mi problema.
Hay un Zoo
que es un contenedor para objetos de Animal
. El desarrollador de Zoo
no sabe qué objetos de Animal
se contendrán mientras él está desarrollando Zoo
; el conjunto de objetos de Animal
de hormigón ejemplificados por Spring se conoce en tiempo de compilación, pero hay varios perfiles de compilación que dan como resultado varios conjuntos de Animal
s, y el código de Zoo
no debe cambiar en estas circunstancias.
El propósito de Zoo
es permitir que otras partes del sistema (ilustradas aquí como ZooPatron
) accedan al conjunto de objetos Animal
en tiempo de ejecución, sin necesidad de depender explícitamente de ciertos Animal
.
En realidad, las clases concretas de Animal
serán aportadas por varios artefactos de Maven. Quiero poder ensamblar una distribución de mi proyecto simplemente dependiendo de los diversos artefactos que contienen estos Animal
hormigón, y tener todo correctamente instalado en el momento de la compilación.
He intentado resolver este problema (sin éxito) haciendo que los Animal
individuales dependan del Zoo
, para que puedan llamar a un método de registro en el Zoo
durante @PostConstruct
. Esto evita que el Zoo
dependa explícitamente de una lista explícita de Animal
.
El problema con este enfoque es que los clientes de Zoo
desean interactuar con él solo cuando todos los Animal
hayan registrado . Hay un conjunto finito de Animal
s que se conoce en tiempo de compilación, y el registro se realiza durante la fase de cableado de Spring de mi ciclo de vida, por lo que un modelo de suscripción no es necesario (es decir, no deseo agregar Animal
s al Zoo
en tiempo de ejecución).
Desafortunadamente, todos los clientes de Zoo
simplemente dependen de Zoo
. Esta es exactamente la misma relación que los Animal
tienen con Zoo
. Por lo tanto, los métodos @PostConstruct
de Animal
s y ZooPatron
se llaman en una secuencia arbitraria. Esto se ilustra con el código de ejemplo a continuación: en el momento en que se invoca ZooPatron
en ZooPatron
, no se ha registrado ningún Animal
, es decir, unos milisegundos más tarde cuando todos se registran.
Entonces hay dos tipos de dependencia aquí, que estoy luchando por expresar en la primavera. Los clientes de Zoo
solo quieren usarlo una vez que todos los Animal
estén en él. (Quizás "Ark" hubiera sido un mejor ejemplo ...)
Mi pregunta es básicamente: ¿cuál es la mejor manera de resolver este problema?
@Component
public class Zoo {
private Set<Animal> animals = new HashSet<Animal>();
public void register(Animal animal) {
animals.add(animal);
}
public Collection<Animal> getAnimals() {
return animals;
}
}
public abstract class Animal {
@Autowired
private Zoo zoo;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
zoo.register(this);
}
@Component
public static class Giraffe extends Animal {
}
@Component
public static class Monkey extends Animal {
}
@Component
public static class Lion extends Animal {
}
@Component
public static class Tiger extends Animal {
}
}
public class ZooPatron {
public ZooPatron(Zoo zoo) {
System.out.println("There are " + zoo.getAnimals().size()
+ " different animals.");
}
}
@Component
public class Test {
@Autowired
private Zoo zoo;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
new Thread(new Runnable() {
private static final int ITERATIONS = 10;
private static final int DELAY = 5;
@Override
public void run() {
for (int i = 0; i<ITERATIONS; i++) {
new ZooPatron(zoo);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
// nop
}
}
}
}).start();
}
}
public class Main {
public static void main(String... args) {
new ClassPathXmlApplicationContext("/context.xml");
}
}
Salida:
There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc
Explicación de la solución aceptada.
Básicamente, la respuesta es: no, no puede garantizar el orden de @PostConstruct
llamadas de @PostConstruct
sin salir "fuera" de Spring o modificar su comportamiento.
El problema real aquí no era que quisiera secuenciar las invocaciones de @PostConstruct
, eso era simplemente un síntoma de que las dependencias se expresaban incorrectamente.
Si los consumidores de Zoo
dependen de él, y Zoo
a su vez depende de Animal
s, todo funciona correctamente. Mi error fue que no quería que Zoo
dependiera de una lista explícita de subclases de Animal
, por lo que introduje este método de registro. Como se señala en las respuestas, la combinación de un mecanismo de registro automático con la inyección de dependencia nunca funcionará sin una complejidad innecesaria.
La respuesta es declarar que Zoo
depende de una colección de Animal
, luego permitir que Spring complete la colección a través del cableado automático.
Por lo tanto, no hay una lista @PostConstruct
de miembros de la colección, los descubrió Spring, pero las dependencias se expresan correctamente y, por lo tanto, los métodos @PostConstruct
en la secuencia que quiero.
Gracias por las excelentes respuestas.
La mejor manera, IMO, es evitar hacer demasiado trabajo durante la construcción del gráfico de objetos (al igual que en Java, evita hacer demasiado trabajo en el constructor) y evitar llamar a los métodos desde dependencias cuando no esté seguro están completamente inicializados todavía.
Si simplemente elimina la anotación @PostConstruct del método Test#init()
, y simplemente la invoca desde su método principal, una vez que se haya creado el contexto, ya no tendrá este problema.
Me parece que en tu caso hay una dependencia entre el objeto Zoo y todos tus tipos de animales. Si diseña su objeto Zoo para reflejar esta dependencia, el problema se resuelve. Por ejemplo podrías hacer:
<bean id="zoo" class="Zoo">
<property name="animals">
<list>
<ref bean="Monkey" />
<ref bean="Tiger" />
<ref bean="Lion" />
</list>
</property>
</bean>
En lugar de utilizar el método de registro.
No creo que haya una forma de asegurar el orden de @PostConstruct sin introducir dependencias.
Creo que estás buscando problemas para mezclar la inyección o el registro automático. Hasta cierto punto, el orden de llamadas de @PostConstruct no debería importar; si lo hace, puede que no sea la herramienta adecuada para el trabajo.
Un par de ideas para tu ejemplo.
- intente tener un @Autowired en Zoo # animales: no es necesario que los animales se registren a sí mismos, también el zoológico es consciente de los animales pero no al revés, lo que se siente más limpio
- mantén el registro, pero deja que los actores externos hagan el registro (alguien está colocando a los animales en el zoológico, ¿no? No están apareciendo en la entrada por sí mismos)
- Si necesita insertar nuevos animales en cualquier momento, pero no desea la inserción manual, haga un acceso más dinámico en el zoológico: no almacene la lista de animales, sino use el contexto Spring para obtener todas las instancias existentes de la interfaz.
No creo que haya una respuesta "correcta", todo depende de su caso de uso.
Replantea tu problema para que no se base en el orden de invocación.