Чому моє поле Spring @Autowired недійсне?


609

Примітка. Це призначено для канонічної відповіді на поширену проблему.

У мене є @Serviceклас Spring ( MileageFeeCalculator), у якому є @Autowiredполе ( rateService), але це поле, nullколи я намагаюся його використовувати. Журнали показують, що і MileageFeeCalculatorквасоля, і MileageRateServiceбоб створюються, але я отримую NullPointerExceptionщоразу, коли намагаюся викликати mileageChargeметод на моєму службовому бобі. Чому весна не проводить автоматичне підключення поля?

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

@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
Інший сценарій може бути, коли bean Fвикликається всередині конструктора іншого S. У цьому випадку передайте необхідний квасоля Fяк параметр іншому Sконструктору бобів та анотуйте конструктор Sз @Autowire. Пам'ятайте , анотувати клас першого бобу Fз @Component.
aliopi

Я зашифрував кілька прикладів, дуже схожих на цей, використовуючи тут Gradle: github.com/swimorsink/spring-aspectj-examples . Сподіваємось, хтось знайде це корисним.
Ross117

Відповіді:


649

Поле зазначено @Autowiredв nullтому, що Spring не знає про копію, MileageFeeCalculatorяку ви створили, newі не знав, щоб її автоматично провести.

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

Контейнер IoC не є магічним, і він не може знати про об'єкти Java, якщо ви якось не інформуєте про них. Коли ви телефонуєте new, JVM створює копію нового об'єкта і передає його вам прямо - він ніколи не проходить процес конфігурації. Існує три способи налаштування бобів.

Я опублікував весь цей код, використовуючи 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);
    }
}

Якщо вам потрібно створити новий примірник вашого сервісного об’єкта для різних запитів, ви все одно можете використовувати ін'єкцію за допомогою діапазонів Spring Bean .

Тег, який працює шляхом введення @MileageFeeCalculatorоб’єкта обслуговування:working-inject-bean

Використовуйте @Configurable

Якщо вам справді потрібні об'єкти, створені за допомогою newавтоматичного з'єднання, ви можете використати весняну @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);
    }
}

Тег, який працює, шукаючи об’єкт послуги вручну в контексті весни: working-manual-lookup


1
Інша річ, на яку слід звернути увагу, - це зробити об’єкти для квасолі в @Configurationквасолі, де анотований метод створення екземпляра певного класу бобів @Bean.
Дональні стипендіати

@DonalFellows Я не зовсім впевнений, про що ви говорите ("робити" неоднозначно). Ви говорите про проблему з численними викликами до @Beanметодів під час використання Spring Proxy AOP?
chrylis -обережнооптимістично-

1
Привіт, я зіткнувся з подібною проблемою, проте коли я використовую першу пропозицію, моя програма вважає, що "calc" є нульовим при виклику методу "mileageFee". Це так, ніби він ніколи не ініціалізує @Autowired MileageFeeCalculator calc. Будь-які думки?
Тео

Я думаю, ви повинні додати запис у верхній частині вашої відповіді, що пояснює, що отримання першого квасолі, кореня, з якого ви все робите, слід робити через ApplicationContext. Деякі користувачі (для яких я закрив як копії) цього не розуміють.
Сотіріос Деліманоліс

@SotiriosDelimanolis Будь ласка, поясніть проблему; Я не впевнений, що саме ви думаєте.
хриліс -обережнооптимістично-

59

Якщо ви не кодуєте веб-додаток, переконайтеся, що ваш клас, в якому виконується @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();

    }

}

привіт, я пережив ваше рішення, це правильно. І тут я хотів би знати "Чому ми не створюємо екземпляр анотованого класу за допомогою нового оператора, чи можу я знати причину цього?"
Ашиш

3
якщо ви створите об'єкт за допомогою нового, ви будете обробляти життєвий цикл бобів, що суперечить концепції МОК. Нам потрібно попросити контейнер зробити це, що робить це краще
Shirish Coolkarni

41

Насправді для виклику методів слід використовувати об'єкти, керовані JVM, або керовані Spring. з наведеного вище коду у вашому класі контролера ви створюєте новий об’єкт для виклику свого класу обслуговування, у якого є об'єкт з автоматичним з'єднанням.

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);
    }
}

4
Це відповідь. Оскільки ви самостійно створюєте новий MilageFeeCalculator, 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боби створюються в
контексті програми Весна при запуску сервера. Але коли ми створюємо об'єкти за допомогою нового оператора, об'єкт не реєструється в контексті програми, який вже створений. Для прикладу клас 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, але я знайшов це робоче рішення. Скажіть, будь ласка, чи це бездоганний спосіб.

Я роблю весну впорскувати 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
Цей зразок необхідний для того, щоб зробити весняні боби доступними для застарілого коду, але їх слід уникати в новому коді.
хриліс -обережнооптимістично-

2
Ви не новачок для Весни. Ви професіонал. :)
сап

ти врятував мене ...
Говінд Сінгх

У моєму випадку я цього вимагав, оскільки сторонніх класів було мало. Весна (МОК) не мала контролю над ними. Ці заняття ніколи не викликали з мого додатка для весняного завантаження. Я дотримувався такого підходу, і він працював на мене.
Joginder Malik

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 вводить лише екземпляри тих класів, які створені як бобові, або позначені анотацією.

Класи, позначені анотацією, повинні бути визначені весною перед введенням, @ComponentScanдоручити весному шукати класи, позначені анотацією. Коли Spring знаходить, @Autowiredвін шукає пов'язаний боб і вводить потрібний екземпляр.

Додаючи лише анотацію, не виправляє та не полегшує введення залежності, Весна повинна знати, де її шукати.


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

5

Якщо це відбувається в тестовому класі, переконайтеся, що ви не забули коментувати клас.

Наприклад, у Spring Boot :

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

Проходить деякий час ...

Весняний черевик продовжує розвиватися . Його більше не потрібно використовувати, @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полями.
nobar

Spring пропонує безліч способів відмовити (без допомоги компілятора). Коли все піде не так, найкраще повернутися до прямого - використовуючи лише комбінацію приміток, які, як ви знаєте, працюватимуть разом.
nobar

4

Іншим рішенням буде виклик: 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()); 
    }
}

Для цього використовується небезпечна публікація.
chrylis -обережнооптимістично-

3

ОНОВЛЕННЯ: Дійсно розумні люди швидко поспілкувалися цьому відповідь, що пояснює дивацтва, описане нижче

ОРИГІНАЛЬНИЙ ВІДПОВІДЬ:

Я не знаю, чи це комусь допомагає, але я застряг з тією ж проблемою, навіть коли робив речі, здавалося б, правильно. У своєму головному методі у мене є такий код:

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;

з незрозумілої причини параметри nul в init (), коли <context:component-scan/>елемент взагалі відсутній, але коли він присутній і має деякі bs як базовий пакет, все працює добре. Цей рядок зараз виглядає приблизно так:

<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 на клас обслуговування та передаючи необхідний клас квасолі A як параметр іншому конструктору classBB класу та примітити конструктор 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

Це справедливо лише у випадку одиничного тесту.

Мій клас обслуговування мав примітку про службу, і це був @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.