Почему мое поле Spring @Autowired null?
Примечание. Это будет канонический ответ для общей проблемы.
У меня есть класс Spring @Service
(MileageFeeCalculator
), который имеет поле @Autowired
(rateService
), но это поле null
, когда я пытаюсь его использовать. Журналы показывают, что создаются как MileageFeeCalculator
bean, так и MileageRateService
bean, но я получаю NullPointerException
, когда я пытаюсь вызвать метод mileageCharge
в моей службе bean. Почему не 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
}
}
Сервис bean, который должен быть автообновлен в 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)
...
Ответы
Ответ 1
Поле, аннотированное @Autowired
, равно null
, потому что Spring не знает о копии MileageFeeCalculator
, которую вы создали с помощью new
, и не знал, чтобы его автоустанавливать.
Контейнер Spring Inversion of Control (IoC) содержит три основных логических компонента: реестр (называемый ApplicationContext
) компонентов (beans) которые доступны для использования приложением, - конфигурационная система, которая вводит в них зависимостей объектов, сопоставляя зависимости с beans в контексте и решатель зависимостей, который может смотреть на конфигурацию множества различных beans и определить, как создавать и настраивать их в необходимом порядке.
Контейнер IoC не является магическим, и он не может знать о объектах Java, если вы их каким-то образом не проинформируете об этом. Когда вы вызываете new
, JVM создает экземпляр нового объекта и передает его прямо вам - он никогда не проходит процесс настройки. Существует три способа настройки конфигурации beans.
Я опубликовал весь этот код, используя Spring Boot для запуска в этом проекте GitHub; вы можете посмотреть полный рабочий проект для каждого подхода, чтобы увидеть все, что вам нужно, чтобы заставить его работать. Тег с NullPointerException
: nonworking
Внесите свой beans
Самый предпочтительный вариант - позволить Spring автоувеличивать все ваши beans; это требует наименьшего количества кода и является наиболее удобным для обслуживания. Чтобы сделать работу по автопостановке, как вы хотели, также выполните автопоиск MileageFeeCalculator
следующим образом:
@Controller
public class MileageFeeController {
@Autowired
private MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
return calc.mileageCharge(miles);
}
}
Если вам нужно создать новый экземпляр объекта службы для разных запросов, вы все равно можете использовать инъекцию с помощью областей Spring bean.
Тег, который работает, введя объект службы @MileageFeeCalculator
: working-inject-bean
Использовать @Configurable
Если вам действительно нужны объекты, созданные с помощью new
для автоустройства, вы можете использовать аннотацию Spring @Configurable
вместе с AspectJ компиляцией во время компиляции до вводите ваши объекты. Этот подход вставляет код в конструктор объекта, который сообщает Spring, что он создается, чтобы Spring мог настроить новый экземпляр. Для этого требуется небольшая конфигурация в вашей сборке (например, компиляция с помощью ajc
) и включение Spring обработчиков конфигурации среды выполнения (@EnableSpringConfigured
с синтаксисом JavaConfig). Этот подход используется системой активной записи Roo, чтобы позволить экземплярам new
ваших объектов получать необходимую информацию о сохранении.
@Service
@Configurable
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService;
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}
Тег, который работает с помощью @Configurable
объекта службы: working-configurable
Ручной поиск bean: не рекомендуется
Этот подход подходит только для взаимодействия с устаревшим кодом в особых ситуациях. Почти всегда предпочтительнее создать одноэлементный класс адаптера, который может быть autowire, и код устаревшего кода может вызывать, но можно напрямую спросить контекст приложения Spring для bean.
Для этого вам нужен класс, к которому 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()
и получить beans, который ему нужен:
@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
Ответ 2
Если вы не кодируете веб-приложение, убедитесь, что ваш класс, в котором выполняется @Autowiring, является spring bean. Как правило, контейнер spring не будет знать класс, который мы можем считать spring bean. Мы должны сообщить контейнеру spring о наших классах spring.
Это может быть достигнуто путем настройки в appln-contxt или , лучший способ - аннотировать класс как @Component и не создавать аннотированный класс с использованием нового оператора.
Убедитесь, что вы получили его из контекста Appln, как показано ниже.
@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();
}
}
Ответ 3
На самом деле, вы должны использовать либо управляемые объекты 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);
}
}
Ответ 4
Однажды я столкнулся с той же проблемой, когда я не совсем привык к the life in the IoC world
. Поле @Autowired
одного из моих beans равно нулю во время выполнения.
Основная причина заключается в том, что вместо использования автоматически созданного bean, поддерживаемого контейнером IoC Spring (чье поле @Autowired
indeed
правильно введено), я newing
мой собственный экземпляр этого bean введите и используйте его. Конечно, это одно @Autowired
поле является нулевым, потому что Spring не имеет возможности его ввести.
Ответ 5
Ваша проблема новая (создание объекта в стиле 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());
}
}
}
}
Ответ 6
Кажется, это редкий случай, но вот что случилось со мной:
Мы использовали @Inject
вместо @Autowired
который является стандартом javaee, поддерживаемым Spring. Во всех местах он работал нормально, и бобы вводили правильно, а не в одном месте. Инъекция бобов кажется такой же
@Inject
Calculator myCalculator
Наконец, мы обнаружили, что ошибка заключалась в том, что мы (фактически, функция автозаполнения Eclipse) импортировали com.opensymphony.xwork2.Inject
вместо javax.inject.Inject
!
@Autowired
итог, убедитесь, что ваши аннотации (@Autowired
, @Inject
, @Service
,...) содержат правильные пакеты!
Ответ 7
Я новичок в Spring, но я нашел это рабочее решение. Пожалуйста, скажите мне, если это неприемлемо.
Я делаю Spring вставлять applicationContext
в этот bean:
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);
Таким образом любой bean может быть получен любым объектом в приложении (также запутанным с new
) и статическим способом.
Ответ 8
Другим решением будет вызов:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Для конструктора MileageFeeCalculator:
@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());
}
}
Ответ 9
Я думаю, что вы пропустили команду spring для сканирования классов с аннотацией.
Вы можете использовать @ComponentScan("packageToScan")
в классе конфигурации вашего приложения spring, чтобы проинструктировать spring для сканирования.
@Service, @Component
и т.д. аннотация добавляет мета-описание.
Spring только вводит экземпляры тех классов, которые либо создаются как bean, либо помечены аннотацией.
Классы, отмеченные аннотацией, должны быть идентифицированы spring перед введением, @ComponentScan
инструктировать spring искать классы, помеченные аннотацией. Когда spring находит @Autowired
, он ищет соответствующий bean и вводит требуемый экземпляр.
Добавление только аннотации, не исправление или облегчение инъекции зависимостей, spring должно знать, где искать.
Ответ 10
Если это происходит в тестовом классе, убедитесь, что вы не забыли аннотировать класс.
Например, в Spring Boot:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
....
Ответ 11
Вы также можете исправить эту проблему, используя аннотацию @Service в классе службы и передав требуемый bean classA в качестве параметра в другой конструктор класса beans classB и аннотируем конструктор класса B с помощью @Autowired. Пример фрагмента здесь:
@Service
public class ClassB {
private ClassA classA;
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}
public void useClassAObjectHere(){
classA.callMethodOnObjectA();
}
}
Ответ 12
ОБНОВЛЕНИЕ: действительно умные люди быстро указали на этот ответ, который объясняет странность, описанную ниже
ОРИГИНАЛЬНЫЙ ОТВЕТ:
Я не знаю, помогает ли это кому-нибудь, но я застрял с той же проблемой, даже когда делал все правильно. В моем методе 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"/>
и это работает. Может быть, кто-то может дать объяснение, но для меня этого достаточно прямо сейчас)
Ответ 13
Это является MileageFeeCalculator calc = new MileageFeeCalculator();
выдачи NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();
Мы используем Spring - не нужно создавать объекты вручную. За созданием объекта позаботится контейнер IoC.
Ответ 14
Также обратите внимание, что если по какой-либо причине вы сделаете метод в @Service
качестве final
, то автоматически настроенные bean-компоненты, к которым вы получите доступ, всегда будут иметь значение null
.
Ответ 15
То, что не было упомянуто здесь, описано в этой статье в параграфе "Порядок исполнения".
После "изучения" того, что я должен был аннотировать класс с помощью @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
}
}