Почему мое поле Spring @Autowired имеет значение null?
Примечание. Это должно быть каноническим ответом на распространенную проблему.
У меня весна @Service
учебный класс (MileageFeeCalculator
) который имеет @Autowired
поле (rateService
), но поле null
когда я пытаюсь использовать это. Журналы показывают, что оба MileageFeeCalculator
боб и MileageRateService
боб создаются, но я получаю NullPointerException
всякий раз, когда я пытаюсь позвонить mileageCharge
метод на моем сервисном бобе. Почему 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; // <--- should be autowired, is null
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile()); // <--- throws 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)
...
22 ответа
Поле аннотированное @Autowired
является null
потому что весна не знает о копии MileageFeeCalculator
что вы создали с new
и не знал, чтобы автоматически подключить его.
Контейнер Spring Inversion of Control (IoC) имеет три основных логических компонента: реестр (называемый ApplicationContext
) компонентов (bean-компонентов), которые доступны для использования приложением, система configurer, которая внедряет в них зависимости объектов путем сопоставления зависимостей с bean-компонентами в контексте, и решатель зависимостей, который может рассматривать конфигурацию множества различных bean-компоненты и определяют, как их создавать и настраивать в нужном порядке.
Контейнер IoC не волшебен, и у него нет возможности узнать об объектах Java, если вы не сообщите ему о них. Когда вы звоните new
JVM создает копию нового объекта и передает ее вам - она никогда не проходит через процесс настройки. Есть три способа настроить ваши bean-компоненты.
Я разместил весь этот код, используя Spring Boot для запуска, в этом проекте GitHub; Вы можете посмотреть на полностью работающий проект для каждого подхода, чтобы увидеть все, что вам нужно для его работы. Тег сNullPointerException
:nonworking
Введите ваши бобы
Наиболее предпочтительный вариант - позволить Spring автоматически связывать все ваши компоненты; это требует наименьшего количества кода и является наиболее поддерживаемым. Для того, чтобы автопроводка работала так, как вы хотели, также MileageFeeCalculator
как это:
@Controller
public class MileageFeeController {
@Autowired
private MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
return calc.mileageCharge(miles);
}
}
Если вам нужно создать новый экземпляр объекта службы для разных запросов, вы все равно можете использовать инъекцию с помощью областей действия Spring.
Тег, который работает, вводя @MileageFeeCalculator
объект обслуживания:working-inject-bean
Используйте @Configurable
Если вам действительно нужны объекты, созданные с new
чтобы быть автоматически подключенным, вы можете использовать Spring @Configurable
аннотации вместе с ткачеством AspectJ во время компиляции для внедрения ваших объектов. Этот подход вставляет код в конструктор вашего объекта, который сообщает Spring, что он создается, чтобы Spring мог сконфигурировать новый экземпляр. Это требует немного конфигурации в вашей сборке (например, компиляция с ajc
) и включение обработчиков конфигурации среды выполнения Spring (@EnableSpringConfigured
с синтаксисом JavaConfig). Этот подход используется системой Roo Active Record, чтобыnew
случаи вашей сущности для получения необходимой информации о постоянстве.
@Service
@Configurable
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService;
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}
Тег, который работает с помощью@Configurable
на объекте обслуживания:working-configurable
Ручной поиск бобов: не рекомендуется
Этот подход подходит только для взаимодействия с устаревшим кодом в особых ситуациях. Почти всегда предпочтительнее создать класс одноэлементного адаптера, который Spring может автоматически связывать и вызывать устаревший код, но можно напрямую запросить бин в контексте приложения Spring.
Для этого вам нужен класс, на который Spring может дать ссылку наApplicationContext
объект:
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
Тогда ваш старый код может позвонитьgetContext()
и получить нужные ему бобы:
@Controller
public class MileageFeeController {
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class);
return calc.mileageCharge(miles);
}
}
Тег, который работает путем ручного поиска объекта службы в контексте Spring:working-manual-lookup
Если вы не программируете веб-приложение, убедитесь, что ваш класс, в котором выполняется @Autowiring, является пружинным компонентом. Как правило, контейнер Spring не будет знать о классе, который мы можем рассматривать как пружинный компонент. Мы должны рассказать контейнеру Spring о наших классах Spring.
Это может быть достигнуто путем настройки в appln-contxt или лучше пометить класс как @Component, и, пожалуйста, не создавайте аннотированный класс с помощью оператора new. Убедитесь, что вы получили его из Appln-context, как показано ниже.
@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();
}
}
На самом деле, вы должны использовать либо управляемые объекты JVM, либо управляемый Spring объект для вызова методов. Исходя из приведенного выше кода в вашем классе контроллера, вы создаете новый объект для вызова вашего класса обслуживания, который имеет объект с автопроводкой.
MileageFeeCalculator calc = new MileageFeeCalculator();
так что это не сработает.
Решение превращает этот Пробег FeeCalculator в объект с автосвязью в самом контроллере.
Измените ваш класс контроллера, как показано ниже.
@Controller
public class MileageFeeController {
@Autowired
MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
return calc.mileageCharge(miles);
}
}
Однажды я столкнулся с той же проблемой, когда я не совсем привык the life in the IoC world
, @Autowired
поле одного из моих бобов является нулевым во время выполнения.
Основная причина заключается в том, что вместо использования автоматически созданного компонента, поддерживаемого контейнером Spring IoC (чей @Autowired
поле indeed
правильно впрыскивается) я newing
мой собственный экземпляр этого типа бина и использование его. Конечно это @Autowired
поле равно нулю, потому что у Spring нет шансов ввести его.
Ваша проблема новая (создание объекта в стиле Java)
MileageFeeCalculator calc = new MileageFeeCalculator();
С аннотацией @Service
, @Component
, @Configuration
бобы создаются в
контекст приложения Spring при запуске сервера. Но когда мы создаем объекты с помощью оператора new, объект не регистрируется в контексте приложения, который уже создан. Например, класс Employee.java, который я использовал.
Проверь это:
public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String name = "tenant";
System.out.println("Bean factory post processor is initialized");
beanFactory.registerScope("employee", new Employee());
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
if (name.equals(definition.getScope())) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, true);
registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
}
}
}
}
Кажется, это редкий случай, но вот что случилось со мной:
Мы использовали @Inject
вместо @Autowired
который является стандартом javaee, поддерживаемым Spring. Во всех местах он работал нормально, и бобы вводили правильно, а не в одном месте. Инъекция бобов кажется такой же
@Inject
Calculator myCalculator
Наконец, мы обнаружили, что ошибка заключалась в том, что мы (фактически, функция автоматического завершения Eclipse) импортировали com.opensymphony.xwork2.Inject
вместо javax.inject.Inject
!
Итак, подведем итог, убедитесь, что ваши аннотации (@Autowired
, @Inject
, @Service
...) есть правильные пакеты!
Я новичок в Spring, но я обнаружил это рабочее решение. Пожалуйста, скажите мне, если это осуждаемый способ.
Я делаю весну впрыскивать applicationContext
в этом бобе:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils {
public static ApplicationContext ctx;
/**
* Make Spring inject the application context
* and save it on a static variable,
* so that it can be accessed from any point in the application.
*/
@Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
}
Вы можете поместить этот код также в основной класс приложения, если хотите.
Другие классы могут использовать это так:
MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);
Таким образом, любой компонент может быть получен любым объектом в приложении (также new
) и статично.
То, что здесь не упомянуто, описано в этой статье в разделе "Порядок исполнения".
После того, как я "узнал", что мне пришлось аннотировать класс с помощью @Component или производных @Service или @Repository (я полагаю, что их больше), чтобы автоматически подключать другие компоненты внутри них, меня поразило, что эти другие компоненты все еще были нулевыми внутри конструктора родительского компонента.
Использование @PostConstruct решает эту проблему:
@SpringBootApplication
public class Application {
@Autowired MyComponent comp;
}
а также:
@Component
public class MyComponent {
@Autowired ComponentDAO dao;
public MyComponent() {
// dao is null here
}
@PostConstruct
public void init() {
// dao is initialized here
}
}
Один из следующих будет работать:
Класс, в котором вы используете @Autowired, не является Bean (я уверен, что где-то вы использовали new()).
Внутри класса SpringConfig вы не упомянули пакеты, которые Spring должен искать @Component (я говорю о @ComponentScan(basePackages"здесь"))
Если два указанных выше не работают... начните помещать System.out.println() и выясните, где что-то не так.
Если это происходит в тестовом классе, убедитесь, что вы не забыли аннотировать класс.
Например, в Spring Boot:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
....
Проще говоря, в основном есть две причины @Autowired
поле быть null
ВАШ КЛАСС - НЕ ВЕСНА.
ПОЛЕ - НЕ ФАСОЛЬ.
Другим решением будет вызов:SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Для того, чтобы Пробег FeeCalculator конструктор, как это:
@Service
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService; // <--- will be autowired when constructor is called
public MileageFeeCalculator() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
}
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}
Я думаю, что вы пропустили указание весной сканировать классы с аннотацией.
Ты можешь использовать @ComponentScan("packageToScan")
на классе конфигурации вашего приложения пружины, чтобы дать команду пружине сканировать.
@Service, @Component
и т.д. аннотации добавить мета-описание.
Spring только внедряет экземпляры тех классов, которые созданы как bean-компоненты или помечены аннотацией.
Классы, помеченные аннотацией, должны быть определены весной до введения, @ComponentScan
поручить весне искать классы, помеченные аннотацией. Когда весна находит @Autowired
он ищет связанный бин и внедряет требуемый экземпляр.
Добавление только аннотаций, не исправляющих и не облегчающих внедрение зависимостей, Spring должно знать, где искать.
Это виновник предоставления NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();
Мы используем Spring - не нужно создавать объекты вручную. Создание объекта позаботится о контейнере IoC.
Также обратите внимание, что если по какой-либо причине вы делаете метод в @Service
как final
автоматические бобы, к которым вы получите доступ, всегда будут null
,
ОБНОВЛЕНИЕ: действительно умные люди быстро указали на этот ответ, который объясняет странность, описанную ниже
ОРИГИНАЛЬНЫЙ ОТВЕТ:
Я не знаю, помогает ли это кому-нибудь, но я застрял с той же проблемой, даже когда делал все правильно. В моем методе Main у меня есть такой код:
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {
"common.xml",
"token.xml",
"pep-config.xml" });
TokenInitializer ti = context.getBean(TokenInitializer.class);
и в token.xml
файл у меня была строка
<context:component-scan base-package="package.path"/>
Я заметил, что package.path больше не существует, поэтому я просто отбросил строку навсегда.
И после этого NPE начал входить. В pep-config.xml
У меня было только 2 боба:
<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>
и класс SomeAbac имеет свойство, объявленное как
@Autowired private Settings settings;
по какой-то неизвестной причине, настройки равны нулю в init(), когда <context:component-scan/>
Элемент вообще не присутствует, но когда он присутствует и имеет несколько bs в качестве basePackage, все работает хорошо. Эта строка теперь выглядит так:
<context:component-scan base-package="some.shit"/>
и это работает. Может быть, кто-то может дать объяснение, но для меня этого достаточно прямо сейчас)
Вы также можете исправить эту проблему, используя аннотацию @Service для класса обслуживания и передавая требуемый bean-компонент classA в качестве параметра другому конструктору classB других bean-компонентов, и аннотируйте конструктор classB с помощью @Autowired. Пример фрагмента здесь:
@Service
public class ClassB {
private ClassA classA;
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}
public void useClassAObjectHere(){
classA.callMethodOnObjectA();
}
}
Я обнаружил, что похожий пост @Autowired bean имеет значение null при ссылке в конструкторе другого bean-компонента.
Основная причина ошибки может быть объяснена в справочном документе Spring ( Autowired) , следующим образом :
Автоматизированные поля
Поля вводятся сразу после создания bean-компонента, до вызова каких-либо методов конфигурации.
Но настоящая причина этого утверждения в документе Spring - это жизненный цикл компонента Spring. Это часть философии дизайна Spring.
Это обзор жизненного цикла Spring Bean: Bean должен быть инициализирован, прежде чем он может быть введен с такими свойствами, как field. Так устроены бобы, вот и настоящая причина.
Надеюсь, этот ответ будет вам полезен!
Это действительно только в случае модульного теста.
У моего класса обслуживания была аннотация службы, и это было @autowired
другой класс компонента. Когда я тестировал класс компонента, он становился нулевым. Поскольку для класса обслуживания я создавал объект, используяnew
Если вы пишете модульный тест, убедитесь, что вы не создаете объект, используя new object()
. Вместо этого используйте injectMock.
Это устранило мою проблему. Вот полезная ссылка
Не совсем связано с вопросом, но если инъекция поля имеет значение null, инъекция на основе конструктора все равно будет работать нормально.
private OrderingClient orderingClient;
private Sales2Client sales2Client;
private Settings2Client settings2Client;
@Autowired
public BrinkWebTool(OrderingClient orderingClient, Sales2Client sales2Client, Settings2Client settings2Client) {
this.orderingClient = orderingClient;
this.sales2Client = sales2Client;
this.settings2Client = settings2Client;
}
Если вы используете
private
метод, это будет
null
, попробуйте изменить
private
к
public
в контроллере.