← Назад к вопросам

Можно ли внедрять prototype бины в другие бины в Spring?

3.0 Senior🔥 81 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли внедрять prototype бины в другие бины в Spring?

Можно технически, но это приводит к проблемам. Prototype beans требуют специального обращения при внедрении в singleton beans, иначе получишь неправильное поведение.

Основная проблема

Singleton bean живёт всю жизнь приложения:

Spring container создаёт его один раз
↓
Все классы получают ссылку на один и тот же объект
↓
Объект никогда не пересоздаётся

Prototype bean должен пересоздаваться при каждом использовании:

Когда нужна новая ссылка
↓
Spring создаёт НОВЫЙ объект
↓
Клиент получает НОВЫЙ экземпляр

Проблема: Prototype в Singleton

@Component
@Scope("prototype")
public class ProtoBean {
    private UUID id = UUID.randomUUID();
    
    public UUID getId() {
        return id;
    }
}

@Component  // Default: singleton
public class SingletonBean {
    @Autowired
    private ProtoBean protoBean;  // ПРОБЛЕМА!
    
    public void printId() {
        System.out.println("ID: " + protoBean.getId());
    }
}

// Использование
public static void main(String[] args) {
    ApplicationContext ctx = new SpringApplicationContext(Main.class);
    
    SingletonBean bean1 = ctx.getBean(SingletonBean.class);
    bean1.printId();  // ID: abc123
    
    SingletonBean bean2 = ctx.getBean(SingletonBean.class);
    bean2.printId();  // ID: abc123  <- ОДИНАКОВЫЙ ID!
    // Ожидали разные ID, но получили одинаковый
    // Потому что protoBean внедрена один раз в singleton
}

ProtoBean создаётся один раз при создании SingletonBean и переиспользуется.

Решение 1: ObjectFactory (рекомендуется)

@Component
public class SingletonBean {
    @Autowired
    private ObjectFactory<ProtoBean> protoBeanFactory;  // Factory вместо bean
    
    public void printId() {
        ProtoBean protoBean = protoBeanFactory.getObject();  // Новый объект!
        System.out.println("ID: " + protoBean.getId());
    }
}

// Использование
SingletonBean bean1 = ctx.getBean(SingletonBean.class);
bean1.printId();  // ID: abc123

SingletonBean bean2 = ctx.getBean(SingletonBean.class);
bean2.printId();  // ID: def456  <- РАЗНЫЕ ID!
// Теперь работает правильно

Решение 2: Provider (альтернатива)

import javax.inject.Provider;

@Component
public class SingletonBean {
    @Autowired
    private Provider<ProtoBean> protoBeanProvider;  // Provider вместо bean
    
    public void printId() {
        ProtoBean protoBean = protoBeanProvider.get();  // Новый объект!
        System.out.println("ID: " + protoBean.getId());
    }
}

// Результат: то же самое, что ObjectFactory

Решение 3: Lookup method injection (старый способ)

@Component
public class SingletonBean {
    @Lookup  // Spring переопределит этот метод
    protected ProtoBean getProtoBean() {
        return null;  // Реализация будет сгенерирована
    }
    
    public void printId() {
        ProtoBean protoBean = getProtoBean();  // Новый объект!
        System.out.println("ID: " + protoBean.getId());
    }
}

// Результат: прямой вызов метода вернёт новый prototype bean

Решение 4: Scoped Proxy (если нужна инъекция)

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProtoBean {
    private UUID id = UUID.randomUUID();
    
    public UUID getId() {
        return id;
    }
}

@Component  // singleton
public class SingletonBean {
    @Autowired
    private ProtoBean protoBean;  // Теперь это proxy
    
    public void printId() {
        System.out.println("ID: " + protoBean.getId());
    }
}

// Результат: proxy каждый раз перенаправляет на новый объект

Сравнение решений

1. ObjectFactory<ProtoBean>

Плюсы:
+ Spring-специфичный
+ Явный код (видно что берёшь новый объект)
+ Производительность выше

Минусы:
- Spring-специфичный (не portable)
- Нужно вызывать getObject()

2. Provider<ProtoBean>

Плюсы:
+ JSR-330 стандарт (более portable)
+ Работает в других DI фреймворках
+ Явный код

Минусы:
- Нужно добавлять javax.inject зависимость
- Нужно вызывать get()

3. @Lookup

