Pourquoi utiliser @PostConstruct?

294

Dans un bean géré, @PostConstructest appelé d'après le constructeur d'objet Java standard.

Pourquoi devrais-je utiliser @PostConstructpour initialiser par bean, au lieu du constructeur normal lui-même?

Jan
la source
4
J'ai eu l'impression que l'injection de constructeur était généralement préférée pour permettre aux dépendances d'être final. Compte tenu de ce modèle, pourquoi est @PostConstruct-il ajouté à J2EE - ils ont sûrement vu un autre cas d'utilisation?
mjaggard

Réponses:

409
  • car lorsque le constructeur est appelé, le bean n'est pas encore initialisé - c'est-à-dire qu'aucune dépendance n'est injectée. Dans la @PostConstructméthode, le bean est entièrement initialisé et vous pouvez utiliser les dépendances.

  • car c'est le contrat qui garantit que cette méthode ne sera invoquée qu'une seule fois dans le cycle de vie du bean. Il peut arriver (bien que peu probable) qu'un bean soit instancié plusieurs fois par le conteneur dans son fonctionnement interne, mais il garantit qu'il @PostConstructne sera invoqué qu'une seule fois.

Bozho
la source
17
au cas où le constructeur lui-même câblerait automatiquement toutes les dépendances - le bean peut également être complètement initialisé dans le constructeur (après avoir défini manuellement tous les champs câblés automatiquement).
yair
7
quel est le cas dans lequel le constructeur d'un bean peut être appelé plus d'une fois?
yair
1
Probablement quelque chose comme "passivation". Si le conteneur décide de stocker le bean sur le magasin de disques, puis de le restaurer à partir de là.
Bozho
13
Il n'est pas improbable de voir le constructeur appelé plusieurs fois. Lorsque le conteneur instancie un proxy, vous verrez que le constructeur est appelé au moins une fois pour le proxy et une fois pour le bean réel.
marcus
Notez que les méthodes @PostConstruct ne sont pas appelées lorsque la page est chargée immédiatement après un redémarrage du serveur. (Il s'agit peut-être d'un bug JBoss.)
Dave Jarvis
96

Le principal problème est que:

dans un constructeur, l'injection des dépendances n'a pas encore eu lieu *

* évidemment hors Constructor Injection


Exemple du monde réel:

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
    }
}

IMPORTANT : @PostConstructet @PreDestroy ont été complètement supprimés dans Java 11 .

Pour continuer à les utiliser, vous devrez ajouter le JAR javax.annotation-api à vos dépendances.

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
la source
19
in a constructor, the injection of the dependencies has not yet occurred. vrai avec l'injection de setter ou de champ, mais pas vrai avec l'injection de constructeur.
Adam Siemion,
Avec la suppression de @PostConstruct dans Java 11, comment pouvons-nous maintenant gérer cet exemple réel avec Java 11?
tet
@tet Comme mentionné dans la réponse, vous devez utiliser la bibliothèque javax.annotation-api. Ces annotations ont été supprimées dans Java 11, mais étaient déjà
déclarées
63

Si votre classe effectue toute son initialisation dans le constructeur, elle @PostConstructest en effet redondante.

Cependant, si votre classe a ses dépendances injectées à l'aide de méthodes de définition, le constructeur de la classe ne peut pas initialiser complètement l'objet, et parfois une initialisation doit être effectuée après l'appel de toutes les méthodes de définition, d'où le cas d'utilisation de @PostConstruct.

skaffman
la source
@staffman: plus un de mon côté. Si je souhaite initialiser un champ de texte d'entrée avec une valeur extraite de la base de données, je peux le faire avec l'aide de PostConstruct, mais échoue lorsque j'essaie de faire la même chose à l'intérieur du constructeur. J'ai cette exigence à initialiser sans utiliser PostContruct. Si vous avez le temps, pouvez-vous également répondre à celle-ci: stackoverflow.com/questions/27540573/…
Shirgill Farhan
10

Considérez le scénario suivant:

public class Car {
  @Inject
  private Engine engine;  

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

Étant donné que Car doit être instancié avant l'injection sur le terrain, le moteur du point d'injection est toujours nul pendant l'exécution du constructeur, ce qui entraîne une NullPointerException.

Ce problème peut être résolu soit par injection de dépendance JSR-330 pour l'injection de constructeur Java, soit par annotations communes JSR 250 pour l'annotation de méthode Java @PostConstruct.

@PostConstruct

JSR-250 définit un ensemble commun d'annotations qui a été inclus dans Java SE 6.

L'annotation PostConstruct est utilisée sur une méthode qui doit être exécutée après l'injection de dépendance pour effectuer toute initialisation. Cette méthode DOIT être invoquée avant la mise en service de la classe. Cette annotation DOIT être prise en charge sur toutes les classes qui prennent en charge l'injection de dépendances.

JSR-250 Chap. 2.5 javax.annotation.PostConstruct

L'annotation @PostConstruct permet de définir les méthodes à exécuter après l'instanciation de l'instance et toutes les injections.

public class Car {
  @Inject
  private Engine engine;  

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

Au lieu d'effectuer l'initialisation dans le constructeur, le code est déplacé vers une méthode annotée avec @PostConstruct.

Le traitement des méthodes post-construction est une simple question de trouver toutes les méthodes annotées avec @PostConstruct et de les invoquer tour à tour.

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);
        }
      });
}

Le traitement des méthodes post-construction doit être effectué une fois l'instanciation et l'injection terminées.

Humoyun Ahmad
la source
1

De plus, l'initialisation basée sur le constructeur ne fonctionnera pas comme prévu chaque fois qu'une sorte de proxy ou de communication à distance est impliquée.

Le ct sera appelé chaque fois qu'un EJB sera désérialisé et chaque fois qu'un nouveau proxy sera créé pour lui ...

struberg
la source