Почему поле с @Autowired в Spring оказывается null?
Примечание: Это предназначено быть каноническим ответом на распространённую проблему.
У меня есть класс Spring с аннотацией @Service
(MileageFeeCalculator
), который имеет поле с аннотацией @Autowired
(rateService
), однако это поле оказывается null
, когда я пытаюсь его использовать. Логи показывают, что оба бина MileageFeeCalculator
и MileageRateService
создаются, но при попытке вызвать метод mileageCharge
на моём сервис-бине возникает NullPointerException
. Почему Spring не выполняет автозаполнение данного поля?
Класс контроллера:
@Controller
public class MileageFeeController {
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
MileageFeeCalculator calc = new MileageFeeCalculator();
return calc.mileageCharge(miles);
}
}
Класс сервиса:
@Service
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService; // <--- должен быть автозаполнен, но равен null
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile()); // <--- вызывает NPE
}
}
Сервис-бин, который должен быть автозаполнен в MileageFeeCalculator
, но это не происходит:
@Service
public class MileageRateService {
public float ratePerMile() {
return 0.565f;
}
}
Когда я пытаюсь выполнить GET /mileage/3
, я получаю это исключение:
java.lang.NullPointerException: null
at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13)
at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14)
...
5 ответ(ов)
Если вы не разрабатываете веб-приложение, убедитесь, что ваш класс, в котором используется аннотация @Autowired, является бином Spring. Обычно контейнер Spring не осведомлён о классе, который мы можем считатьSpring-биным. Нам нужно сообщить контейнеру Spring о наших классах.
Это можно сделать, настроив файл конфигурации в application-context, или лучший способ — аннотировать класс как @Component и, пожалуйста, не создавайте аннотированный класс с помощью оператора new. Убедитесь, что вы получаете его из контекста приложения, как показано ниже:
@Component
public class MyDemo {
@Autowired
private MyService myService;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("test");
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
System.out.println("ctx>>" + ctx);
Customer c1 = null;
MyDemo myDemo = ctx.getBean(MyDemo.class);
System.out.println(myDemo);
myDemo.callService(ctx);
}
public void callService(ApplicationContext ctx) {
// TODO Auto-generated method stub
System.out.println("---callService---");
System.out.println(myService);
myService.callMydao();
}
}
Таким образом, убедитесь, что класс MyDemo
правильно аннотирован и получает зависимости через контейнер Spring, чтобы избежать возможных проблем с инъекцией зависимостей.
На самом деле, вам следует использовать либо объекты, управляемые JVM, либо объекты, управляемые Spring, для вызова методов. В вашем коде контроллера вы создаете новый объект для вызова вашего сервисного класса, который имеет авто-распределенный объект.
MileageFeeCalculator calc = new MileageFeeCalculator();
Поэтому так не получится.
Решение заключается в том, чтобы сделать MileageFeeCalculator
авто-распределяемым объектом непосредственно в контроллере.
Измените ваш класс контроллера следующим образом:
@Controller
public class MileageFeeController {
@Autowired
MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
return calc.mileageCharge(miles);
}
}
Таким образом, Spring позаботится о создании и управлении жизненным циклом объекта MileageFeeCalculator
, что обеспечит правильную работу автозаполненных зависимостей.
Я тоже столкнулся с этой проблемой, когда еще не привык к жизни в мире IoC. Поле @Autowired
одного из моих бинов оказалось нулевым во время выполнения.
Корень проблемы в том, что вместо использования авто-созданного бина, который поддерживается контейнером Spring IoC (где поле @Autowired
правильно инъектируется), я создал свой собственный экземпляр этого типа бина с помощью оператора new
и использовал его. Конечно, поле @Autowired
в этом случае будет нулевым, потому что у Spring не было возможности выполнить его инъекцию.
Ваш вопрос касается создания объектов в стиле Java (с использованием оператора new
) и их регистрации в контексте приложения Spring.
Когда вы используете аннотации, такие как @Service
, @Component
или @Configuration
, Spring автоматически создает бины и регистрирует их в контексте приложения при старте сервера. Однако, когда вы создаете объекты с помощью оператора new
, эти объекты не регистрируются в уже созданном контексте приложения.
В приведённом вами примере, вы пытаетесь зарегистрировать новый объект типа Employee
в контексте приложения через BeanFactoryPostProcessor
. Однако важно отметить, что использование оператора new
для создания экземпляров классов (например, new Employee()
) делает их обычными объектами Java, которые не под управлением Spring и, следовательно, не попадают в контекст Spring.
Чтобы корректно зарегистрировать Employee
как бин в контексте Spring, вам нужно либо использовать аннотации, либо программно добавлять бины через BeanDefinitionRegistry
. В вашем коде вы начинаете с создания Employee
, что является неверным с точки зрения регистрации в Spring. Вместо этого, вам нужно создавать его как Spring-бин:
@Bean
public Employee employee() {
return new Employee();
}
Или использовать @Component
вместе с дальнейшей конфигурацией для управления областью видимости.
Если вы хотите зарегистрировать бины в конкретной области, вы можете использовать уже имеющийся код, но создайте ваши объекты с помощью Spring-методов, чтобы они были полностью интегрированы в его контейнер и управление.
Вы можете использовать предложенное решение, но стоит отметить, что внедрение ApplicationContext
в статическую переменную не является лучшей практикой в Spring. Дело в том, что это нарушает принципы инверсии контроля и управления зависимостями, которые являются основополагающими для Spring.
Хотя ваш код будет работать, и с его помощью можно получать любые бины из контекста, использование статического контекста может привести к трудностям в тестировании и увеличивает связанность компонентов. Рекомендуется избегать использования статических методов и переменных для доступа к бинам.
Вместо этого, лучше всего использовать зависимости через инъекции непосредственно в классы, где они необходимы. Это делает код более чистым и легче тестируемым.
Если вам нужно получить доступ к ApplicationContext
, рассмотрите возможность внедрения его в нужные классы через конструктор или поля, а не через статические ссылки. Это поможет сохранить принципы SOLID и повысить гибкость вашего приложения.
Если вы хотите знать, как можно улучшить вашу реализацию, я с радостью помогу.
Нужна ли проверка на null перед вызовом instanceof?
Что значит "Не удалось найти или загрузить основной класс"?
Как установить Java 8 на Mac
Почему в RecyclerView отсутствует onItemClickListener()?
Что значит 'synchronized'?