Как Spring обрабатывает метод destroy() для бинов с областями видимости Singleton и Prototype?

Ответ

Spring контейнер по-разному управляет завершением жизненного цикла для бинов Singleton и Prototype.

Singleton Scope

  • Бин создается один раз при инициализации контекста.
  • Spring полностью управляет его жизненным циклом.
  • Метод уничтожения (аннотация @PreDestroy или destroy-method) вызывается автоматически при закрытии контекста приложения (ApplicationContext.close()).

Prototype Scope

  • Новый экземпляр создается при каждом запросе (getBean() или инъекции).
  • Spring передает созданный объект клиенту и прекращает управление его жизненным циклом.
  • Методы уничтожения НЕ вызываются автоматически. Ответственность за очистку ресурсов лежит на клиентском коде.

Пример и сравнение:

@Component
@Scope("singleton")
public class SingletonBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("SingletonBean: destroy method called");
    }
}

@Component
@Scope("prototype")
public class PrototypeBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("PrototypeBean: destroy method called"); // НИКОГДА не вызовется Spring'ом
    }
}

Сценарий выполнения:

ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

SingletonBean sb1 = ctx.getBean(SingletonBean.class);
SingletonBean sb2 = ctx.getBean(SingletonBean.class); // sb1 == sb2

PrototypeBean pb1 = ctx.getBean(PrototypeBean.class);
PrototypeBean pb2 = ctx.getBean(PrototypeBean.class); // pb1 != pb2

ctx.close(); // Вызовет cleanup() только для SingletonBean

Рекомендация для Prototype бинов: Используйте шаблон disposable bean или явно вызывайте метод очистки в клиентском коде:

public class ClientService {
    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanFactory;

    public void process() {
        PrototypeBean bean = prototypeBeanFactory.getObject();
        try {
            // работа с бином
        } finally {
            // Явный вызов cleanup
            if (bean instanceof DisposableBean) {
                ((DisposableBean) bean).destroy();
            }
        }
    }
}

Ответ 18+ 🔞

А, слушай, вот эта вся хуйня с бинами в Spring — она, блядь, такая хитрая, что мозг можно сломать. Особенно когда дело доходит до того, как они помирают, эти твои бины. Так вот, пиздец, запомни раз и навсегда:

Singleton — это как царь и бог в своём контексте. Создали его один раз, когда приложение запустилось, и он там сидит, блядь, как пизда с ушами, до самого конца. Spring за ним, сука, присматривает как за родным: накормит, напоит, и когда придёт время закрывать лавочку (ApplicationContext.close()), он ему вежливо так скажет: «Ну всё, дружок, пора на тот свет» — и вызовет метод уничтожения. Честно, по-пацански.

А вот Prototype — это, блядь, совсем другая история, ёпта! Это как одноразовые стаканчики. Захотел — взял новый (getBean()), попил, выкинул. Spring тебе этот стаканчик вручил и пошёл нахуй. Он больше про него нихуя не знает и не хочет знать. Управлять его смертью? Да хуй там! Твой @PreDestroy он проигнорирует с таким видом, будто это не его собака бегала. Он его даже в списке покойников не отметит, вот те крест.

Смотри, вот тебе наглядный пиздец, прости, пример:

@Component
@Scope("singleton")
public class SingletonBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("SingletonBean: destroy method called"); // ЭТО ВЫЗОВЕТСЯ, БЛЯДЬ!
    }
}

@Component
@Scope("prototype")
public class PrototypeBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("PrototypeBean: destroy method called"); // А ЭТО — НИКОГДА, ХУЙ ТЕБЕ, А НЕ ВЫЗОВ!
    }
}

И вот как это всё, сука, работает:

ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

SingletonBean sb1 = ctx.getBean(SingletonBean.class);
SingletonBean sb2 = ctx.getBean(SingletonBean.class); // sb1 == sb2, один и тот же мудак

PrototypeBean pb1 = ctx.getBean(PrototypeBean.class);
PrototypeBean pb2 = ctx.getBean(PrototypeBean.class); // pb1 != pb2, каждый раз новый мудак

ctx.close(); // И вот тут Spring вызовет cleanup() ТОЛЬКО для SingletonBean. Про Prototype он забудет, как про вчерашний запой.

Так что, если ты работаешь с Prototype, то ты, дружок, сам себе злобный буратино. Сам создал — сам и прибивай. Вот тебе, блядь, совет, как не обосраться с утечками памяти:

public class ClientService {
    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanFactory; // Хитрая жопа, даёт фабрику

    public void process() {
        PrototypeBean bean = prototypeBeanFactory.getObject(); // Достал новый экземпляр
        try {
            // делай тут что хочешь с этим бедным бином
        } finally {
            // А вот тут, сука, ОБЯЗАТЕЛЬНО прибери за собой!
            // Сам вызови его destroy-метод, если он реализует DisposableBean
            if (bean instanceof DisposableBean) {
                ((DisposableBean) bean).destroy(); // Вот так, по-взрослому
            }
        }
    }
}

А иначе получишь овердохуища мусора, и приложение твоё накроется медным тазом. Не говори потом, что я не предупреждал, ёпта!