Где лучше размещать Autowired в Spring?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Где лучше размещать @Autowired в Spring
В Spring есть три способа разместить @Autowired: на полях, конструкторе или сеттере. Каждый имеет плюсы и минусы.
1. Field Injection (на полях) — МЕНЕЕ ПРЕДПОЧТИТЕЛЬНО
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // На поле
@Autowired
private EmailService emailService; // На поле
public void createUser(String name) {
User user = new User(name);
userRepository.save(user);
emailService.sendWelcome(user);
}
}
Проблемы Field Injection
❌ NullPointerException — если забыть @Autowired или бин не создался
@Service
public class BadService {
// ЗАБЫЛИ @Autowired!
private UserRepository userRepository;
public void createUser() {
userRepository.save(new User()); // NPE! userRepository == null
}
}
❌ Тестирование сложнее — нужно использовать Spring test контекст
// Для тестирования нужен Spring контекст
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testCreateUser() {
userService.createUser("Alice"); // Работает только в Spring контексте
}
}
// Unit-тест без Spring сложен
public class UserServiceUnitTest {
private UserService userService = new UserService();
// А где userRepository??
// Нельзя передать в конструктор, нельзя установить через setter
// Нужно использовать reflection для тестирования
}
❌ Скрытые зависимости — не видно, какие зависимости нужны классу
public class MyService {
@Autowired
private Service1 service1;
@Autowired
private Service2 service2;
@Autowired
private Service3 service3;
// ... 10 ещё зависимостей
// Понять, какие зависимости нужны, сложно!
}
❌ Неизменяемость — поле может быть изменено после создания
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Кто-то может случайно переписать!
public void setRepository(UserRepository repo) {
this.userRepository = repo; // Плохо!
}
}
❌ Нарушает принцип DIP — зависимость от Spring Framework
@Service
public class UserService {
@Autowired // Зависимость от Spring!
private UserRepository repo;
// Класс невозможно использовать без Spring
}
2. Setter Injection (через сеттер) — СРЕДНЕ ПРЕДПОЧТИТЕЛЬНО
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
// Зависимости через сеттеры
@Autowired
public void setUserRepository(UserRepository repo) {
this.userRepository = repo;
}
@Autowired
public void setEmailService(EmailService service) {
this.emailService = service;
}
public void createUser(String name) {
userRepository.save(new User(name));
emailService.sendWelcome(name);
}
}
Преимущества Setter Injection
✅ Опциональные зависимости
@Service
public class ReportService {
private ReportGenerator generator;
@Autowired(required = false) // Опциональная зависимость
public void setGenerator(ReportGenerator gen) {
this.generator = gen;
}
public void generateReport() {
if (generator != null) {
generator.generate();
} else {
System.out.println("Generator not available");
}
}
}
✅ Изменяемость — зависимости можно обновить
@Service
public class ConfigurableService {
private DatabaseService db;
@Autowired
public void setDatabase(DatabaseService database) {
this.db = database;
}
// Можно переключить БД во время выполнения
public void switchDatabase(DatabaseService newDb) {
this.db = newDb;
}
}
Проблемы Setter Injection
❌ Все ещё нужен Spring для тестирования
❌ Поле может быть null — если сеттер не был вызван
public class ProblematicService {
private UserRepository repo;
@Autowired
public void setRepo(UserRepository r) {
this.repo = r;
}
public void deleteUser(Long id) {
// repo может быть null!
if (repo == null) {
throw new NullPointerException();
}
repo.delete(id);
}
}
❌ Непонятно, какие зависимости обязательны
3. Constructor Injection (через конструктор) — ЛУЧШИЙ ПОДХОД
@Service
public class UserService {
private final UserRepository userRepository; // final — неизменяемо
private final EmailService emailService; // final — неизменяемо
// Зависимости через конструктор
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public void createUser(String name) {
User user = new User(name);
userRepository.save(user);
emailService.sendWelcome(user);
}
}
Преимущества Constructor Injection
✅ Очень легко тестировать — можно создать объект без Spring
// Unit-тест БЕЗ Spring, просто с Mockito
public class UserServiceTest {
@Mock
private UserRepository mockRepo;
@Mock
private EmailService mockEmail;
@InjectMocks
private UserService userService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testCreateUser() {
// Просто создаём объект
UserService service = new UserService(mockRepo, mockEmail);
service.createUser("Alice");
verify(mockRepo).save(any(User.class));
verify(mockEmail).sendWelcome(any(User.class));
}
}
// Или даже совсем реальные объекты
public class IntegrationTest {
@Test
public void testFlow() {
UserRepository repo = new InMemoryUserRepository();
EmailService email = new MockEmailService();
UserService service = new UserService(repo, email);
service.createUser("Bob");
assertEquals(1, repo.count());
}
}
✅ Явные зависимости — видны в сигнатуре конструктора
public UserService(UserRepository repo, EmailService email) {
// Ясно видно: нужны UserRepository и EmailService
}
// Сравните с field injection:
@Service
public class UserService {
@Autowired private UserRepository repo; // Скрыто
@Autowired private EmailService email; // Скрыто
@Autowired private DatabaseService db; // Скрыто
@Autowired private CacheService cache; // Скрыто
@Autowired private LoggingService logger; // Скрыто
// Много скрытых зависимостей!
}
✅ Неизменяемость — поля final
public class ImmutableService {
private final UserRepository repo; // final — нельзя изменить
private final EmailService email; // final — нельзя изменить
public ImmutableService(UserRepository repo, EmailService email) {
this.repo = repo;
this.email = email;
// Больше нельзя изменить!
}
}
✅ Работает без Spring — зависит только от конструктора
// Можно использовать в любом коде, без Spring
public class StandaloneApp {
public static void main(String[] args) {
// Создаём сервис вручную, без Spring контекста
UserRepository repo = new DatabaseUserRepository();
EmailService email = new SmtpEmailService();
UserService service = new UserService(repo, email);
service.createUser("Alice");
}
}
✅ Соответствует SOLID — зависит от абстракций (interfaces)
// Dependency Inversion Principle
public class UserService {
private final UserRepository repo; // Interface, не конкретная реализация
private final EmailService email; // Interface, не конкретная реализация
public UserService(UserRepository repo, EmailService email) {
this.repo = repo;
this.email = email;
}
}
✅ Легко заметить God Object — когда слишком много зависимостей
public class ProblematicService {
public ProblematicService(
UserRepository repo,
EmailService email,
SmsService sms,
PaymentService payment,
ReportService report,
CacheService cache,
LoggingService log,
ConfigService config,
MetricsService metrics,
NotificationService notification
) { // Слишком много! Нужен рефакторинг
}
}
Пример: Сравнение трёх подходов
Field Injection
@Service
public class OrderService {
@Autowired
private UserRepository userRepository;
@Autowired
private PaymentService paymentService;
public void createOrder(Long userId, BigDecimal amount) {
User user = userRepository.findById(userId).orElseThrow();
paymentService.charge(user, amount);
}
}
// Тестирование сложное
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
// Зависит от Spring контекста
}
Setter Injection
@Service
public class OrderService {
private UserRepository userRepository;
private PaymentService paymentService;
@Autowired
public void setUserRepository(UserRepository repo) {
this.userRepository = repo;
}
@Autowired
public void setPaymentService(PaymentService service) {
this.paymentService = service;
}
public void createOrder(Long userId, BigDecimal amount) {
User user = userRepository.findById(userId).orElseThrow();
paymentService.charge(user, amount);
}
}
Constructor Injection (ЛУЧШЕ)
@Service
public class OrderService {
private final UserRepository userRepository;
private final PaymentService paymentService;
public OrderService(UserRepository userRepository, PaymentService paymentService) {
this.userRepository = userRepository;
this.paymentService = paymentService;
}
public void createOrder(Long userId, BigDecimal amount) {
User user = userRepository.findById(userId).orElseThrow();
paymentService.charge(user, amount);
}
}
// Тестирование простое
public class OrderServiceTest {
@Test
public void testCreateOrder() {
UserRepository mockRepo = mock(UserRepository.class);
PaymentService mockPayment = mock(PaymentService.class);
// Просто создаём объект
OrderService service = new OrderService(mockRepo, mockPayment);
service.createOrder(1L, BigDecimal.valueOf(100));
verify(mockPayment).charge(any(), any());
}
}
Автоматическое добавление @Autowired на конструктор
Spring Boot автоматически внедрит в конструктор
@Service
public class UserService {
private final UserRepository repository;
// В Spring 4.3+ @Autowired добавляется автоматически
// на конструктор, если есть только один
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// Эквивалентно:
@Service
public class UserService {
private final UserRepository repository;
@Autowired // Можно добавить, но не обязательно
public UserService(UserRepository repository) {
this.repository = repository;
}
}
Lombok упрощает конструктор
@Service
@RequiredArgsConstructor // Генерирует конструктор с final полями
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Lombok генерирует:
// public UserService(UserRepository userRepository, EmailService emailService) {
// this.userRepository = userRepository;
// this.emailService = emailService;
// }
}
Рекомендуемый подход
@Service
@RequiredArgsConstructor // Ленький конструктор
public class UserService {
private final UserRepository userRepository; // final, неизменяемо
private final EmailService emailService; // final, неизменяемо
private final LoggingService logger; // final, неизменяемо
public User createUser(String name, String email) {
logger.info("Creating user: " + name);
User user = new User(name, email);
userRepository.save(user);
emailService.sendWelcome(user);
return user;
}
}
// Тестирование
public class UserServiceTest {
@Test
public void testCreateUser() {
var mockRepo = mock(UserRepository.class);
var mockEmail = mock(EmailService.class);
var mockLogger = mock(LoggingService.class);
var service = new UserService(mockRepo, mockEmail, mockLogger);
var user = service.createUser("Alice", "alice@example.com");
assertNotNull(user);
verify(mockRepo).save(user);
verify(mockEmail).sendWelcome(user);
}
}
Таблица сравнения
| Критерий | Field | Setter | Constructor |
|---|---|---|---|
| Тестируемость | ❌ Плохо | ❌ Плохо | ✅ Отлично |
| Видимость зависимостей | ❌ Скрыто | ⚠️ Не очень | ✅ Явно |
| Неизменяемость | ❌ Нет (mutable) | ⚠️ Частично | ✅ Да (final) |
| Опциональные зависимости | ❌ Сложно | ✅ Легко | ⚠️ Через Optional |
| Работает без Spring | ❌ Нет | ❌ Нет | ✅ Да |
| Циклические зависимости | ✅ Работает | ✅ Работает | ❌ Ошибка (хорошо!) |
Вывод
Constructor Injection — ЛУЧШИЙ подход:
✅ Явные и обязательные зависимости
✅ Легко тестировать (не нужен Spring)
✅ Неизменяемые поля (final)
✅ Работает в любом коде
✅ Соответствует SOLID принципам
✅ Автоматически в Spring 4.3+
✅ Хорошо работает с Lombok
Рекомендация: используй Constructor Injection с @RequiredArgsConstructor от Lombok