2011-08-15 6 views
15

Tengo un sistema que usa Spring para inyección de dependencia. Yo uso el autoenvío basado en anotaciones. Los granos se descubrieron en los escaneos componente, es decir, mi contexto XML contiene esto:¿Es posible garantizar el orden en que se invocan los métodos de @PostConstruct?

<context:component-scan base-package="org.example"/> 

He creado un ejemplo Noddy a continuación para ilustrar mi problema.

Hay un Zoo que es un contenedor para objetos Animal. El desarrollador de Zoo no sabe qué objetos Animal estarán contenidos mientras desarrolla Zoo; el conjunto de objetos concretos Animal instanciados por Spring es conocido 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 para Zoo no debe cambiar en estas circunstancias.

El propósito de Zoo es para permitir que otras partes del sistema (ilustrado aquí como ZooPatron) para acceder al conjunto de Animal objetos en tiempo de ejecución, sin necesidad de depender explícitamente en ciertos Animal s.

En realidad, las clases de hormigón Animal serán contribuidas por varios artefactos de Maven. Quiero ser capaz de armar una distribución de mi proyecto simplemente dependiendo de los diversos artefactos que contienen estos concretos Animal s, y hacer que todo se autoenlace correctamente en tiempo de compilación.

He intentado resolver este problema (sin éxito) por tener los individuales Animal s dependen de la Zoo, con el fin de que puedan llamar a un método de registro en el Zoo durante @PostConstruct. Esto evita el Zoo dependiendo explícitamente en una lista explícita de Animal s.

El problema con este enfoque es que los clientes de Zoo desean interactuar con él sólo cuando todos los Animal s han registrado. Hay un conjunto finito de Animal s que se conoce en tiempo de compilación, y el registro ocurre durante la fase de cableado de Spring de mi ciclo de vida, por lo que un modelo de suscripción debería ser innecesario (es decir, no deseo agregar Animal s al Zoo en tiempo de ejecución).

Lamentablemente, todos los clientes de Zoo simplemente dependen de Zoo. Esta es exactamente la misma relación que el Animal s con Zoo. Por lo tanto, los métodos @PostConstruct de Animal sy ZooPatron se invocan en una secuencia arbitraria. Esto se ilustra con el código de ejemplo siguiente: en el momento en que se invoca @PostConstruct en ZooPatron, no se ha registrado Animal, algunos milisegundos más tarde cuando se registran todos.

Así que hay dos tipos de dependencia aquí, que estoy luchando para expresar en primavera. Los clientes de Zoo solo quieren usarlo una vez que todos los Animal s 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 se puede garantizar el orden de @PostConstruct llamadas sin bien va Primavera "fuera" o modificar su comportamiento .

El verdadero problema aquí era no que quería para secuenciar los @PostConstruct invocaciones, que no era más que un síntoma de las dependencias que se expresa de forma incorrecta.

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 las subclases Animal, y por lo tanto introduje este método de registro. Como se señala en las respuestas, mezclar un mecanismo de autorregistro con inyección de dependencia nunca funcionará sin una complejidad innecesaria.

La respuesta es declarar que Zoo depende de una de recogida de Animal s, y luego permitir la primavera para poblar la colección a través de auto-cableado.

Por lo tanto, no hay una lista difícil de miembros de la colección, sino Spring los descubre, pero las dependencias se expresan correctamente y, por lo tanto, los métodos @PostConstruct ocurren en la secuencia que deseo.

Gracias por las excelentes respuestas.

+0

Ok, solo lo comprobé muy rápido ... pero creo que Jirafa, mono, león y tiget también deberían tener un @PostConstuct. ¿Y por qué la estática? – Cygnusx1

+0

Esos animales concretos no necesitan PostConstruct, el método PostConstruct de su padre abstracto es invocado correctamente por Spring. –

Respuesta

4

En su lugar, puede tener el conjunto de animales @Inject ed en el zoológico.

@Component 
public class Zoo { 

    @Inject 
    private Set<Animal> animals = new HashSet<Animal>(); 

    // ... 
} 

Entonces Zoológico de @PostConstruct sólo debería existir una vez que se inyectan todos los animales. El único problema es que debe haber al menos un Animal en el sistema, pero no parece que eso sea un problema.

