Dlaczego moje pole Spring @Autowired ma wartość NULL?


609

Uwaga: ma to być kanoniczna odpowiedź na typowy problem.

Mam @Serviceklasę Spring ( MileageFeeCalculator), która ma @Autowiredpole ( rateService), ale pole jest nullwtedy, gdy próbuję go użyć. Dzienniki pokazują, że zarówno MileageFeeCalculatorkomponent bean, jak i MileageRateServicekomponent bean są tworzone, ale pojawia się za NullPointerExceptionkażdym razem, gdy próbuję wywołać mileageChargemetodę w komponencie bean usługi. Dlaczego Spring nie jest autowirerem na polu?

Klasa kontrolera:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

Klasa usług:

@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
    }
}

Komponent bean, który powinien być automatycznie dodany, MileageFeeCalculatorale nie jest to:

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

Kiedy próbuję GET /mileage/3, otrzymuję ten wyjątek:

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
Innym scenariuszem może być Fwywołanie komponentu bean wewnątrz konstruktora innej fasoli S. W takim przypadku przekaż wymaganą fasolę Fjako parametr do innego Skonstruktora fasoli i dodaj adnotację do konstruktora za Spomocą @Autowire. Pamiętaj, aby opisywanie klasy pierwszej fasoli Fz @Component.
aliopi,

Tutaj napisałem kilka przykładów bardzo podobnych do tego, używając Gradle: github.com/swimorsink/spring-aspectj-examples . Mam nadzieję, że ktoś uzna to za przydatne.
Ross117,

Odpowiedzi:


649

Pole opatrzone adnotacjami @Autowiredjest nullspowodowane tym, że Spring nie wie o kopii MileageFeeCalculator, którą utworzyłeś newi nie wiedział, jak ją automatycznie zapisać.

Kontener Spring Inversion of Control (IoC) ma trzy główne elementy logiczne: rejestr (zwany ApplicationContext) komponentami (komponentami bean), które są dostępne do użycia przez aplikację, system konfiguracyjny, który wstrzykuje do nich zależności obiektów poprzez dopasowanie zależności z komponentami bean w kontekście oraz solver zależności, który może spojrzeć na konfigurację wielu różnych komponentów bean i określić, jak utworzyć ich instancję i skonfigurować je w niezbędnej kolejności.

Kontener IoC nie jest magią i nie ma możliwości poznania obiektów Java, chyba że w jakiś sposób o nich poinformujesz. Podczas wywoływania newJVM tworzy kopię nowego obiektu i przekazuje go prosto do Ciebie - nigdy nie przechodzi przez proces konfiguracji. Istnieją trzy sposoby skonfigurowania fasoli.

Wysłałem cały ten kod, używając Spring Boot do uruchomienia, w tym projekcie GitHub ; możesz spojrzeć na działający projekt dla każdego podejścia, aby zobaczyć wszystko, czego potrzebujesz, aby to zadziałało. Oznacz za pomocą NullPointerException:nonworking

Wstrzyknij fasolę

Najkorzystniejszą opcją jest zezwolenie Springowi na automatyczne sterowanie wszystkimi twoimi ziarnami; wymaga to najmniejszej ilości kodu i jest najbardziej łatwe w utrzymaniu. Aby autouzupełnianie działało tak, jak chciałeś, wykonaj również autouzupełnianie w MileageFeeCalculatornastępujący sposób:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

Jeśli musisz utworzyć nową instancję obiektu usługi dla różnych żądań, możesz nadal używać wstrzykiwania za pomocą zakresów komponentu Spring bean .

Tag, który działa poprzez wstrzyknięcie @MileageFeeCalculatorobiektu usługi:working-inject-bean

Użyj @Configurable

Jeśli naprawdę potrzebujesz obiektów utworzonych w newcelu automatycznego tworzenia, możesz użyć @Configurableadnotacji Spring wraz z tkaniem czasu kompilacji AspectJ do wstrzyknięcia obiektów. To podejście wstawia kod do konstruktora obiektu, który ostrzega Springa, że ​​jest tworzony, aby Spring mógł skonfigurować nowe wystąpienie. Wymaga to nieco konfiguracji w kompilacji (np. Kompilacji ajc) i włączenia procedur konfiguracyjnych środowiska wykonawczego Springa ( @EnableSpringConfiguredze składnią JavaConfig). Podejście to jest stosowane przez system Roo Active Record, aby umożliwić newinstancjom twoich jednostek uzyskanie niezbędnych informacji o trwałości.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

Tag, który działa przy użyciu @Configurableobiektu usługi:working-configurable

Ręczne wyszukiwanie fasoli: niezalecane

