it-roy-ru.com

Почему мое поле Spring @Autowired имеет значение null?

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

У меня есть класс Spring @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)
    ...
497
chrylis

Поле @Autowired с комментариями - null, потому что Spring не знает о копии MileageFeeCalculator, которую вы создали с помощью new, и не знал, как ее автоматически связать.

Контейнер Spring Inversion of Control (IoC) имеет три основных логических компонента: реестр (называемый ApplicationContext) компонентов (бинов), которые доступны для использования приложением, система конфигуратора, которая внедряет зависимости объектов в сопоставляя их с bean-компонентами в контексте, и решателем зависимостей, который может посмотреть на конфигурацию множества различных bean-компонентов и определить, как создавать и настраивать их в нужном порядке.

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

Если вам нужно создать новый экземпляр вашего сервисного объекта для разных запросов, вы все равно можете использовать инъекцию, используя области действия bean-объекта 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

543
chrylis

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

Это может быть достигнуто путем настройки в appln-contxt или лучший способ аннотировать класс как @Component и, пожалуйста, не создавайте аннотированный класс с помощью оператора new . Убедитесь, что вы получаете его от 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();

    }

}
49
Shirish Coolkarni

На самом деле, вы должны использовать либо управляемые объекты 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);
    }
}
29
Ravi Durairaj

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

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

21
smwikipedia

Ваша проблема нова (создание объекта в стиле 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());
        }
    }
}

}
18
Deepak

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

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

@Inject
Calculator myCalculator

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

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

9
Alireza Fattahi

Я новичок в 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);

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

7
bluish

Другим решением будет размещение вызова: 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()); 
    }
}
4
Ondrej Bozek

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

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

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

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

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

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

4
msucil

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

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

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    ....
3
nobar

Вы также можете исправить эту проблему, используя аннотацию @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();
    }
}
2
apandey846

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

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

Я не знаю, помогает ли это кому-нибудь, но я застрял с той же проблемой, даже когда делал все правильно. В моем методе 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-компонента: 

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

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

2
62mkv

Также обратите внимание, что если по какой-либо причине вы сделаете метод в @Service как final, то с автоматическим подключением bean-компонентов, к которым вы получите к нему доступ, всегда будет null.

0
yglodt

Это является причиной предоставления NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator(); Мы используем Spring - не нужно создавать объект вручную. Создание объекта позаботится о контейнере IoC.

0
Atul Jain