Я заметил, что перехватчики @PreDestroy моих компонентов Spring с областью видимости прототипа не выполнялись.
С тех пор я прочитал здесь, что это на самом деле задумано. Контейнер Spring уничтожит одноэлементные bean-компоненты, но не уничтожит их прототипы. Мне непонятно почему. Если контейнер Spring создаст мой прототип bean-компонента и выполнит свой хук @PostConstruct, почему он не уничтожит и мой bean-компонент, когда контейнер закрыт? Когда мой контейнер Spring был закрыт, имеет ли смысл продолжать использовать какой-либо из его bean-компонентов? Я не вижу сценария, в котором вы хотели бы закрыть контейнер до того, как закончите с его bean-компонентами. Можно ли продолжать использовать прототип компонента Spring после закрытия его контейнера?
Вышеупомянутое описывает загадочную подоплеку моего основного вопроса: если контейнер Spring не уничтожает прототипы bean-компонентов, означает ли это, что может произойти утечка памяти? Или в какой-то момент компонент-прототип получит сборщик мусора?
В документации Spring говорится:
The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding. To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up.
Что это обозначает? Текст подсказывает мне, что я, как программист, несу ответственность за явное (вручную) уничтожение моих прототипов beans. Это правильно? Если да, то как мне это сделать?
Spring не знает, каков жизненный цикл компонента-прототипа, поэтому он будет вызывать только инициализаторы, а не обратные вызовы уничтожения, поскольку он просто не работает (и не может) сейчас, когда он вам больше не нужен.
Спасибо вам обоим. @ M.Deinum, то, что вы сказали, я обсуждал в своем вопросе. Закрытие контейнера Spring должно дать Spring большой намек на то, что мы закончили с нашими прототипами beans. После закрытия контейнера можно ли продолжать использовать любой компонент Spring (даже компонент-прототип)? Даже если это так, цитата из документации Spring предполагает, что, если я не уничтожу bean-компонент, произойдет утечка памяти. Вот что меня пугает. Я слышал Михала, который предполагает, что сборщик мусора подберет его, как только объектная переменная в компоненте-прототипе выйдет за пределы области видимости.
Нет, это не так. Как обстоят дела с Spring теперь, когда все еще существуют прототипы beans? Некоторые используют контекст с прототипами bean-компонентов как фабрику для короткоживущих bean-компонентов (или для получения чертежа bean-компонента). Теоретически могли быть созданы тысячи экземпляров прототипов bean-компонентов, которые уже были обработаны сборщиком мусора. Spring этого просто не знает. Утечка памяти возникает только в том случае, если этот прототип beans содержит ссылку на что-то, что предотвращает сборку мусора.
@Michal Но как бы вы это сделали (освободите ресурсы, используемые компонентом), если ловушка уничтожения компонента (@PreDestroy) никогда не вызывается Spring. Полагаю, единственный выход - вызвать ловушку разрушения вручную из моего клиентского кода?
Спасибо @ M.Deinum, ваш комментарий имеет смысл.




