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

Как создать ленивый Bean в Spring?

1.8 Middle🔥 161 комментариев
#Spring Framework

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

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

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

# Как создать ленивый 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

  1. Используй @Lazy для truly optional beans:

    @Bean
    @Lazy
    public AnalyticsService analytics() {}
    
  2. Предпочитай ObjectProvider для гибкости:

    @Autowired
    private ObjectProvider<OptionalService> service;
    
  3. Логируй создание heavy beans:

    @PostConstruct
    public void init() {
        logger.info("Heavy bean initialized");
    }
    
  4. Тестируй 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.

Как создать ленивый Bean в Spring? | PrepBro