Por que usar o @PostConstruct?

294

Em um bean gerenciado, @PostConstruct é chamado após o construtor de objeto Java comum.

Por que eu usaria @PostConstructpara inicializar por bean, em vez do próprio construtor regular?

Jan
fonte
4
Tive a impressão de que a injeção de construtores era geralmente preferida para permitir que dependências existissem final. Dado esse padrão, por que está @PostConstructsendo adicionado ao J2EE - eles devem ter visto outro caso de uso com certeza?
Mjaggard

Respostas:

409
  • porque quando o construtor é chamado, o bean ainda não foi inicializado - ou seja, nenhuma dependência é injetada. No @PostConstructmétodo, o bean é totalmente inicializado e você pode usar as dependências.

  • porque este é o contrato que garante que esse método será chamado apenas uma vez no ciclo de vida do bean. Pode acontecer (embora improvável) que um bean seja instanciado várias vezes pelo contêiner em seu trabalho interno, mas garante que @PostConstructserá invocado apenas uma vez.

Bozho
fonte
17
caso o próprio construtor conecte automaticamente todas as dependências - o bean também poderá ser totalmente inicializado no construtor (depois de configurar manualmente todos os campos conectados automaticamente).
yair 20/03
7
qual é o caso em que o construtor de um bean pode ser chamado mais de uma vez?
yair 20/03
1
Provavelmente algo como "passivação". Se o contêiner decidir armazenar o bean no armazenamento de disco e restaurá-lo a partir daí.
Bozho
13
Não é tão improvável que o construtor seja chamado várias vezes. Quando o contêiner instancia um proxy, você verá que o construtor é chamado pelo menos uma vez para o proxy e uma vez para o bean real.
marcus
Observe que os métodos @PostConstruct não são chamados quando a página é carregada imediatamente após a reinicialização do servidor. (Isso pode ser um bug JBoss.)
Dave Jarvis
96

O principal problema é que:

em um construtor, a injeção das dependências ainda não ocorreu *

* obviamente excluindo injeção de construtor


Exemplo do 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 : @PostConstructe @PreDestroy foram completamente removidos no Java 11 .

Para continuar usando-os, você precisará adicionar o JAR javax.annotation-api às suas dependências.

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
fonte
19
in a constructor, the injection of the dependencies has not yet occurred. true com injeção de setter ou campo, mas não com injeção de construtor.
Adam Siemion 9/11
Com o @PostConstruct sendo removido no Java 11, como podemos lidar com este exemplo do mundo real com o Java 11?
tet
Como mencionado na resposta, você precisa usar a biblioteca javax.annotation-api. Essas anotações foram removidas no Java 11, mas já foram marcadas como obsoletas desde o Java 9.
narendra-choudhary
63

Se sua classe executa toda a sua inicialização no construtor, então @PostConstructé realmente redundante.

No entanto, se sua classe tiver suas dependências injetadas usando métodos setter, o construtor da classe não poderá inicializar completamente o objeto e, algumas vezes, alguma inicialização precisará ser executada após todos os métodos setter terem sido chamados, daí o caso de uso de @PostConstruct.

skaffman
fonte
@staffman: mais um do meu lado. Se eu desejar inicializar um campo de texto de entrada com um valor buscado no banco de dados, eu posso fazê-lo com a ajuda do PostConstruct, mas falhar ao tentar fazer o mesmo dentro do construtor. Eu tenho esse requisito para inicializar sem o uso de PostContruct. Se você tiver tempo, você pode responder a este também: stackoverflow.com/questions/27540573/… #
Shirgill Farhan
10

Considere o seguinte cenário:

public class Car {
  @Inject
  private Engine engine;  

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

Como o carro precisa ser instanciado antes da injeção no campo, o mecanismo do ponto de injeção ainda é nulo durante a execução do construtor, resultando em uma NullPointerException.

Esse problema pode ser resolvido pelo JSR-330 Dependency Injection for Java construtor ou pelo JSR 250 Common Annotations para a anotação do método Java @PostConstruct.

@PostConstruct

O JSR-250 define um conjunto comum de anotações que foi incluído no Java SE 6.

A anotação PostConstruct é usada em um método que precisa ser executado após a injeção de dependência ser executada para executar qualquer inicialização. Este método deve ser invocado antes que a classe seja colocada em serviço. Esta anotação DEVE ser suportada em todas as classes que suportam injeção de dependência.

JSR-250 Chap. 2.5 javax.annotation.PostConstruct

A anotação @PostConstruct permite a definição de métodos a serem executados após a instância ter sido instanciada e todas as injeções terem sido executadas.

public class Car {
  @Inject
  private Engine engine;  

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

Em vez de executar a inicialização no construtor, o código é movido para um método anotado com @PostConstruct.

O processamento de métodos pós-construção é uma questão simples de encontrar todos os métodos anotados com @PostConstruct e invocá-los por 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);
        }
      });
}

O processamento dos métodos pós-construção deve ser realizado após a conclusão da instanciação e injeção.

Humoyun Ahmad
fonte
1

Também a inicialização baseada em construtor não funcionará como pretendido sempre que algum tipo de proxy ou remoting estiver envolvido.

O ct será chamado sempre que um EJB for desserializado e sempre que um novo proxy for criado para ele ...

Struberg
fonte