Плюсы:
+ Очень чистый код
+ Выглядит как обычный метод

Минусы:
- Использует CGLIB proxy (может быть медленнее)
- Нужно что-то возвращать (null/dummy)

4. Scoped Proxy

Плюсы:
+ Можно инъектить как обычный bean
+ Прозрачно для клиента

Минусы:
- Всегда через proxy (небольшие overheads)
- Может быть запутанно что происходит

Практический пример: Request scope

Частый случай - внедрять request-scoped bean в singleton:

// Request scope - новый для каждого HTTP request
@Component
@Scope("request")
public class UserContext {
    private String userId;
    private String requestId;
    
    public UserContext() {
        this.requestId = UUID.randomUUID().toString();
    }
    
    // getters/setters
}

// Singleton service
@Service
public class UserService {
    
    // НЕПРАВИЛЬНО - будет всегда один и тот же UserContext
    // @Autowired
    // private UserContext userContext;
    
    // ПРАВИЛЬНО - ObjectFactory
    @Autowired
    private ObjectFactory<UserContext> userContextFactory;
    
    public void processUser(String userId) {
        UserContext context = userContextFactory.getObject();  // Новый для текущего request!
        String requestId = context.getRequestId();
        // ...
    }
}

Когда prototype вообще использовать?

Prototype используется когда:

1. Нужен stateful объект
2. Каждый запрос требует своего экземпляра
3. Объект содержит данные, специфичные для использования

Примеры:

  • Request-scoped user context
  • Prototype для каждой задачи в потоке
  • Объекты, которые изменяют состояние

Лучше использовать:

  • Stateless singleton beans (по умолчанию)
  • Кэширование для performance
  • Prototype только если действительно нужно

Антипаттерны

Антипаттерн 1: Prototype в Singleton без Factory

// ПЛОХО
@Component
public class SingletonBean {
    @Autowired
    private ProtoBean protoBean;  // Это будет один объект!
}

Антипаттерн 2: Избыточное использование Prototype

// Если объект stateless, зачем prototype?
@Component
@Scope("prototype")
public class UtilityService {
    public String doSomething(String input) {
        return input.toUpperCase();
    }
}

// Singleton был бы правильнее
@Component
public class UtilityService {
    public String doSomething(String input) {
        return input.toUpperCase();
    }
}

Правильный паттерн

// 1. Определи scope явно
@Component
@Scope("prototype")
public class MyProtoBean {
    // ...
}

// 2. Внедрi через Factory
@Service
public class MyService {
    @Autowired
    private ObjectFactory<MyProtoBean> factory;
    
    public void doWork() {
        MyProtoBean bean = factory.getObject();  // Новый объект
        // ...
    }
}

// 3. Или используй @Lookup
@Service
public class MyService {
    @Lookup
    protected MyProtoBean getProtoBean() {
        return null;
    }
    
    public void doWork() {
        MyProtoBean bean = getProtoBean();  // Новый объект
        // ...
    }
}

Тестирование

Правильное тестирование prototype beans:

@SpringBootTest
public class PrototypeBeanTest {
    
    @Autowired
    private ObjectFactory<ProtoBean> factory;
    
    @Test
    public void testPrototypeCreatesNewInstance() {
        ProtoBean bean1 = factory.getObject();
        ProtoBean bean2 = factory.getObject();
        
        assertNotSame(bean1, bean2);  // Разные объекты
        assertNotEquals(bean1.getId(), bean2.getId());  // Разные ID
    }
}

Рекомендации

  1. По умолчанию используй Singleton — это более эффективно
  2. Используй Prototype только если действительно нужно — stateful объекты
  3. При внедрении Prototype в Singleton используй ObjectFactory или Provider — самый чистый способ
  4. Избегай Scoped Proxy если можно — излишняя сложность
  5. Документируй почему объект Prototype — не очевидно для других разработчиков

Вывод

Можно внедрять prototype beans, но с правильным подходом:

  1. Технически работает — Spring позволяет
  2. Но неправильно просто инъектить — singleton переиспользует prototype
  3. Используй ObjectFactory<T> или Provider<T> — лучший способ
  4. Или @Lookup — если нужен чистый код
  5. Или Scoped Proxy — если нужна инъекция

Главное — понимай разницу между scope'ами и выбирай правильный инструмент для внедрения.

Можно ли внедрять prototype бины в другие бины в Spring? | PrepBro