Для пользы других я представлю ниже то, что я собрал в результате своих исследований:
Пока компонент-прототип не содержит ссылку на другой ресурс, такой как соединение с базой данных или объект сеанса, он будет собирать мусор, как только все ссылки на объект будут удалены или объект выйдет за пределы области видимости. Поэтому обычно нет необходимости явно уничтожать компонент-прототип.
Однако в случае, когда может произойти утечка памяти, как описано выше, компоненты-прототипы могут быть уничтожены путем создания постпроцессора одноэлементного компонента, метод уничтожения которого явно вызывает перехватчики уничтожения ваших компонентов-прототипов. Поскольку постпроцессор сам является одноэлементным, его ловушка уничтожения будут вызывается Spring:
Создайте постпроцессор bean для обработки уничтожения всех ваших прототипов bean-компонентов. Это необходимо, потому что Spring не уничтожает прототипы bean-компонентов, и поэтому любые хуки @PreDestroy в вашем коде никогда не будут вызваны контейнером.
Реализуйте следующие интерфейсы:
1.BeanFactoryAware
Этот интерфейс предоставляет метод обратного вызова, который получает объект Beanfactory. Этот объект BeanFactory используется в классе постпроцессора для идентификации всех компонентов-прототипов с помощью его метода BeanFactory.isPrototype (String beanName).
2. Одноразовые
Этот интерфейс предоставляет метод обратного вызова Destroy (), вызываемый контейнером Spring. Мы будем вызывать методы Destroy () всех наших прототипов bean-компонентов из этого метода.
3. BeanPostProcessor
Реализация этого интерфейса обеспечивает доступ к обратным вызовам пост-обработки, из которых мы готовим внутренний List <> всех объектов-прототипов, созданных контейнером Spring. Позже мы переберем этот List <>, чтобы уничтожить каждый из наших прототипов bean-компонентов.
3. Наконец, реализуйте интерфейс DisposableBean в каждом из ваших прототипов bean-компонентов, предоставив метод Destroy (), требуемый этим контрактом.
Чтобы проиллюстрировать эту логику, я привожу ниже код, взятый из этого статья:
/**
* Bean PostProcessor that handles destruction of prototype beans
*/
@Component
public class DestroyPrototypeBeansPostProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {
private BeanFactory beanFactory;
private final List<Object> prototypeBeans = new LinkedList<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanFactory.isPrototype(beanName)) {
synchronized (prototypeBeans) {
prototypeBeans.add(bean);
}
}
return bean;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void destroy() throws Exception {
synchronized (prototypeBeans) {
for (Object bean : prototypeBeans) {
if (bean instanceof DisposableBean) {
DisposableBean disposable = (DisposableBean)bean;
try {
disposable.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
prototypeBeans.clear();
}
}
}
Собирает ли компонент-прототип сборщик мусора, если он не содержит ссылки на сеанс или пул, но содержит ссылки на другие службы (компоненты без состояния). Бин-прототип вызывает метод транзакционных служб. Таким образом, bean-компонент сам по себе не является транзакцией и не имеет сеанса, а внедренный beand-компонент.
По какой-то причине beanFactory.isPrototype() ломается в интеграционных тестах с NoSuchBeanDefinitionException на самом интеграционном тестовом компоненте. В остальном этот подход потрясающий.
В качестве дополнительной заметки лучше использовать Deque<Object> prototypeBeans = new ArrayDeque<>(); и push() вместо add(), чтобы цикл выполнялся в обратном порядке. Никогда не знаешь, есть ли дополнительные зависимости между bean-компонентами ...
И снова интеграционные тесты: не забудьте добавить @DirtiesContext в интеграционный тест, если он создает экземпляр bean-компонента с областью видимости прототипа.
Если мы используем приложение весенней загрузки на основе отдыха, в какой ситуации будет вызван метод уничтожения DestroyPrototypeBeansPostProcessor?
Я понимаю, как это происходит, но я не понимаю, почему. вы говорите, поскольку есть случай, когда у прототипов beans есть db, ссылки на сеанс, бремя должно ложиться на клиента, а не на структуру, чтобы уничтожить его с помощью специального процессора. Но почему?
"Пока компонент-прототип не содержит ссылку на другой ресурс, такой как соединение с базой данных или объект сеанса, он будет собирать мусор, как только все ссылки на объект будут удалены или объект выйдет за пределы области видимости. " Как вы думаете, почему bean-компонент не будет собираться, если он поддерживает соединение с базой данных?
Ваш ответ отличный. Я также хотел бы поделиться некоторыми замечаниями об альтернативном решении, которое позволяет членам прототипа, которые изначально управляются жизненным циклом контейнера Spring IoC с помощью внутренних компонентов.
Недавно я написал отвечать в отдельный вопрос о внутренних bean-компонентах. Внутренние bean-компоненты создаются путем присвоения значений свойств bean-компонентам как объекты BeanDefinition. Значения свойств определения компонента автоматически разрешаются в экземпляры (inner) (как управляемые одноэлементные компоненты) того компонента, который они определяют.
Следующий элемент конфигурации контекста XML может использоваться для создания отдельных автоматически подключаемых bean-компонентов ForkJoinPool для каждой управляемой ссылки (@PreDestroy будет вызываться при выключении контекста):
<!-- Prototype-scoped bean for creating distinct FJPs within the application -->
<bean id = "forkJoinPool" class = "org.springframework.beans.factory.support.GenericBeanDefinition" scope = "prototype">
<property name = "beanClass" value = "org.springframework.scheduling.concurrent.ForkJoinPoolFactoryBean" />
</bean>
Однако такое поведение зависит от того, назначена ли ссылка как стоимость имущества определения компонента. Это означает, что @Autowired и внедрение конструктора не работают с этим по умолчанию, поскольку эти методы автоматического подключения разрешают значение немедленно, а не используют разрешение значения свойства в AbstractAutowireCapableBeanFactory#applyPropertyValues. Автоматическое связывание по типу также не будет работать, поскольку разрешение типа не будет распространяться через bean-компоненты, являющиеся BeanDefinition, для поиска произведенного типа.
Этот метод будет работать, только если выполняется одно из двух условий:
AutowireCapableBeanFactory#AUTOWIRE_BY_NAME<!-- Setting bean references through XML -->
<beans ...>
<bean id = "myOtherBean" class = "com.example.demo.ForkJoinPoolContainer">
<property name = "forkJoinPool" ref = "forkJoinPool" />
</bean>
</beans>
<!-- Or setting the default autowire mode -->
<beans default-autowire = "byName" ...>
...
</beans>
Скорее всего, можно было бы внести два дополнительных изменения, чтобы включить внедрение конструктора и внедрение @Autowired.
Конструктор-инъекция:
Фабрика компонентов назначает AutowireCandidateResolver для внедрения конструктора. Значение по умолчанию (ContextAnnotationAutowireCandidateResolver) можно переопределить (DefaultListableBeanFactory#setAutowireCandidateResolver), чтобы применить распознаватель кандидатов, который ищет подходящие компоненты типа BeanDefinition для внедрения.
@Autowired-инъекция:
Постпроцессор bean-компонентов AutowiredAnnotationBeanPostProcessor напрямую устанавливает значения bean-компонентов без разрешения внутренних компонентов BeanDefinition. Этот постпроцессор можно переопределить, или можно создать отдельный постпроцессор bean для обработки пользовательской аннотации для управляемых компонентов-прототипов (например, @AutowiredManagedPrototype).
Вам не нужно ничего делать, пока вам не нужно освобождать ресурсы, используемые вашим прототипом bean-компонента. например если вы создаете пул соединений с БД в своем прототипе bean-компонента с областью видимости, вам, вероятно, нужно закрыть его :)