To podejście nadaje się tylko do łączenia ze starszym kodem w szczególnych sytuacjach. Prawie zawsze preferowane jest utworzenie klasy adaptera singleton, którą Spring może automatycznie uruchomić, a starszy kod może wywoływać, ale możliwe jest bezpośrednie zapytanie kontekstu aplikacji Spring o komponent bean.

Aby to zrobić, potrzebujesz klasy, do której Spring może podać odwołanie do ApplicationContextobiektu:

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

Wówczas Twój starszy kod może wywoływać getContext()i pobierać potrzebne ziarna:

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

Tag, który działa poprzez ręczne wyszukiwanie obiektu usługi w kontekście Spring: working-manual-lookup


1
Inną rzeczą, na którą należy spojrzeć, jest tworzenie obiektów dla ziaren w @Configurationfasoli, w których opisana jest metoda tworzenia instancji określonej klasy fasoli @Bean.
Donal Fellows

@DonalFellows Nie jestem do końca pewien, o czym mówisz („tworzenie” jest dwuznaczne). Czy mówisz o problemie z wieloma wywołaniami @Beanmetod podczas korzystania z AOP Spring Proxy?
Chrylis

1
Cześć, napotykam na podobny problem, jednak kiedy używam twojej pierwszej sugestii, moja aplikacja uważa, że ​​„calc” jest zerowe podczas wywoływania metody „mileageFee”. To tak, jakby nigdy nie inicjował @Autowired MileageFeeCalculator calc. jakieś pomysły?
Theo

Myślę, że powinieneś dodać pozycję na górze odpowiedzi, która wyjaśnia, że ​​odzyskanie pierwszej fasoli, katalogu głównego, z którego robisz wszystko, powinno odbywać się za pośrednictwem ApplicationContext. Niektórzy użytkownicy (dla których zamknąłem się jako duplikaty) nie rozumieją tego.
Sotirios Delimanolis

@SotiriosDelimanolis Proszę wyjaśnić problem; Nie jestem pewien, co dokładnie robisz.
Chrylis

59

Jeśli nie kodujesz aplikacji internetowej, upewnij się, że twoja klasa, w której wykonano @Autowiring, jest fasolką szparagową. Zazwyczaj wiosenny pojemnik nie będzie świadomy klasy, którą moglibyśmy uznać za wiosenną fasolę. Musimy powiedzieć pojemnikowi wiosennemu o naszych klasach wiosennych.

Można to osiągnąć przez skonfigurowanie w appln-contxt lub lepszym sposobem jest opatrzenie klasy adnotacją @Component i nie należy tworzyć klasy z adnotacjami za pomocą nowego operatora. Upewnij się, że pobierasz go z kontekstu Appln, jak poniżej.

@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();

    }

}

cześć, przejrzałem twoje rozwiązanie, to prawda. I tutaj chciałbym wiedzieć „Dlaczego nie tworzymy wystąpienia klasy z adnotacjami za pomocą nowego operatora, czy mogę znać przyczynę tego.
Ashish

3
jeśli utworzysz obiekt za pomocą nowego, będziesz obsługiwał cykl życia fasoli, co jest sprzeczne z koncepcją MKOl. Musimy poprosić kontener o zrobienie tego, co robi to lepiej
Shirish Coolkarni

41

W rzeczywistości należy używać obiektów zarządzanych przez JVM lub obiektów zarządzanych przez Spring do wywoływania metod. z powyższego kodu w klasie kontrolera tworzysz nowy obiekt, który ma wywoływać klasę usługi, która ma obiekt z okablowaniem automatycznym.

MileageFeeCalculator calc = new MileageFeeCalculator();

więc to nie zadziała w ten sposób.

Rozwiązanie sprawia, że ​​ten MileageFeeCalculator jest obiektem z automatycznym okablowaniem w samym kontrolerze.

Zmień klasę kontrolera jak poniżej.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

4
Oto odpowiedź. Ponieważ samodzielnie tworzysz nowy MilageFeeCalculator, Spring nie bierze udziału w tworzeniu, więc Spring Spring nie wie, że obiekt istnieje. Dlatego nic nie może na to poradzić, na przykład wstrzykiwać zależności.
Robert Greathouse,

26

Kiedyś napotkałem ten sam problem, kiedy nie byłem do tego przyzwyczajony the life in the IoC world. @AutowiredPole jednego z moich fasoli jest null w czasie wykonywania.

Główną przyczyną jest to, że zamiast używać automatycznie utworzonej fasoli obsługiwanej przez kontener Spring IoC (której @Autowiredpole zostało indeedpoprawnie wstrzyknięte), jestem newingwłasną instancją tego typu fasoli i korzystam z niej. Oczywiście pole tego @Autowiredpola jest puste, ponieważ Wiosna nie ma szans na wstrzyknięcie go.


22

