Почему мое поле 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, если вы не сообщите ему о них. Когда вы звоните newJVM создает копию нового объекта и передает ее вам - она ​​никогда не проходит через процесс настройки. Есть три способа настроить ваши 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
    }
}

Один из следующих будет работать:

  1. Класс, в котором вы используете @Autowired, не является Bean (я уверен, что где-то вы использовали new()).

  2. Внутри класса 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;
    }

Кроме того, не вводите static член, это будет null.

Если вы используете private метод, это будет null, попробуйте изменить private к public в контроллере.

Другие вопросы по тегам