Почему мое поле 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 нет шансов ввести его.

smwikipedia
источник
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 должно знать, где искать.

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

nobar
источник
Смотрите также: 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"/>

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

62mkv
источник
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
    }
}
JackLeEmmerdeur
источник
1

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

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

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

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

Ришабх Агарвал
источник
0

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

yglodt
источник
0

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

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

  • ПОЛЕ НЕ БИН.

samuelj90
источник
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;
    }
Чакладер Асфак Арефе
источник