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

Какие знаешь способы запроса нового экземпляра prototype бина?

1.8 Middle🔥 171 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data#Spring Framework

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

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

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

Способы запроса нового экземпляра prototype бина в Spring

Prototype scope в Spring означает, что контейнер создаёт новый экземпляр бина каждый раз, когда тот запрашивается. Это отличается от singleton (по умолчанию), который создаётся один раз.

Проблема: Singleton инъекция prototype бина

Есть опасная ситуация:

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

@Component
public class SingletonService {
    @Autowired
    private PrototypeService prototypeService; // Проблема!
    
    public void doWork() {
        // Это будет всегда один и тот же объект!
        // Prototype scope игнорируется
        System.out.println(prototypeService.getId());
    }
}

// Тест
SingletonService service1 = context.getBean(SingletonService.class);
service1.doWork(); // ID: abc123

SingletonService service2 = context.getBean(SingletonService.class);
service2.doWork(); // ID: abc123 (ДОЛЖНО БЫТЬ ДРУГОЕ!)

Почему это происходит?

  • SingletonService создаётся один раз
  • PrototypeService инъектируется один раз
  • Spring не создаёт новый prototype при каждом вызове метода

Способ 1: ObjectFactory (Рекомендуется)

Используй ObjectFactory для ленивого создания prototype экземпляров:

@Component
public class SingletonService {
    @Autowired
    private ObjectFactory<PrototypeService> prototypeFactory;
    
    public void doWork() {
        // Новый экземпляр каждый раз!
        PrototypeService proto = prototypeFactory.getObject();
        System.out.println(proto.getId());
    }
}

// Тест
SingletonService service = context.getBean(SingletonService.class);
service.doWork(); // ID: abc123
service.doWork(); // ID: def456
service.doWork(); // ID: ghi789

Преимущества:

  • Тип-безопасен
  • Прост в использовании
  • Рекомендуется Spring

Способ 2: ObjectProvider (Spring 4.3+)

ObjectProvider — более гибкая версия ObjectFactory:

@Component
public class SingletonService {
    @Autowired
    private ObjectProvider<PrototypeService> prototypeProvider;
    
    public void doWork() {
        PrototypeService proto = prototypeProvider.getObject();
        System.out.println(proto.getId());
    }
    
    public void doWorkOptional() {
        // С optional поведением
        prototypeProvider.ifAvailable(proto -> {
            System.out.println(proto.getId());
        });
    }
    
    public void doWorkWithDefault() {
        // С дефолтным значением
        PrototypeService proto = prototypeProvider.getIfAvailable(
            () -> new PrototypeService() // fallback
        );
    }
}

Преимущества:

  • Более функциональный API
  • null-safety методы
  • Более читаемый код

Способ 3: Метод с аннотацией @Lookup

@Lookup — интересный декларативный способ:

@Component
public class SingletonService {
    
    // Spring сгенерирует реализацию этого метода
    @Lookup
    public PrototypeService getPrototype() {
        return null; // Spring переопределит
    }
    
    public void doWork() {
        PrototypeService proto = getPrototype();
        System.out.println(proto.getId());
    }
}

Spring будет использовать CGLIB для создания динамического подкласса:

// Примерно так Spring реализует это
public class SingletonServiceEnhanced extends SingletonService {
    @Override
    public PrototypeService getPrototype() {
        return context.getBean(PrototypeService.class); // Новый каждый раз
    }
}

Ограничения:

  • Класс не должен быть final
  • Метод не должен быть final
  • Требует CGLIB (для прокси создания)

Способ 4: Прямое обращение к ApplicationContext

Менее элегантно, но работает:

@Component
public class SingletonService {
    @Autowired
    private ApplicationContext context;
    
    public void doWork() {
        PrototypeService proto = context.getBean(PrototypeService.class);
        System.out.println(proto.getId());
    }
}

Недостатки:

  • Service coupling к Spring
  • Контейнер не инъектируется, а используется напрямую
  • Сложнее тестировать

Способ 5: Параметризованный @Lookup

С параметрами при инъекции:

@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public PrototypeService prototypeService() {
        return new PrototypeService();
    }
}

@Component
public class SingletonService {
    private ObjectFactory<PrototypeService> prototypeFactory;
    
    public SingletonService(ObjectFactory<PrototypeService> prototypeFactory) {
        this.prototypeFactory = prototypeFactory;
    }
    
    public void doWork() {
        PrototypeService proto = prototypeFactory.getObject();
        System.out.println(proto.getId());
    }
}

Сравнение способов

СпособТип-безопасностьПростотаЗависимость от SpringПроизводительность
ObjectFactoryДаХорошаяЕстьОтличная
ObjectProviderДаОтличнаяЕстьОтличная
@LookupДаХорошаяЕстьХорошая
ApplicationContextНетПлохаяЕстьХорошая

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

Аналогичная проблема с request scope в web приложениях:

@Component
@Scope("request") // Новый экземпляр для каждого HTTP запроса
public class RequestContext {
    private String requestId = UUID.randomUUID().toString();
    
    public String getRequestId() {
        return requestId;
    }
}

@Component
public class SingletonService {
    @Autowired
    private ObjectFactory<RequestContext> requestContextFactory;
    
    public void processRequest() {
        // Новый RequestContext для каждого запроса
        RequestContext ctx = requestContextFactory.getObject();
        System.out.println("Request ID: " + ctx.getRequestId());
    }
}

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

Используй prototype scope когда:

// 1. Stateful объекты, которые не должны быть переиспользованы
@Component
@Scope("prototype")
public class UserSession {
    private User currentUser;
    private List<Action> history = new ArrayList<>();
}

// 2. Дорогостоящие объекты, которые нужны на короткий период
@Component
@Scope("prototype")
public class DatabaseConnection {
    // Новое соединение для каждого запроса
}

// 3. Объекты с mutable state, которые изменяются
@Component
@Scope("prototype")
public class Calculator {
    private BigDecimal result;
    // Состояние не должно быть разделено
}

НЕ используй prototype для:

// Stateless services (это singleton)
@Component
public class EmailService {
    public void send(Email email) { /* ... */ }
}

// Stateless utilities
@Component
public class DateFormatter {
    public String format(Date date) { /* ... */ }
}

Частые ошибки

Ошибка 1: Забыли ObjectFactory

// Плохо
@Component
public class Service {
    @Autowired
    private PrototypeBean proto; // Всегда один объект!
}

// Хорошо
@Component
public class Service {
    @Autowired
    private ObjectFactory<PrototypeBean> protoFactory;
}

Ошибка 2: @Lookup на final методе

// Ошибка: CGLIB не может переопределить final метод
@Lookup
public final PrototypeService getPrototype() { /* ... */ }

// Правильно
@Lookup
public PrototypeService getPrototype() { /* ... */ }

Заключение

Для запроса нового экземпляра prototype бина:

  1. Используй ObjectFactory — стандартный способ
  2. Используй ObjectProvider — если нужна большая функциональность
  3. Используй @Lookup — если нравится декларативный стиль
  4. Избегай ApplicationContext — последний вариант

Лучшая практика:

@Component
public class MyService {
    private final ObjectFactory<PrototypeBean> beanFactory;
    
    public MyService(ObjectFactory<PrototypeBean> beanFactory) {
        this.beanFactory = beanFactory;
    }
    
    public void work() {
        PrototypeBean bean = beanFactory.getObject();
        // Новый экземпляр каждый раз
    }
}