Twój problem jest nowy (tworzenie obiektów w stylu Java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Z dopiskiem @Service, @Component, @Configurationfasola są tworzone w
kontekście aplikacji wiosny, gdy serwer jest uruchomiony. Ale kiedy tworzymy obiekty za pomocą nowego operatora, obiekt nie jest rejestrowany w kontekście aplikacji, który został już utworzony. Na przykład klasy Employee.java, z której korzystałem.

Spójrz na to:

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

Jestem nowy w Spring, ale odkryłem to działające rozwiązanie. Proszę powiedz mi, czy jest to niefortunny sposób.

Robię Spring iniekcji applicationContextw tę fasolę:

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

Możesz umieścić ten kod także w głównej klasie aplikacji, jeśli chcesz.

Inne klasy mogą używać tego w następujący sposób:

MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);

W ten sposób można uzyskać dowolną fasolę przez dowolny obiekt w aplikacji (również z intencją new) i w sposób statyczny .


1
Ten wzór jest konieczny, aby ziarna fasoli były dostępne dla starszego kodu, ale należy tego unikać w nowym kodzie.
Chrylis

2
Nie jesteś nowy na wiosnę. Jesteś zawodowcem. :)
sapy

uratowałeś mnie ...
Govind Singh

W moim przypadku wymagałem tego, ponieważ było kilka zajęć stron trzecich. Wiosna (MKOl) nie miała nad nimi kontroli. Te klasy nigdy nie były wywoływane z mojej wiosennej aplikacji rozruchowej. Zastosowałem to podejście i zadziałało to dla mnie.
Joginder Malik

12

Wydaje się, że to rzadki przypadek, ale oto co się ze mną stało:

Użyliśmy @Injectzamiast tego @Autowiredstandardu javaee obsługiwanego przez Spring. Każde miejsce działało dobrze, a fasola była wstrzykiwana poprawnie, zamiast jednego miejsca. Zastrzyk fasoli wydaje się taki sam

@Inject
Calculator myCalculator

W końcu okazało się, że błąd polegał na tym, że zaimportowaliśmy (a właściwie funkcję automatycznego uzupełniania Eclipse) com.opensymphony.xwork2.Injectzamiast javax.inject.Inject!

Więc podsumować, upewnij się, że adnotacje ( @Autowired, @Inject, @Service, ...) mają odpowiednie pakiety!


5

Myślę, że nie zdążyłeś poinstruować wiosny, aby skanowała zajęcia z adnotacjami.

Możesz użyć @ComponentScan("packageToScan")w klasie konfiguracji aplikacji sprężynowej, aby poinstruować sprężynę, aby skanowała.

@Service, @Component adnotacje itp. dodają opis meta.

Wiosna wstrzykuje tylko wystąpienia tych klas, które są albo tworzone jako fasola, albo oznaczone adnotacją.

Klasy oznaczone adnotacją muszą być identyfikowane przez wiosnę przed wstrzyknięciem, @ComponentScanpoinstruuj , aby szukały klas oznaczone adnotacją. Gdy Spring znajdzie @Autowired, szuka powiązanej fasoli i wstrzykuje wymaganą instancję.

Dodanie tylko adnotacji, nie naprawia ani nie ułatwia wstrzykiwania zależności, Spring musi wiedzieć, gdzie szukać.


wpadłem na to, gdy zapomniałem dodać <context:component-scan base-package="com.mypackage"/>do mojego beans.xmlpliku
Ralph Callaway

5

Jeśli dzieje się tak na zajęciach testowych, upewnij się, że nie zapomniałeś dodać komentarza do zajęć.

Na przykład w Spring Boot :

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

Minęło trochę czasu ...

Spring Boot ewoluuje . Nie jest już wymagane, aby używać @RunWith poprawnej wersji JUnit .

Dla @SpringBootTestdo pracy stand alone, trzeba użyć @Testz JUnit5 zamiast JUnit4 .

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

@SpringBootTest
public class MyTests {
    ....

Jeśli pojawi się taka konfiguracja źle twoje testy skompiluje, ale @Autowiredi @Valuepola (na przykład) będzie null. Ponieważ Spring Boot działa magicznie, możesz mieć kilka możliwości bezpośredniego debugowania tej awarii.



Uwaga: @Valuebędzie zerowy, jeśli zostanie użyty z staticpolami.
nobar

Wiosna zapewnia wiele sposobów niepowodzenia (bez pomocy kompilatora). Kiedy coś pójdzie nie tak, najlepiej jest powrócić do punktu wyjścia - używając tylko kombinacji adnotacji, o których wiesz, że będą działać razem.
nobar

4

Innym rozwiązaniem byłoby wywołanie: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Konstruktor MileageFeeCalculator w następujący sposób:

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

Wykorzystuje to niebezpieczną publikację.
Chrylis

3

AKTUALIZACJA: Naprawdę sprytni ludzie szybko wskazali na odpowiedź, co wyjaśnia dziwność opisaną poniżej

ORYGINALNA ODPOWIEDŹ:

Nie wiem, czy to komukolwiek pomaga, ale utknąłem z tym samym problemem, nawet robiąc rzeczy pozornie właściwe. W mojej metodzie Main mam taki kod:

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {
        "common.xml",
        "token.xml",
        "pep-config.xml" });
    TokenInitializer ti = context.getBean(TokenInitializer.class);