+0

Sí, esta es la mejor idea, como también lo sugirió @ptyx. –

1

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 métodos desde dependencias cuando Aún no estoy seguro de que estén completamente inicializados.

Si solo quita la anotación @PostConstruct del método Test#init(), y simplemente lo invoca desde su método principal, una vez que se haya creado el contexto, ya no tendrá este problema.

+0

Quizás mi código de ejemplo fue demasiado simplificado; en realidad, mi aplicación es una aplicación web, así que tengo un XmlWebApplicationContext, cortesía de un DispatcherServlet. Entonces, no estoy seguro de tener esa oportunidad. –

+0

Aún puede inicializar su clase de Prueba (o cualquiera que sea su nombre real) de forma perezosa (la primera vez que se llama a uno de sus métodos, o la primera vez que se accede a través de una fábrica), o utilizando un ServletContextListener. –

0

Me parece que en su caso existe una dependencia entre el objeto Zoo y todos sus tipos de animales. Si diseña su objeto Zoo para reflejar esta dependencia, el problema está resuelto. Por ejemplo, usted podría 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.

+0

Ah, no, eso es exactamente lo que trato de evitar :-) Ni el ZooPatron ni el Zoo quieren tener una lista "dura" de animales. La situación del mundo real es que tengo un montón de * juegos * que son aportados por varios artefactos de Maven, y un * registro de juegos * en el "núcleo" del sistema. Solo el registro del juego será visible para el resto del sistema, los juegos individuales no son conocidos por el desarrollador que consume el registro del juego, pero Spring los conoce en el momento de la compilación cuando se ensambla una "distribución" de los diversos juegos y otros componentes. Editaré la q para aclarar. –

+0

Tienes razón: en el dominio del problema, el zoológico depende de los animales. Expresarlo de otra manera lleva a problemas. Mi estupidez consistía en asumir que tenía que hacer una lista explícita de animales, cuando Spring puede inyectarme una colección. –

2

No creo que haya una forma de garantizar el orden de @PostConstruct sin introducir dependencias.

Creo que está buscando problemas para mezclar la inyección o el auto registro. Hasta cierto punto, el orden de las llamadas @PostConstruct no debería importar; si lo hace, puede que no sea la herramienta adecuada para el trabajo.

Un par de ideas para su ejemplo

  • tratar de tener un @Autowired en zoológico # animales: no es necesario para la auto-registro de los animales, el zoológico también es consciente de los animales, pero no a la inversa, lo cual se siente más limpio
  • mantenga el registro, pero deje que los actores externos realicen el registro (alguien está poniendo los animales en el zoológico, ¿no? - no se muestran en la entrada por sí mismos)
  • si necesita insertar nuevos animales en cualquier momento, pero no quieren inserción manual, hagan un acceso más dinámico en el zoológico: no almacene la lista de animales, pero use el contexto de primavera para obtener todas las instancias existentes de la interfaz.

No creo que haya una respuesta "correcta", todo depende de su caso de uso.

+0

Creo que has dado en el clavo: no puedo encontrar la manera de hacerlo funcionar sin distorsionar el propósito de PostConstruct. La raíz del problema es que no quiero que nadie se dé cuenta de animales en particular (lo cual probablemente tiene que ver más con mi arquitectura de construcción que con cualquier otra cosa); No puedo tener cambios de código si cambio el conjunto de animales. Tener un animal en la ruta de clase debería ser suficiente para que se conecte al sistema. Sin embargo, otras personas necesitan saber el conjunto total de dichos animales en tiempo de ejecución. –

+1

Si sus animales son frijoles, use una colección autocableada. Spring colocará automáticamente todos los beans declarados en un mapa Autowired Map animals; - No es necesario que los liste uno por uno en el Zoo Bean. – ptyx

+0

Sí, una colección autoconectada es la mejor solución. Una vez que las dependencias se expresan correctamente, la secuencia de invocaciones PostConstruct no es importante. –

2

Modifique de nuevo el problema para que no dependa del orden de invocación.

+0

Sí. Ahora veo que las dependencias tal como se expresan en el código son de ida y vuelta; solucionar eso significa que no tengo que confiar en el orden de invocación. –

Cuestiones relacionadas