NullPointerException возникает при использовании необязательных и загружаемых данных из базы данных [duplicate]

Примечание: это будет канонический ответ для общей проблемы.

У меня есть класс Spring @Service (MileageFeeCalculator), который имеет поле @Autowired (rateService) , но это поле null, когда я пытаюсь его использовать. Журналы показывают, что и компонент MileageFeeCalculator, и компонент MileageRateService создаются, но я получаю NullPointerException всякий раз, когда я пытаюсь вызвать метод mileageCharge на моем сервисном компоненте. Почему нет Spring autowiring в поле?

Класс контроллера:

@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)
    ...
441
задан 22 March 2017 в 20:24

10 ответов

UPDATE: Действительно умные люди быстро указали на этот ответ, что объясняет странность, описанную ниже

UPDATE:

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

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"/>

Я заметил, что пакет .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;

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

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

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

17
ответ дан 15 August 2018 в 16:41
  • 1
    Этот ответ является объяснением. <context:component-scan/> неявно позволяет <context:annotation-config/>, необходимый для работы @Autowired. – ForNeVeR 12 October 2017 в 07:55

Если вы не кодируете веб-приложение, убедитесь, что ваш класс, в котором выполняется @Autowiring, является весенним бобом. Как правило, весенний контейнер не будет знать о классе, который мы могли бы назвать весенним бобом. Мы должны рассказать весеннему контейнеру о наших весенних классах.

Этого можно достичь путем настройки в 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();

    }

}
45
ответ дан 15 August 2018 в 16:41
  • 1
    привет, я прошел через ваше решение, это правильно. И здесь я хотел бы знать, Почему мы не создаем экземпляр аннотированного класса с использованием нового оператора, могу ли я узнать причину этого. – Ashish 29 December 2014 в 15:30
  • 2
    если u создаст объект с использованием нового, u будет обрабатывать жизненный цикл компонента, что противоречит концепции IOC. Нам нужно попросить контейнер сделать это, что делает это лучше – Shirish Coolkarni 20 August 2015 в 06:04

Вы также можете исправить эту проблему, используя аннотацию @Service в классе службы и передав требуемый bean classA в качестве параметра в другой конструктор класса beans и создайте аннотацию конструктора класса B с помощью @Autowired. Пример фрагмента здесь:

@Service
public class ClassB {

    private ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}
2
ответ дан 15 August 2018 в 16:41
  • 1
    это сработало для меня, но можете ли вы рассказать о том, как это решает проблему? – CruelEngine 5 March 2018 в 16:58
  • 2
    @CruelEngine, посмотрите, что это инжектор конструктора (где вы явно устанавливаете объект) вместо того, чтобы просто использовать полевую инъекцию (в основном это делается с помощью конфигурации весны). Поэтому, если вы создаете объект ClassB, используя «новый», оператор - это еще одна область, которая не была бы видимой или автоподзадачной для ClassA. Следовательно, при вызове classB.useClassAObjectHere () будет вызывать NPE, поскольку объект classA не был автообновлен, если вы просто объявляете поле Injection. Читайте хрилис пытается объяснить то же самое. И это, почему инъекция конструктора рекомендуется для инъекций в поле. Это имеет смысл сейчас? – apandey846 11 April 2018 в 11:45
  • 3
    Теперь я понимаю . благодаря :) – CruelEngine 11 April 2018 в 15:28

Я новичок в Spring, но я обнаружил это рабочее решение. Скажите, пожалуйста, если это неприемлемо.

Я делаю 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);

Таким образом, любой bean-компонент может быть получен любым объектом в приложении (также инспирированным с new) и статическим способом.

7
ответ дан 15 August 2018 в 16:41
  • 1
    Этот шаблон необходим, чтобы сделать Spring beans доступным для устаревшего кода, но его следует избегать в новом коде. – chrylis 14 May 2015 в 16:06

На самом деле, вы должны использовать управляемые объекты 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);
    }
}
19
ответ дан 15 August 2018 в 16:41
  • 1
    Это ответ. Поскольку вы создаете новый MilageFeeCalculator самостоятельно, Spring не участвует в создании экземпляра, поэтому Spring Spring не знает, что объект существует. Таким образом, он ничего не может с этим поделать, например, инъекции зависимостей. – Robert Greathouse 19 September 2017 в 20:27

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

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

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

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

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

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

3
ответ дан 15 August 2018 в 16:41

Если это происходит в тестовом классе, убедитесь, что вы не забыли аннотировать класс.

Например, в Spring Boot:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    ....
1
ответ дан 15 August 2018 в 16:41

Еще одно решение - поставить вызов: 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
ответ дан 15 August 2018 в 16:41

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

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

21
ответ дан 15 August 2018 в 16:41
  • 1
    Рамка DI создана для таких людей, как мы (для меня), которые хотят больше за меньшее время, даже если у нас меньше знаний о предмете. Аналогичным примером является то, что мы можем сделать OOP в c, но Java был создан просто для упрощения для программиста, подобного мне, чтобы избежать ошибок. – Prakash Pandey 12 July 2017 в 18:27

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

Мы использовали @Inject вместо @Autowired, который является стандартом javaee, поддерживаемым Spring. В каждом месте он работал нормально, а бобы вводили правильно, а не в одно место. Инъекция компонента выглядит так же

@Inject
Calculator myCalculator

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

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

7
ответ дан 15 August 2018 в 16:41

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

Похожие вопросы: