¿Por qué usar @PostConstruct?

294

En un bean gestionado, @PostConstructse llama después del constructor de objetos Java normal.

¿Por qué usaría @PostConstruct para inicializar por bean, en lugar del constructor regular en sí?

ene
fuente
44
Me dio la impresión de que la inyección del constructor generalmente se prefería para permitir las dependencias final. Dado ese patrón, ¿por qué se @PostConstructagrega a J2EE, seguramente deben haber visto otro caso de uso?
mjaggard

Respuestas:

409
  • porque cuando se llama al constructor, el bean aún no se inicializa, es decir, no se inyectan dependencias. En el @PostConstructmétodo, el bean está completamente inicializado y puede usar las dependencias.

  • porque este es el contrato que garantiza que este método se invocará solo una vez en el ciclo de vida del bean. Puede suceder (aunque es poco probable) que un contenedor se instancia varias veces por el contenedor en su funcionamiento interno, pero garantiza que @PostConstructse invocará solo una vez.

Bozho
fuente
17
en caso de que el propio constructor active automáticamente todas las dependencias, el bean también se puede inicializar completamente en el constructor (después de configurar manualmente todos los campos con cableado automático).
Yair
77
¿Cuál es el caso en el que el constructor de un bean se puede llamar más de una vez?
yair
1
Probablemente algo así como "pasivación". Si el contenedor decide almacenar el bean en el almacén de discos y luego restaurarlo desde allí.
Bozho
13
No es tan improbable ver al constructor llamado varias veces. Cuando el contenedor crea una instancia de un proxy, verá que se llama al constructor al menos una vez para el proxy y una vez para el bean real.
marcus
Tenga en cuenta que no se llama a los métodos @PostConstruct cuando la página se carga inmediatamente después de reiniciar el servidor. (Esto podría ser un error de JBoss.)
Dave Jarvis
96

El principal problema es que:

en un constructor, la inyección de las dependencias aún no se ha producido *

* obviamente excluyendo la inyección de constructor


Ejemplo del mundo real:

public class Foo {

    @Inject
    Logger LOG;

    @PostConstruct
    public void fooInit(){
        LOG.info("This will be printed; LOG has already been injected");
    }

    public Foo() {
        LOG.info("This will NOT be printed, LOG is still null");
        // NullPointerException will be thrown here
    }
}

IMPORTANTE : @PostConstructy @PreDestroy se han eliminado por completo en Java 11 .

Para seguir usándolos, deberá agregar el JAR javax.annotation-api a sus dependencias.

Maven

<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

Gradle

// https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api
compile group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
Andrea Ligios
fuente
19
in a constructor, the injection of the dependencies has not yet occurred. verdadero con setter o inyección de campo, pero no verdadero con inyección de constructor.
Adam Siemion el
Con la eliminación de @PostConstruct en Java 11, ¿cómo podemos manejar este ejemplo del mundo real con Java 11?
tet
@tet Como se mencionó en la respuesta, debe usar la biblioteca javax.annotation-api. Estas anotaciones se eliminaron en Java 11, pero ya se marcaron como obsoletas desde Java 9.
narendra-choudhary
63

Si su clase realiza toda su inicialización en el constructor, entonces @PostConstruct es redundante.

Sin embargo, si su clase tiene sus dependencias inyectadas usando métodos setter, entonces el constructor de la clase no puede inicializar completamente el objeto, y algunas veces es necesario realizar alguna inicialización después de haber llamado a todos los métodos setter, de ahí el caso de uso de @PostConstruct.

skaffman
fuente
@staffman: más uno de mi lado. Si deseo inicializar un campo inputtext con un valor obtenido de la base de datos, puedo hacerlo con la ayuda de PostConstruct, pero falla cuando intento hacer lo mismo dentro del constructor. Tengo este requisito para inicializar sin el uso de PostContruct. Si tiene tiempo, puede usted por favor contestar esta pregunta también: stackoverflow.com/questions/27540573/...
Shirgill Farhan
10

Considere el siguiente escenario:

public class Car {
  @Inject
  private Engine engine;  

  public Car() {
    engine.initialize();  
  }
  ...
}

Dado que Car debe ser instanciado antes de la inyección de campo, el motor del punto de inyección sigue siendo nulo durante la ejecución del constructor, lo que resulta en una NullPointerException.

Este problema puede resolverse mediante la inyección de dependencia JSR-330 para la inyección del constructor Java o las anotaciones comunes JSR 250 para la anotación del método Java @PostConstruct.

@PostConstruct

JSR-250 define un conjunto común de anotaciones que se ha incluido en Java SE 6.

La anotación PostConstruct se usa en un método que debe ejecutarse después de realizar la inyección de dependencia para realizar cualquier inicialización. Este método DEBE invocarse antes de que la clase se ponga en servicio. Esta anotación DEBE ser compatible con todas las clases que admitan la inyección de dependencia.

JSR-250 Cap. 2.5 javax.annotation.PostConstruct

La anotación @PostConstruct permite la definición de los métodos que se ejecutarán después de que se haya instanciado la instancia y se hayan realizado todas las inyecciones.

public class Car {
  @Inject
  private Engine engine;  

  @PostConstruct
  public void postConstruct() {
    engine.initialize();  
  }
  ...
} 

En lugar de realizar la inicialización en el constructor, el código se mueve a un método anotado con @PostConstruct.

El procesamiento de los métodos posteriores a la construcción es una simple cuestión de encontrar todos los métodos anotados con @PostConstruct e invocarlos a su vez.

private  void processPostConstruct(Class type, T targetInstance) {
  Method[] declaredMethods = type.getDeclaredMethods();

  Arrays.stream(declaredMethods)
      .filter(method -> method.getAnnotation(PostConstruct.class) != null) 
      .forEach(postConstructMethod -> {
         try {
           postConstructMethod.setAccessible(true);
           postConstructMethod.invoke(targetInstance, new Object[]{});
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {      
          throw new RuntimeException(ex);
        }
      });
}

El procesamiento de los métodos posteriores a la construcción debe realizarse después de que se hayan completado la creación de instancias y la inyección.

Humoyun Ahmad
fuente
1

Además, la inicialización basada en constructor no funcionará según lo previsto cuando se trate de algún tipo de representación remota o remota.

Se llamará al ct cada vez que se deserialice un EJB, y cada vez que se cree un nuevo proxy para él ...

Struberg
fuente