Avatar ·

Почему Spring @Autowired поле равно null и как это исправить?

Всем привет. Я новичок в Spring и столкнулся с непонятной проблемой. У меня есть сервис `UserService`, который я хочу внедрить в контроллер через аннотацию `@Autowired`. Но при попытке вызвать метод сервиса я получаю `NullPointerException` — поле `userService` равно `null`. Вот мой код: ```java public class UserController { @Autowired private UserService userService; public void handleRequest() { userService.getUser(1); // Здесь NullPointerException } } @Service public class UserService { public String getUser(int id) { return "User " + id; } } ``` Я также пробовал использовать `@Component` на контроллере, но это не помогло. В `applicationContext.xml` я ничего не настраивал — думал, что Spring Boot сам всё подхватит. Проект запускается без ошибок, но при вызове метода `handleRequest()` падает исключение. В чём может быть причина? Нужно ли добавлять какие-то дополнительные аннотации или конфигурации? Заранее спасибо за помощь! <<>>

Основная причина: Spring не управляет вашим объектом

Проблема в том, что UserController создаётся вами вручную (через new UserController()), а не через контейнер Spring. Аннотация @Autowired работает только с бинами, которыми управляет Spring IoC-контейнер. Если вы создаёте объект сами, Spring не имеет возможности внедрить зависимости, и поле остаётся null.

Как это исправить?

Есть три основных подхода. Рассмотрим каждый с примерами.

1. Сделайте контроллер бином Spring

Добавьте аннотацию @Component (или @Controller для веб-слоя) и получайте объект из контекста:

@Controller // или @Component
public class UserController {
    @Autowired
    private UserService userService;

    public void handleRequest() {
        userService.getUser(1); // Теперь работает
    }
}

// В точке входа:
public static void main(String[] args) {
    ApplicationContext context = SpringApplication.run(MyApp.class, args);
    UserController controller = context.getBean(UserController.class);
    controller.handleRequest();
}

2. Используйте внедрение через конструктор (рекомендуется)

Этот подход считается лучшей практикой — он делает зависимости явными и упрощает тестирование:

@Controller
public class UserController {
    private final UserService userService;

    // Spring автоматически внедрит зависимость через конструктор
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void handleRequest() {
        userService.getUser(1);
    }
}

При использовании одного конструктора @Autowired можно опустить — Spring сам его найдёт.

3. Проверьте настройки компонентного сканирования

Убедитесь, что ваши классы находятся в пакетах, которые сканирует Spring. По умолчанию Spring Boot сканирует пакет, где находится класс с @SpringBootApplication, и все вложенные пакеты. Если ваш контроллер лежит в другом пакете, добавьте @ComponentScan:

@SpringBootApplication
@ComponentScan("com.example.controllers") // Укажите ваш пакет
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

Дополнительные советы

  • Не создавайте бины через new — всегда получайте их из контекста или внедряйте через DI.
  • Проверьте логи — Spring пишет, какие бины созданы. Ищите сообщения вида "Creating bean 'userController'".
  • Используйте конструктор — это позволяет сделать поля final и избежать мутабельности.
  • Для тестов используйте @SpringBootTest и @Autowired прямо в тестовом классе.

Почему это происходит?

Spring — это контейнер, который управляет жизненным циклом бинов. Когда вы пишете @Autowired, вы говорите: «Spring, найди подходящий бин и вставь его сюда». Но если объект не является бином (не аннотирован и не создан контейнером), Spring просто игнорирует аннотацию, и поле остаётся null. Это классическая ошибка новичков — смешивать ручное создание объектов с DI.

<<>>

Альтернативный подход: Использование ApplicationContext напрямую

Если по каким-то причинам вы не можете сделать класс бином (например, это legacy-код), можно получить бин вручную через ApplicationContext. Но это считается антипаттерном — используйте только в крайнем случае.

public class UserController {
    private UserService userService;

    public UserController() {
        // Получаем контекст Spring
        ApplicationContext context = SpringContextHolder.getContext();
        this.userService = context.getBean(UserService.class);
    }

    public void handleRequest() {
        userService.getUser(1);
    }
}

// Вспомогательный класс для хранения контекста
@Component
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

Когда это может пригодиться?

  • В утилитных классах, которые не могут быть бинами.
  • При интеграции со сторонними библиотеками, которые создают объекты через рефлексию.
  • В редких случаях миграции старого кода.

Однако помните: такой код сложнее тестировать, он создаёт скрытые зависимости и нарушает принцип инверсии контроля. Лучше всегда стремиться к тому, чтобы ваши классы были бинами Spring.