Почему мое поле 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)
    ...
360
задан chrylis 11 нояб. '13 в 3:05
источник поделиться

11 ответов

Поле, аннотированное @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

435
ответ дан chrylis 11 нояб. '13 в 3:05
источник поделиться

Если вы не кодируете веб-приложение, убедитесь, что ваш класс, в котором выполняется @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();

    }

}
38
ответ дан Shirish Coolkarni 22 апр. '14 в 9:47
источник поделиться

Однажды я столкнулся с той же проблемой, когда я не совсем привык к the life in the IoC world. Поле @Autowired одного из моих beans равно нулю во время выполнения.

Основная причина заключается в том, что вместо использования автоматически созданного bean, поддерживаемого контейнером IoC Spring (чье поле @Autowired indeed правильно введено), я newing мой собственный экземпляр этого bean введите и используйте его. Конечно, это одно @Autowired поле является нулевым, потому что Spring не имеет возможности его ввести.

18
ответ дан smwikipedia 12 нояб. '15 в 16:41
источник поделиться

На самом деле вы должны использовать либо управляемые объекты JVM, либо Spring managed Object для вызова методов. из вашего вышеуказанного кода в классе контроллера вы создаете новый объект для вызова своего класса сервиса, у которого есть автообученный объект.

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);
    }
}
13
ответ дан Ravi Durairaj 26 июля '16 в 12:25
источник поделиться

Ваша проблема новая (создание объекта в стиле java)

 MileageFeeCalculator calc = new MileageFeeCalculator();

С аннотацией @Service, @Component, @Configuration beans создаются в   контекст приложения Spring при запуске сервера. Но когда мы создаем объекты   используя новый оператор, объект не зарегистрирован в контексте приложения, который уже создан. Для примера используется класс 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());
        }
    }
}

}
8
ответ дан Deepak 08 окт. '15 в 12:17
источник поделиться

Я новичок в 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) и статическим способом.

6
ответ дан bluish 14 мая '15 в 15:44
источник поделиться

ОБНОВЛЕНИЕ: Действительно умные люди быстро указали на этот ответ, который объясняет странность, описанную ниже

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Я не знаю, помогает ли это кому-либо, но я был застрял с той же проблемой, даже когда делал что-то вроде правды. В моем основном методе у меня есть такой код:

        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 beans:

<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

а класс SomeAbac имеет свойство, объявленное как

    @Autowired private Settings settings;

по неизвестной причине, настройки имеют значение null в init(), когда элемент <context:component-scan/> вообще отсутствует, но когда он присутствует и имеет некоторые bs в качестве basePackage, все работает хорошо. Теперь эта строка выглядит так:

    <context:component-scan base-package="some.shit"/>

и он работает. Может быть, кто-то может дать объяснение, но для меня это достаточно прямо сейчас)

3
ответ дан 62mkv 12 окт. '17 в 7:43
источник поделиться

Я думаю, что вы пропустили команду spring для сканирования классов с аннотацией.

Вы можете использовать @ComponentScan("packageToScan") в классе конфигурации вашего приложения spring, чтобы проинструктировать spring для сканирования.

@Service, @Component и т.д. аннотация добавляет мета-описание.

Spring только вводит экземпляры тех классов, которые либо создаются как bean, либо помечены аннотацией.

Классы, отмеченные аннотацией, должны быть идентифицированы spring перед введением, @ComponentScan инструктировать spring искать классы, помеченные аннотацией. Когда spring находит @Autowired, он ищет соответствующий bean и вводит требуемый экземпляр.

Добавление только аннотации, не исправление или облегчение инъекции зависимостей, spring должно знать, где искать.

3
ответ дан msucil 10 янв. '17 в 21:00
источник поделиться

Кажется, что это редкий случай, но вот что со мной произошло:

Мы использовали @inject вместо @autowired, который является стандартом javaee, поддерживаемым spring. В каждом месте он работал нормально, а beans вводился правильно, а не в одном месте. Инъекция bean кажется тем же самым

@inject
Calculator myCalculator

Наконец, мы обнаружили, что ошибка заключалась в том, что мы (фактически, функция eclipse ide out) импортировали com.opensymphony.xwork2.Inject вместо javax.inject.Inject!

Итак, чтобы подвести итог, убедитесь, что ваши аннотации (@autowired, @inject, @Service,...) имеют правильные пакеты!

2
ответ дан Alireza Fattahi 11 июня '16 в 18:15
источник поделиться

Другим решением будет вызов: 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()); 
    }
}
2
ответ дан Ondrej Bozek 21 апр. '15 в 14:35
источник поделиться

Вы также можете исправить эту проблему, используя аннотацию @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();
    }
}
1
ответ дан apandey846 13 окт. '16 в 21:41
источник поделиться

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