Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как создать ленивый Bean в Spring
Определение
Lazy Bean — это bean, который создается Spring контейнером только когда он первый раз запрашивается или используется, а не при инициализации приложения. Это экономит память и ускоряет startup.
Проблема
По умолчанию Spring создает все singleton beans при запуске приложения:
// Без @Lazy — создается при startup
@Configuration
public class AppConfig {
@Bean
public DatabaseConnection dbConnection() {
System.out.println("Database connection created"); // Выполнится при startup
return new DatabaseConnection();
}
}
// При запуске приложения выведет сразу:
// Database connection created
Проблемы:
- Долгий startup если много heavy beans
- Память занята неиспользуемыми beans
- Если bean требует недоступный ресурс (БД), весь app упадет
Решение 1: @Lazy аннотация
На @Bean методе
@Configuration
public class AppConfig {
@Bean
@Lazy // Создастся только при первом использовании
public DatabaseConnection dbConnection() {
System.out.println("Database connection created");
return new DatabaseConnection();
}
@Bean
@Lazy
public FileService fileService() {
System.out.println("File service initialized");
return new FileService();
}
}
@RestController
public class UserController {
@Autowired
private DatabaseConnection dbConnection; // Инжектируется без создания
@Autowired
private FileService fileService; // Инжектируется без создания
@GetMapping("/users")
public List<User> getUsers() {
// Первый вызов dbConnection — bean создается здесь
System.out.println("Fetching users");
return dbConnection.getUsers();
}
@GetMapping("/export")
public void exportUsers() {
// Первый вызов fileService — bean создается здесь
fileService.export();
}
}
На классе
@Component
@Lazy // Создастся при первом использовании
public class ExpensiveService {
public ExpensiveService() {
System.out.println("ExpensiveService initialized");
// Длительная инициализация
loadConfigFromFile();
connectToExternalAPI();
}
public void doSomething() {
System.out.println("Doing expensive operation");
}
}
Решение 2: ObjectProvider (Spring 5.1+)
ObjectProvider позволяет инжектировать lazy reference без @Lazy аннотации:
@Component
public class LazyBeanExample {
// Не создается при инициализации
@Autowired
private ObjectProvider<ExpensiveService> expensiveServiceProvider;
public void processData() {
// Bean создается при первом вызове getIfAvailable() или getObject()
ExpensiveService service = expensiveServiceProvider.getIfAvailable();
if (service != null) {
service.doSomething();
}
}
}
Преимущества ObjectProvider:
- Не требует @Lazy на bean
- Более гибко — может быть null если bean не существует
- Удобно для optional beans
@Configuration
public class AppConfig {
@Bean
public NormalService normalService() {
System.out.println("Normal service created");
return new NormalService(); // Создается при startup
}
}
@Component
public class MyComponent {
// ObjectProvider делает это ленивым без @Lazy
@Autowired
private ObjectProvider<NormalService> serviceProvider;
public void use() {
// Первый раз при вызове getObject()
NormalService service = serviceProvider.getObject();
service.work();
}
}
Решение 3: Proxy
Spring может создать lazy proxy, который задерживает создание реального bean:
@Configuration
public class AppConfig {
@Bean
@Lazy
public HeavyDatabase heavyDatabase() {
System.out.println("HeavyDatabase initialized");
return new HeavyDatabase();
}
}
@Component
public class DatabaseUser {
@Autowired
private HeavyDatabase database; // Инжектируется proxy
public void query() {
// При первом вызове метода proxy создает реальный bean
System.out.println("Querying...");
database.execute("SELECT * FROM users");
}
}
Решение 4: javax.inject.Provider
@Component
public class ProviderExample {
@Autowired
private javax.inject.Provider<SlowService> slowServiceProvider;
public void work() {
// SlowService создается при вызове get()
SlowService service = slowServiceProvider.get();
service.work();
}
}
Решение 5: Conditional Bean Initialization
@Configuration
public class AppConfig {
@Bean
@ConditionalOnProperty(
name = "features.analytics.enabled",
havingValue = "true"
)
@Lazy
public AnalyticsService analyticsService() {
System.out.println("Analytics service created");
return new AnalyticsService();
}
}
// application.yml
# Если false — bean вообще не создастся, даже лениво
features:
analytics:
enabled: false
Практический пример: Кеш с lazy инициализацией
@Component
@Lazy
public class CacheManager {
private Map<String, Object> cache;
public CacheManager() {
System.out.println("CacheManager initializing...");
this.cache = loadFromDisk(); // Дорогая операция
}
private Map<String, Object> loadFromDisk() {
// Имитация длительной загрузки
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new HashMap<>();
}
public Object get(String key) {
return cache.get(key);
}
public void put(String key, Object value) {
cache.put(key, value);
}
}
@RestController
public class CacheController {
@Autowired
private CacheManager cacheManager; // Инжектируется без инициализации
@GetMapping("/startup-time")
public String getStartupTime() {
// CacheManager еще не создан!
return "App started quickly";
}
@GetMapping("/cache/{key}")
public Object getCacheValue(@PathVariable String key) {
// Первый запрос к кешу — CacheManager создается сейчас
return cacheManager.get(key);
}
}
Output:
App startup (1 сек) — CacheManager не создается
Pервый GET /cache/key:
CacheManager initializing... (2 сек на инициализацию)
Возвращает значение
Когда использовать @Lazy
Используй @Lazy для:
// 1. Heavy beans, требующие много времени
@Bean
@Lazy
public DatabaseConnection dbConnection() {
// Долгое подключение к БД
}
// 2. Beans которые редко используются
@Bean
@Lazy
public AdminOnlyService adminService() {
// Нужна только администраторам
}
// 3. Beans с внешними зависимостями, которые могут быть недоступны
@Bean
@Lazy
public ExternalAPIClient externalAPI() {
// API может быть down при старте
}
// 4. Beans для optional features
@Bean
@Lazy
public NotificationService emailNotification() {
// Email уведомления опциональны
}
НЕ используй @Lazy для:
// 1. Beans, которые требуются при запуске
@Bean // БЕЗ @Lazy
public DatabaseMigration migration() {
// Нужна при старте
}
// 2. Beans с initialization проверками
@Bean // БЕЗ @Lazy
public ConfigValidator configValidator() {
// Ошибка должна быть видна при старте
}
// 3. Критичные зависимости
@Bean // БЕЗ @Lazy
public AuthenticationManager authManager() {
// Нужна для всех запросов
}
Best Practices
-
Используй @Lazy для truly optional beans:
@Bean @Lazy public AnalyticsService analytics() {} -
Предпочитай ObjectProvider для гибкости:
@Autowired private ObjectProvider<OptionalService> service; -
Логируй создание heavy beans:
@PostConstruct public void init() { logger.info("Heavy bean initialized"); } -
Тестируй lazy beans явно:
@Test public void testLazyBeanInitialization() { assertThat(context.getBeansOfType(LazyBean.class)).isEmpty(); }
Вывод
@Lazy аннотация задерживает создание bean до первого использования. Это ускоряет startup приложения и экономит память для редко используемых beans. Используй @Lazy для:
- Heavy beans (долгая инициализация)
- Optional beans (редко используемые)
- Beans с внешними зависимостями
Для более гибкого управления используй ObjectProvider или javax.inject.Provider.