Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Принцип инъекции зависимостей (Dependency Injection)
Инъекция зависимостей (Dependency Injection, DI) — это паттерн проектирования, который помогает создавать слабосвязанный, гибкий и тестируемый код. Вместо того чтобы объект сам создавал свои зависимости, они передаются ему извне.
Основная идея
Зависимость — это объект, который требуется другому объекту для работы. Вместо создания зависимости внутри класса, мы передаём её как параметр.
❌ Без DI (Tight Coupling)
Класс A создаёт Класс B сам
↓
Класс B создаёт Класс C сам
↓
Класс C создаёт Класс D сам
Проблема: сильная связанность, сложно тестировать
✅ С DI (Loose Coupling)
Класс A получает Класс B от внешнего источника
Класс B получает Класс C от внешнего источника
Класс C получает Класс D от внешнего источника
Преимущество: слабая связанность, легко тестировать
Проблема без DI
// ❌ Плохо — жёсткая связанность
public class UserService {
private UserRepository repository = new UserRepository(); // Создаём сами!
private EmailService emailService = new EmailService(); // Создаём сами!
public void createUser(User user) {
repository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
}
}
// Проблемы:
// 1. Сложно тестировать (нельзя использовать mock)
// 2. Если изменится конструктор EmailService — нужно менять везде
// 3. Невозможно использовать разные реализации
Решение с DI
// ✅ Хорошо — слабая связанность
public class UserService {
private UserRepository repository; // Зависимость
private EmailService emailService; // Зависимость
// Конструктор получает зависимости извне
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
public void createUser(User user) {
repository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
}
}
// Использование
UserRepository repo = new UserRepository();
EmailService email = new EmailService();
UserService service = new UserService(repo, email); // Инъекция!
Три способа инъекции зависимостей
1. Constructor Injection (Инъекция через конструктор)
Это лучший способ!
public class OrderService {
private final PaymentProcessor paymentProcessor;
private final InventoryService inventoryService;
// Зависимости передаются через конструктор
public OrderService(
PaymentProcessor paymentProcessor,
InventoryService inventoryService
) {
this.paymentProcessor = paymentProcessor;
this.inventoryService = inventoryService;
}
public void placeOrder(Order order) {
inventoryService.reserve(order.getItems());
paymentProcessor.charge(order.getTotal());
}
}
// Использование
PaymentProcessor processor = new PaymentProcessor();
InventoryService inventory = new InventoryService();
OrderService service = new OrderService(processor, inventory);
Преимущества:
- Зависимости видны в сигнатуре конструктора
- Можно сделать поля final (неизменяемость)
- Легко создавать тесты
- Обязательные зависимости явно видны
2. Setter Injection (Инъекция через setter)
public class UserService {
private UserRepository repository; // Может быть null
// Зависимость передаётся через метод
public void setRepository(UserRepository repository) {
this.repository = repository;
}
public void getUser(Long id) {
if (repository == null) {
throw new NullPointerException("Repository не инициализирован!");
}
return repository.findById(id);
}
}
// Использование
UserService service = new UserService();
service.setRepository(new UserRepository());
Недостатки:
- Зависимость может быть null
- Сложнее тестировать
- Порядок вызова методов важен
3. Interface Injection
// Интерфейс для инъекции
public interface RepositoryInjector {
void injectRepository(UserRepository repository);
}
// Реализация
public class UserService implements RepositoryInjector {
private UserRepository repository;
@Override
public void injectRepository(UserRepository repository) {
this.repository = repository;
}
}
// Используется редко, в основном в Spring
DI контейнеры
DI контейнер (DI Container) — это фреймворк, который автоматически управляет инъекцией зависимостей. Spring Framework — самый популярный DI контейнер в Java.
Spring Framework DI
// Интерфейсы
public interface UserRepository {
User findById(Long id);
void save(User user);
}
// Реализация
@Repository // Spring автоматически создаст bean
public class JpaUserRepository implements UserRepository {
@Autowired
private JpaRepository jpaRepo;
@Override
public User findById(Long id) {
return jpaRepo.findById(id).orElse(null);
}
@Override
public void save(User user) {
jpaRepo.save(user);
}
}
// Сервис
@Service // Spring автоматически создаст bean
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
// Spring автоматически инъектирует зависимости!
@Autowired
public UserService(
UserRepository repository,
EmailService emailService
) {
this.repository = repository;
this.emailService = emailService;
}
public void createUser(User user) {
repository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
}
}
// Контроллер
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired // Spring инъектирует UserService
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
userService.createUser(user);
return ResponseEntity.created(URI.create("/api/users/" + user.getId()))
.body(user);
}
}
Практический пример: Email Service
// ❌ Без DI
public class OrderNotificationService {
private GmailEmailService emailService = new GmailEmailService(); // Жёстко привязано!
public void notifyOrderCreated(Order order) {
emailService.send(
order.getCustomer().getEmail(),
"Order Confirmation",
"Your order has been created"
);
}
}
// Проблема: если нужно использовать SendGridEmailService, нужно менять код!
// ✅ С DI
public interface EmailService {
void send(String to, String subject, String body);
}
public class GmailEmailService implements EmailService {
@Override
public void send(String to, String subject, String body) {
// Отправка через Gmail
}
}
public class SendGridEmailService implements EmailService {
@Override
public void send(String to, String subject, String body) {
// Отправка через SendGrid
}
}
public class OrderNotificationService {
private final EmailService emailService; // Зависит от интерфейса!
public OrderNotificationService(EmailService emailService) {
this.emailService = emailService;
}
public void notifyOrderCreated(Order order) {
emailService.send(
order.getCustomer().getEmail(),
"Order Confirmation",
"Your order has been created"
);
}
}
// Использование
OrderNotificationService service1 = new OrderNotificationService(
new GmailEmailService() // Легко переключаться!
);
OrderNotificationService service2 = new OrderNotificationService(
new SendGridEmailService() // Другая реализация, тот же код
);
Тестирование с DI
// Mock для тестирования
public class MockEmailService implements EmailService {
private List<String> sentEmails = new ArrayList<>();
@Override
public void send(String to, String subject, String body) {
sentEmails.add(to);
}
public List<String> getSentEmails() {
return sentEmails;
}
}
// Тест
@Test
public void testOrderNotification() {
MockEmailService mockEmail = new MockEmailService();
OrderNotificationService service = new OrderNotificationService(mockEmail);
Order order = new Order();
order.getCustomer().setEmail("customer@example.com");
service.notifyOrderCreated(order);
// Проверяем, что email был отправлен
assertThat(mockEmail.getSentEmails())
.contains("customer@example.com");
}
Преимущества DI
- Слабая связанность — код не зависит от конкретных реализаций
- Тестируемость — легко подставить mock объекты
- Гибкость — легко менять реализации без изменения кода
- Переиспользуемость — компоненты легко комбинируются по-новому
- Явность — зависимости видны в сигнатуре конструктора
Разница: Inversion of Control (IoC)
IoC — более общий паттерн, означающий что фреймворк контролирует поток выполнения. DI — конкретная реализация IoC, где зависимости инъектируются.
IoC (Inversion of Control)
├── Dependency Injection (DI)
├── Factory Pattern
├── Service Locator
└── Другие паттерны
Заключение
Дependency Injection — это мощный паттерн, который делает код гибким, тестируемым и maintainable. Spring Framework делает DI простым и удобным. Всегда передавай зависимости через конструктор вместо создания их внутри класса.