Зачем использовать @PostConstruct?


295

В управляемом компоненте @PostConstructвызывается после обычного конструктора объектов Java.

Почему я использовал @PostConstructбы инициализацию с помощью bean-компонента вместо обычного конструктора?


4
У меня сложилось впечатление, что инжекция в конструктор, как правило, предпочтительнее, чтобы зависимости были final. Учитывая этот шаблон, почему @PostConstructон добавляется в J2EE - они наверняка видели другой вариант использования?
Мьяггард

Ответы:


410
  • потому что, когда вызывается конструктор, бин еще не инициализирован, т.е. не вводятся зависимости. В @PostConstructметоде компонент полностью инициализирован, и вы можете использовать зависимости.

  • потому что это контракт, который гарантирует, что этот метод будет вызываться только один раз в жизненном цикле компонента. Может случиться (хотя и маловероятно), что контейнер во время своей внутренней работы несколько раз создается экземпляром контейнера, но это гарантирует, что @PostConstructон будет вызван только один раз.


17
в случае, если сам конструктор автоматически связывает все зависимости - тогда bean-компонент также может быть полностью инициализирован в конструкторе (после установки вручную всех автоматически связываемых полей).
Яир

7
в каком случае конструктор бина может быть вызван более одного раза?
Яир

1
Вероятно, что-то вроде «пассивации». Если контейнер решит сохранить компонент в хранилище диска, а затем восстановить его оттуда.
Божо

13
Не исключено, что конструктор будет вызываться несколько раз. Когда контейнер создает экземпляр прокси, вы увидите, что конструктор вызывается как минимум один раз для прокси и один раз для реального бина.
Маркус

Обратите внимание, что методы @PostConstruct не вызываются, когда страница загружается сразу после перезапуска сервера. (Это может быть ошибка JBoss.)
Дейв Джарвис

96

Основная проблема заключается в том , что:

в конструкторе внедрение зависимостей еще не произошло *

* очевидно, исключая конструктор инъекций


Пример из реальной жизни:

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

ВАЖНО : @PostConstructи @PreDestroy были полностью удалены в Java 11 .

Чтобы продолжать использовать их, вам нужно добавить JAR-файл javax.annotation-api в ваши зависимости.

специалист

<!-- 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'

19
in a constructor, the injection of the dependencies has not yet occurred. true с установщиком или внедрением поля, но не верно с внедрением конструктора.
Адам Симион

После удаления @PostConstruct в Java 11, как теперь мы можем обработать этот реальный пример с Java 11?
Tet

@tet Как уже упоминалось в ответе, вам нужно использовать библиотеку javax.annotation-api. Эти аннотации были удалены в Java 11, но были помечены как устаревшие с Java 9.
narendra-choudhary

63

Если ваш класс выполняет всю свою инициализацию в конструкторе, то @PostConstructон действительно избыточен.

Однако, если ваш класс имеет свои зависимости, введенные с использованием методов установки, тогда конструктор класса не может полностью инициализировать объект, и иногда некоторая инициализация должна выполняться после того, как все методы установки были вызваны, отсюда и пример использования @PostConstruct.


@staffman: плюс один с моей стороны. Если я хочу инициализировать поле ввода текста значением, полученным из базы данных, я могу сделать это с помощью PostConstruct, но не удается, когда пытаюсь сделать то же самое внутри конструктора. У меня есть это требование для инициализации без использования PostContruct. Если у вас есть время, не могли бы вы ответить на этот вопрос также: stackoverflow.com/questions/27540573/…
Ширгилл Фархан

10

Рассмотрим следующий сценарий:

public class Car {
  @Inject
  private Engine engine;  

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

Так как Car должен быть создан перед внедрением поля, механизм точки инжекции все еще равен нулю во время выполнения конструктора, что приводит к исключению NullPointerException.

Эта проблема может быть решена либо с помощью внедрения зависимостей JSR-330 для внедрения в конструктор Java, либо с помощью общих аннотаций JSR 250 для аннотации метода Java @PostConstruct.

@PostConstruct

JSR-250 определяет общий набор аннотаций, который был включен в Java SE 6.

Аннотация PostConstruct используется для метода, который необходимо выполнить после внедрения зависимости для выполнения любой инициализации. Этот метод ДОЛЖЕН быть вызван до того, как класс введен в эксплуатацию. Эта аннотация ДОЛЖНА поддерживаться всеми классами, которые поддерживают внедрение зависимостей.

JSR-250 гл. 2.5 javax.annotation.PostConstruct

Аннотация @PostConstruct позволяет определять методы, которые должны быть выполнены после того, как экземпляр был создан и все инъекции были выполнены.

public class Car {
  @Inject
  private Engine engine;  

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

Вместо выполнения инициализации в конструкторе код перемещается в метод, аннотированный @PostConstruct.

Обработка методов после конструирования - это простой вопрос поиска всех методов, аннотированных @PostConstruct, и последующего их вызова.

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

Обработка методов после конструирования должна выполняться после того, как реализация и внедрение были завершены.


1

Кроме того, инициализация на основе конструктора не будет работать так, как задумано, когда задействован какой-либо тип прокси или удаленного взаимодействия.

Ct будет вызываться всякий раз, когда EJB десериализуется, и всякий раз, когда для него создается новый прокси ...

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.