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


608

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

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

3
Другой сценарий может быть, когда компонент Fвызывается внутри конструктора другого компонента S. В этом случае передайте необходимый bean-компонент Fв качестве параметра другому Sконструктору bean-компонентов и аннотируйте конструктор Swith @Autowire. Не забудьте аннотировать класс первого компонента Fс @Component.
— Алиопи

Я кодировал несколько примеров, очень похожих на этот, используя Gradle здесь: github.com/swimorsink/spring-aspectj-examples . Надеюсь, кто-то найдет это полезным.
— Ross117

Ответы:


649

Аннотированное поле @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автоматического подключения, вы можете использовать @Configurableаннотацию Spring вместе с переплетением во время компиляции 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


1
Другая вещь, на которую стоит обратить внимание, - создание объектов для bean-компонентов в @Configurationbean-компоненте, где аннотируется метод создания экземпляра определенного класса bean-компонента @Bean.
— Донал Феллоуз

@DonalFellows Я не совсем уверен, о чем ты говоришь («делать» неоднозначно). Вы говорите о проблеме с несколькими вызовами @Beanметодов при использовании Spring Proxy AOP?
— chrylis -cautiouslyoptimistic-

1
Привет, я сталкиваюсь с подобной проблемой, однако, когда я использую ваше первое предложение, мое приложение думает, что "calc" является нулевым при вызове метода "m milesFee". Как будто он никогда не инициализирует @Autowired MileageFeeCalculator calc. Какие-нибудь мысли?
— Тео

Я думаю, что вы должны добавить запись в верхней части вашего ответа, объясняющую, что получение первого бина, корня, из которого вы делаете все, должно выполняться через ApplicationContext. Некоторые пользователи (для которых я закрыт как дубликаты) не понимают этого.
— Сотириос Делиманолис,

@ SotiriosDelimanolis Пожалуйста, объясните проблему; Я не уверен, что именно вы делаете.
— chrylis -cautiouslyoptimistic-

59

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

    }

}

привет, я прошел через ваше решение, это правильно. И здесь я хотел бы знать: «Почему мы не создаем экземпляр аннотированного класса с использованием оператора new, могу я узнать причину этого»
— Ашиш

3
если вы создаете объект, используя new, вы будете обрабатывать жизненный цикл компонента, который противоречит концепции IOC. Нам нужно попросить контейнер сделать это, что делает это лучше
— Шириш Кулкарни

41

На самом деле, вы должны использовать либо управляемые объекты 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
Это ответ. Поскольку вы создаете экземпляр нового MilageFeeCalculator самостоятельно, Spring не участвует в создании экземпляра, поэтому Spring Spring не знает, что объект существует. Таким образом, он ничего не может с этим поделать, как вводить зависимости.
— Роберт Грейтхаус

26

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

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


22

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

MileageFeeCalculator calc = new MileageFeeCalculator();

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

}

12

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

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


1
Этот шаблон необходим, чтобы сделать бины Spring доступными для унаследованного кода, но его следует избегать в новом коде.
— chrylis -cautiouslyoptimistic-

2
Вы не новичок в весне. Вы профессионал. :)
— Сапи

ты спас меня ...
— Говинд Сингх

В моем случае я требовал этого, потому что было мало сторонних классов. Spring (IOC) не контролировал их. Эти классы никогда не вызывались из моего весеннего загрузочного приложения. Я придерживался этого подхода, и он работал для меня.
— Джогиндер Малик

12

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

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

@Inject
Calculator myCalculator

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

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


5

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

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

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

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

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

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


столкнулся с этим, когда я забыл добавить <context:component-scan base-package="com.mypackage"/>в мой beans.xmlфайл
— Ральф Каллавей

5

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

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

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

Прошло немного времени ...

Spring Boot продолжает развиваться . Больше не требуется использовать, @RunWith если вы используете правильную версию JUnit .

Для @SpringBootTestработать в одиночку стоять, вам нужно использовать @Testс JUnit5 вместо JUnit4 .

//import org.junit.Test; // JUnit4
import org.junit.jupiter.api.Test; // JUnit5

@SpringBootTest
public class MyTests {
    ....

Если вы ошиблись в этой конфигурации, ваши тесты будут скомпилированы, но @Autowiredи @Valueполя (например) будут null. Поскольку Spring Boot работает по волшебству, у вас может быть немного возможностей для прямой отладки этого сбоя.


Смотрите также: stackoverflow.com/questions/4130486/...
— nobar

Примечание: @Valueбудет нулевым при использовании с staticполями.
— Нобар

Spring предоставляет множество способов выхода из строя (без помощи компилятора). Когда что-то идет не так, лучше всего вернуться на круги своя, используя только те комбинации аннотаций, которые, как вы знаете, будут работать вместе.
— Нобар

4

Другое решение было бы поместить вызов: в 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()); 
    }
}

Это использует небезопасную публикацию.
— chrylis -cautiouslyoptimistic-

3

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

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

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

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


5
Этот ответ является объяснением. <context:component-scan/>неявно включает <context:annotation-config/>необходимые для @Autowiredработы.
— ForNeVeR

3

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


2

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

это сработало для меня, но не могли бы вы рассказать, как это решает проблему?
— CruelEngine

1
@CruelEngine, посмотрите, это инжекция в конструктор (где вы явно устанавливаете объект), а не просто использование инжекции поля (в основном это делается с помощью весенней конфигурации). Таким образом, если вы создаете объект ClassB с помощью оператора «new», это какая-то другая область видимости, которая не будет видна или будет автоматически установлена ​​для ClassA. Следовательно, при вызове classB.useClassAObjectHere () возникнет NPE, поскольку объект classA не был подключен автоматически, если вы просто объявите поле Injection. Читайте Хрилис, пытайтесь объяснить то же самое. И именно поэтому инжекция в конструктор рекомендуется по сравнению с инжекцией в поле. Имеет ли это смысл сейчас?
— Абхишек,

1

То, что не было упомянуто здесь, описано в этой статье в параграфе «Порядок исполнения».

После «изучения» того, что я должен был аннотировать класс с помощью @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

Это действительно только в случае модульного теста.

В моем классе Service была аннотация службы, и это был @autowiredдругой класс компонентов. Когда я тестировал класс компонента, он становился нулевым. Потому что для класса обслуживания я создавал объект, используяnew

Если вы пишете модульный тест, убедитесь, что вы не создаете объект с помощью new object(). Используйте вместо этого injectMock.

Это исправило мою проблему. Вот полезная ссылка


0

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


0

Проще говоря, есть две причины, по которым @Autowiredполе должно бытьnull

  • ВАШ КЛАСС НЕ ВЕСНА.

  • ПОЛЕ НЕ БИН.


0

Не совсем связанный с вопросом, но если инъекция поля равна нулю, инжекция на основе конструктора все равно будет работать нормально.

    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;
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.