aw token.xmlpliku mam linię

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

Zauważyłem, że pakiet.path już nie istnieje, więc porzuciłem linię na dobre.

A potem zaczęła się pojawiać NPE. pep-config.xmlMiałem tylko 2 ziarna:

<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

a klasa SomeAbac ma właściwość zadeklarowaną jako

@Autowired private Settings settings;

z jakiegoś nieznanego powodu ustawienia są puste w init (), kiedy <context:component-scan/>element w ogóle nie jest obecny, ale gdy jest obecny i ma trochę bs jako pakietu podstawowego, wszystko działa dobrze. Ta linia wygląda teraz tak:

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

i to działa. Może ktoś może wyjaśnić, ale dla mnie to wystarczy teraz)


5
Ta odpowiedź jest wyjaśnieniem. <context:component-scan/>pośrednio umożliwia <context:annotation-config/>niezbędne @Autowireddo pracy.
ForNeVeR

3

To jest przyczyna podania wyjątku NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();Używamy Springa - nie trzeba ręcznie tworzyć obiektu. Za tworzenie obiektów zajmie się kontener IoC.


2

Możesz również rozwiązać ten problem, używając adnotacji @Service w klasie usług i przekazując wymaganą klasę fasoli A jako parametr do innego konstruktora klasy bean w fasoli i opatrz konstruktorem klasy B adnotacją @Autowired. Przykładowy fragment tutaj:

@Service
public class ClassB {

    private ClassA classA;

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

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}

to działało dla mnie, ale czy możesz wyjaśnić, w jaki sposób to rozwiązuje problem?
CruelEngine

1
@CruelEngine, spójrz, to jest wstrzyknięcie do konstruktora (gdzie jawnie ustawiasz obiekt) zamiast po prostu zastrzyku w polu (odbywa się to głównie przez konfigurację sprężyny). Więc jeśli tworzysz obiekt klasy B za pomocą „nowego” operatora to jakiś inny zakres, to nie byłby on widoczny ani ustawiony automatycznie dla klasy A. Dlatego podczas wywoływania classB.useClassAObjectHere () wyrzuciłby NPE, ponieważ obiekt classA nie byłby automatycznie zapisany, jeśli tylko zadeklarujesz pole Injection. Czytaj Chrylis próbuje to wyjaśnić. I dlatego zaleca się wstrzykiwanie konstruktora zamiast wstrzykiwania w terenie. Czy to ma teraz sens?
Abhishek

1

To, czego tu nie wymieniono, opisano w tym artykule w akapicie „Kolejność wykonania”.

Po „nauczeniu się”, że muszę adnotować klasę za pomocą @Component lub pochodnych @Service lub @Repository (wydaje mi się, że jest ich więcej), aby automatycznie włączyć inne komponenty w nich, uderzyło mnie, że te inne komponenty wciąż są puste w konstruktorze komponentu macierzystego.

Korzystanie z @PostConstruct rozwiązuje:

@SpringBootApplication
public class Application {
    @Autowired MyComponent comp;
}

i:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

    public MyComponent() {
        // dao is null here
    }

    @PostConstruct
    public void init() {
        // dao is initialized here
    }
}

1

Jest to ważne tylko w przypadku testu jednostkowego.

Moja klasa usługi miała adnotację o usłudze i była to @autowiredkolejna klasa komponentów. Kiedy testowałem, klasa komponentów była zerowa. Ponieważ dla klasy usług tworzyłem obiekt za pomocąnew

Jeśli piszesz test jednostkowy, upewnij się, że nie tworzysz obiektu za pomocą new object(). Użyj zamiast tego zastrzyku.

To naprawiło mój problem. Oto przydatny link


0

Zauważ też, że jeśli, z jakiegokolwiek powodu, stworzysz metodę w @Serviceas final, automatycznie przetwarzane fasole, do których będziesz mieć dostęp, zawsze będą null.


0

Krótko mówiąc, istnieją dwa powody, dla których @Autowiredpole powinno byćnull

  • TWOJA KLASA NIE JEST ZIARNA WIOSENNEGO.

  • POLA NIE JEST ZIARNA.


0

Nie do końca związane z pytaniem, ale jeśli wstrzyknięcie pola jest zerowe, wstrzyknięcie oparte na konstruktorze nadal będzie działać dobrze.

    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;
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.