← Назад к вопросам
Как работает внедрение зависимостей?
2.3 Middle🔥 271 комментариев
#SOLID и паттерны проектирования#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает внедрение зависимостей (Dependency Injection)
Концепция
Dependency Injection (DI) — это паттерн проектирования, который решает проблему управления зависимостями объектов. Вместо того, чтобы объект сам создавал свои зависимости, они внедряются извне.
Проблема без DI (❌ плохо)
public class UserService {
// UserService сам создает свои зависимости
private UserRepository repository = new DatabaseUserRepository();
private EmailService emailService = new GmailEmailService();
private NotificationService notificationService = new PushNotificationService();
public void registerUser(UserRequest request) {
User user = repository.save(new User(request));
emailService.sendWelcomeEmail(user);
notificationService.notify(user, "Добро пожаловать!");
}
}
Проблемы:
- Тесты невозможны — нельзя использовать mock, будет создана реальная база и реальная почта
- Жесткая связанность — изменить реализацию сложно
- Сложно масштабировать — каждый сервис создает все свои зависимости
- Неправильная ответственность — UserService занимается не только бизнес-логикой, но и созданием объектов
Решение с DI (✓ хорошо)
public class UserService {
// Зависимости внедряются, а не создаются
private final UserRepository repository;
private final EmailService emailService;
private final NotificationService notificationService;
// Constructor Injection
public UserService(UserRepository repository,
EmailService emailService,
NotificationService notificationService) {
this.repository = repository;
this.emailService = emailService;
this.notificationService = notificationService;
}
public void registerUser(UserRequest request) {
User user = repository.save(new User(request));
emailService.sendWelcomeEmail(user);
notificationService.notify(user, "Добро пожаловать!");
}
}
Преимущества:
- Легко тестировать — подставляем mock реализации
- Слабая связанность — работает с интерфейсами
- Гибко масштабировать — контейнер управляет созданием объектов
- Правильная ответственность — сервис только о бизнес-логике
Механизм: как работает DI в Spring
Этап 1: Регистрация beans
@Configuration
public class AppConfig {
// Регистрируем beans — инструкция Spring как создавать объекты
@Bean
public UserRepository userRepository() {
return new DatabaseUserRepository();
}
@Bean
public EmailService emailService() {
return new GmailEmailService();
}
@Bean
public UserService userService(UserRepository repository,
EmailService emailService) {
return new UserService(repository, emailService);
}
}
// Или с аннотациями (Stereotype Annotations)
@Repository
public class DatabaseUserRepository implements UserRepository { }
@Service
public class EmailService { }
@Service
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
// Spring автоматически увидит конструктор и внедрит зависимости
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}
Этап 2: Инициализация ApplicationContext
Жизненный цикл Spring контейнера:
1. PARSING (Разбор конфига)
↓
2. BEAN REGISTRATION (Регистрация beans)
Spring сканирует классы с @Bean, @Service, @Repository и т.д.
↓
3. DEPENDENCY RESOLUTION (Разрешение зависимостей)
Spring анализирует конструкторы, setters, fields
Строит граф зависимостей
↓
4. INSTANTIATION (Создание объектов)
Spring создает beans в правильном порядке
(сначала зависимости, потом объекты которые их используют)
↓
5. INJECTION (Внедрение)
Spring внедряет зависимости в каждый bean
↓
6. INITIALIZATION (Инициализация)
Вызывает @PostConstruct методы
↓
7. READY (Готово)
ApplicationContext ready к использованию
Три способа внедрения зависимостей
1. Constructor Injection (рекомендуется)
@Service
public class UserService {
private final UserRepository repository; // Final = immutable
private final EmailService emailService; // Обязательны при создании
// Spring вызывает этот конструктор и внедряет зависимости
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}
Преимущества:
- Очевидны все зависимости
- Объект immutable (final поля)
- Легко тестировать
- Невозможно создать объект без всех зависимостей
Как это работает в Spring:
Spring видит конструктор с параметрами
↓
Для каждого параметра ищет bean по типу
↓
Находит UserRepository bean и EmailService bean
↓
Передает их в конструктор
↓
Создает новый UserService(repository, emailService)
2. Setter Injection
@Service
public class UserService {
private UserRepository repository; // Не final
private EmailService emailService; // Опциональны
@Autowired
public void setRepository(UserRepository repository) {
this.repository = repository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Недостатки:
- Можно случайно создать объект без зависимостей
- Объект mutable (можно изменить после создания)
- Не ясны обязательные зависимости
3. Field Injection
@Service
public class UserService {
@Autowired
private UserRepository repository; // Прямое внедрение в поле
@Autowired
private EmailService emailService;
}
Недостатки:
- Сложнее тестировать (нужна рефлексия для внедрения в тестах)
- Не ясны зависимости
- Сложно понять, какие поля обязательны
Как Spring находит beans для внедрения
1. По типу (Type)
// Spring ищет bean типа UserRepository
@Service
public class UserService {
public UserService(UserRepository repository) { } // Найдет DatabaseUserRepository
}
@Repository
public class DatabaseUserRepository implements UserRepository { }
2. По имени (Name)
@Bean
public UserRepository userRepository() {
return new DatabaseUserRepository();
}
@Service
public class UserService {
// Если несколько реализаций, можно указать имя
public UserService(@Qualifier("userRepository") UserRepository repo) { }
}
3. По аннотациям
@Qualifier("primary") // Приоритет
@Repository
public class DatabaseUserRepository implements UserRepository { }
@Primary // Альтернатива — эта реализация по умолчанию
@Repository
public class CachedUserRepository implements UserRepository { }
Пример полного процесса DI
// Шаг 1: Регистрируем beans
@Configuration
public class Config {
@Bean
public DataSource dataSource() {
return new DataSource("jdbc:postgresql://localhost/mydb");
}
@Bean
public UserRepository userRepository(DataSource dataSource) {
return new UserRepository(dataSource);
}
@Bean
public EmailService emailService() {
return new EmailService();
}
@Bean
public UserService userService(UserRepository repository,
EmailService emailService) {
return new UserService(repository, emailService);
}
}
// Шаг 2: Приложение
public class Application {
public static void main(String[] args) {
// Spring создает ApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// Получаем bean из контекста
UserService userService = context.getBean(UserService.class);
// Используем (все зависимости уже внедрены)
userService.registerUser(new UserRequest("john@example.com"));
}
}
// Шаг 3: Тестирование (подставляем mocks)
@SpringBootTest
class UserServiceTest {
@MockBean
private UserRepository userRepository; // Mock вместо реального
@Autowired
private UserService userService; // Spring внедрит сервис с mock'ом
@Test
void testRegisterUser() {
// Настраиваем mock
Mockito.when(userRepository.save(any()))
.thenReturn(new User(1L, "john@example.com"));
// Тестируем
userService.registerUser(new UserRequest("john@example.com"));
// Проверяем
Mockito.verify(userRepository).save(any());
}
}
Жизненный цикл beans
Bean жизненный цикл:
1. INSTANTIATION
Создается экземпляр класса (через конструктор или фабрику)
↓
2. POPULATE PROPERTIES
Вызываются setters для свойств
↓
3. SET BEAN NAME
Если реализует BeanNameAware, вызывается setBeanName()
↓
4. SET FACTORY
Если реализует BeanFactoryAware, вызывается setBeanFactory()
↓
5. POST PROCESS BEFORE INIT
Вызываются BeanPostProcessor.postProcessBeforeInitialization()
↓
6. INIT METHOD
Вызывается @PostConstruct или initMethod
↓
7. POST PROCESS AFTER INIT
Вызываются BeanPostProcessor.postProcessAfterInitialization()
↓
8. USE
Bean готов к использованию
↓
9. DESTROY
При shutdown контекста вызывается @PreDestroy или destroyMethod
Пример:
@Service
public class UserService implements InitializingBean, DisposableBean {
private UserRepository repository;
public UserService(UserRepository repository) {
System.out.println("1. Конструктор вызван");
this.repository = repository;
}
@PostConstruct
public void init() {
System.out.println("2. PostConstruct вызван - инициализация");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("3. afterPropertiesSet вызван");
}
public void doWork() {
System.out.println("4. Основной метод вызван");
}
@PreDestroy
public void cleanup() {
System.out.println("5. PreDestroy вызван - очистка");
}
@Override
public void destroy() throws Exception {
System.out.println("6. destroy вызван");
}
}
Spring Bean Scopes
// SINGLETON (по умолчанию) — один экземпляр на весь ApplicationContext
@Bean
@Scope("singleton")
public UserService userService() { }
// PROTOTYPE — новый экземпляр каждый раз
@Bean
@Scope("prototype")
public UserService userService() { }
// REQUEST — один экземпляр на HTTP request
@Bean
@Scope("request")
public UserService userService() { }
// SESSION — один экземпляр на HTTP session
@Bean
@Scope("session")
public UserService userService() { }
Резюме
Dependency Injection — это:
- Паттерн — объекты получают зависимости извне
- Принцип — Inversion of Control (IoC) — управление жизненным циклом
- Инструмент в Spring — ApplicationContext создает и управляет beans
- Преимущество — код становится тестируемым, гибким, масштабируемым
В Spring это работает благодаря:
- Отражению (рефлексия для анализа конструкторов)
- Аннотациям (@Bean, @Service, @Autowired)
- Регистрации beans в ApplicationContext
- Разрешению зависимостей по типам