Нужно ли уничтожать бобы-прототипы Spring вручную?
Я заметил, что @PreDestroy
крючки из моего прототипа Spring beanted не выполнялись.
С тех пор я прочитал здесь, что это на самом деле дизайн. Контейнер Spring уничтожит одноэлементные компоненты, но не уничтожит компоненты-прототипы. Мне непонятно почему. Если контейнер Spring создаст мой bean-прототип и выполнит его @PostConstruct
Крюк, почему он не уничтожит мой боб, когда контейнер закрыт? Когда мой контейнер Spring был закрыт, имеет ли смысл продолжать использовать какой-либо из его компонентов? Я не вижу сценария, когда вы захотите закрыть контейнер, прежде чем закончите работу с его bean-компонентами. Можно ли продолжать использовать прототип Spring bean после закрытия его контейнера?
Выше описан загадочный фон для моего основного вопроса: если контейнер Spring не уничтожает компоненты-прототипы, значит ли это, что может произойти утечка памяти? Или в какой-то момент прототип будет собирать мусор?
Весенняя документация гласит:
Клиентский код должен очистить объекты в области прототипов и высвободить дорогостоящие ресурсы, которые содержатся в прототипных компонентах. Чтобы заставить контейнер Spring высвобождать ресурсы, содержащиеся в bean-объектах с прототипом, попробуйте использовать собственный постпроцессор bean-компонента, который содержит ссылку на bean-компоненты, которые необходимо очистить.
Что это значит? Текст подсказывает мне, что я, как программист, несу ответственность за (вручную) уничтожение моих прототипов bean-компонентов. Это правильно? Если так, то как мне это сделать?
2 ответа
Для блага других я представлю ниже то, что я извлек из своих исследований:
Пока компонент-прототип сам не содержит ссылку на другой ресурс, такой как соединение с базой данных или объект сеанса, он будет собирать мусор, как только все ссылки на объект будут удалены или объект выйдет из области видимости. Поэтому обычно нет необходимости явно уничтожать bean-прототип.
Однако в случае, когда может произойти утечка памяти, как описано выше, компоненты-прототипы могут быть уничтожены путем создания постпроцессора одноэлементного компонента, метод уничтожения которого явно вызывает ловушки уничтожения ваших компонентов-прототипов. Поскольку постпроцессор сам по себе имеет одноэлементную область видимости, Spring будет вызывать его хук уничтожения
Создайте постпроцессор bean для обработки всех ваших bean-компонентов. Это необходимо, потому что Spring не уничтожает компоненты-прототипы, и поэтому любые хуки @PreDestroy в вашем коде никогда не будут вызваны контейнером.
Реализуйте следующие интерфейсы:
1.BeanFactoryAware
Этот интерфейс предоставляет метод обратного вызова, который получает объект Beanfactory. Этот объект BeanFactory используется в классе постпроцессора для идентификации всех компонентов-прототипов с помощью его метода BeanFactory.isPrototype(String beanName).
2. Одноразовый Бин
Этот интерфейс предоставляет метод обратного вызова Destroy(), вызываемый контейнером Spring. Мы будем вызывать методы Destroy() всех наших прототипов bean-компонентов из этого метода.
3. BeanPostProcessor
Реализация этого интерфейса обеспечивает доступ к обратным вызовам постобработки, из которых мы подготавливаем внутренний список<> всех объектов-прототипов, созданных контейнером Spring. Позже мы пройдемся по этому списку <>, чтобы уничтожить каждый из наших прототипов.
3. Наконец, реализуйте интерфейс DisposableBean в каждом из ваших компонентов-прототипов, предоставив метод 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();
}
}
}
Ваш ответ великолепен. Я также хотел бы поделиться некоторыми заметками об альтернативном решении, которое позволяет членам-прототипам, которые естественным образом управляются жизненным циклом контейнера IoC Spring с помощью внутренних компонентов.
Недавно я написал ответ на отдельный вопрос о внутренних бобах. Внутренние компоненты создаются путем назначения значений свойств компонента как BeanDefinition
объекты. Значения свойств определения компонента автоматически разрешаются в (inner)
экземпляры (как управляемые одноэлементные компоненты) определяемого ими компонента.
Следующий элемент конфигурации контекста XML может быть использован для создания отдельных автопереключаемых 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
, Автопроводка по типу также не будет работать, так как разрешение типа не будет распространяться через бины, которые BeanDefinition
s, чтобы найти произведенный тип.
Этот метод будет работать, только если выполняется одно из двух условий:
- Зависимые бины также определены в XML
- Или если режим автопроводки установлен на
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
-injection.
Конструктор-инъекция:
Бобовая фабрика назначает
AutowireCandidateResolver
для конструктора инъекций. Значение по умолчанию (ContextAnnotationAutowireCandidateResolver
) может быть переопределено (DefaultListableBeanFactory#setAutowireCandidateResolver
) применить распознаватель-кандидат, который ищет подходящие компоненты типаBeanDefinition
для инъекций.@Autowired
-injection:AutowiredAnnotationBeanPostProcessor
постпроцессор бина напрямую устанавливает значения бина без разрешенияBeanDefinition
внутренние бобы. Этот постпроцессор может быть переопределен, или может быть создан отдельный постпроцессор bean-компонента для обработки пользовательской аннотации для управляемых bean-компонентов-прототипов (например,@AutowiredManagedPrototype
).