Почему мое поле 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
    }
}