Perché il mio campo Spring @Autowired è nullo?


608

Nota: questa è una risposta canonica a un problema comune.

Ho una @Serviceclasse Spring ( MileageFeeCalculator) che ha un @Autowiredcampo ( rateService), ma il campo è nullquando provo ad usarlo. I registri mostrano che vengono creati sia il MileageFeeCalculatorbean che il MileageRateServicebean, ma ottengo un NullPointerExceptionogni volta che provo a chiamare il mileageChargemetodo sul mio bean di servizio. Perché Spring non autorizza il campo?

Classe del controller:

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

Classe di servizio:

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

Bean di servizio che dovrebbe essere autowired, MileageFeeCalculatorma non è:

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

Quando provo a GET /mileage/3ottenere questa eccezione:

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
Un altro scenario può essere quando il bean Fviene chiamato all'interno del costruttore di un altro bean S. In questo caso, passare il bean richiesto Fcome parametro all'altro Scostruttore di bean e annotare il costruttore di Swith @Autowire. Ricorda di annotare la classe del primo bean Fcon @Component.
aliopi,

Ho codificato alcuni esempi molto simili a questo usando Gradle qui: github.com/swimorsink/spring-aspectj-examples . Spero che qualcuno lo troverà utile.
Ross117

Risposte:


649

Il campo annotato @Autowiredè nullperché Spring non è a conoscenza della copia MileageFeeCalculatorche hai creato newe non sapeva di autowire.

Il contenitore Spring Inversion of Control (IoC) ha tre componenti logici principali: un registro (chiamato ApplicationContext) di componenti (bean) disponibili per essere utilizzati dall'applicazione, un sistema configuratore che inietta in essi le dipendenze degli oggetti abbinando il dipendenze con bean nel contesto e un risolutore di dipendenze che può esaminare una configurazione di molti bean diversi e determinare come istanziarli e configurarli nell'ordine necessario.

Il contenitore IoC non è magico e non ha modo di conoscere gli oggetti Java se non lo si informa in qualche modo. Quando chiami new, JVM crea un'istanza di una copia del nuovo oggetto e te lo passa direttamente, non passa mai attraverso il processo di configurazione. Esistono tre modi per configurare i bean.

Ho pubblicato tutto questo codice, usando Spring Boot per il lancio, in questo progetto GitHub ; puoi guardare un progetto in piena esecuzione per ogni approccio per vedere tutto il necessario per farlo funzionare. Tagga con NullPointerException:nonworking

Iniettare i fagioli

L'opzione più preferibile è lasciare che Spring autowire tutti i tuoi bean; questo richiede la minima quantità di codice ed è il più gestibile. Per fare in modo che l'autowiring funzioni come desiderato, anche autowire in MileageFeeCalculatorquesto modo:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Se è necessario creare una nuova istanza dell'oggetto del servizio per richieste diverse, è comunque possibile utilizzare l'iniezione utilizzando gli ambiti Spring bean .

Tag che funziona iniettando l' @MileageFeeCalculatoroggetto del servizio:working-inject-bean

Usa @Configurable

Se hai davvero bisogno degli oggetti creati con newper essere autowired, puoi usare l' @Configurableannotazione Spring insieme alla tessitura in fase di compilazione AspectJ per iniettare i tuoi oggetti. Questo approccio inserisce il codice nel costruttore del tuo oggetto che avvisa Spring che viene creato in modo che Spring possa configurare la nuova istanza. Ciò richiede un po 'di configurazione nella build (come la compilazione con ajc) e l'attivazione dei gestori di configurazione runtime di Spring ( @EnableSpringConfiguredcon la sintassi JavaConfig). Questo approccio viene utilizzato dal sistema Roo Active Record per consentire alle newistanze delle entità di ottenere le informazioni di persistenza necessarie iniettate.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Tag che funziona utilizzando @Configurablel'oggetto del servizio:working-configurable

Ricerca bean manuale: non consigliata

Questo approccio è adatto solo per l'interfaccia con il codice legacy in situazioni speciali. È quasi sempre preferibile creare una classe di adattatori singleton che Spring può autowire e il codice legacy può chiamare, ma è possibile chiedere direttamente al contesto dell'applicazione Spring un bean.

Per fare ciò, è necessaria una classe a cui Spring può fornire un riferimento ApplicationContextall'oggetto:

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

Quindi il tuo codice legacy può chiamare getContext()e recuperare i bean di cui ha bisogno:

@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 che funziona cercando manualmente l'oggetto del servizio nel contesto Spring: working-manual-lookup


1
L'altra cosa da guardare è creare oggetti per bean in un @Configurationbean, in cui è annotato il metodo per creare un'istanza di una particolare classe di bean @Bean.
Donal Fellows,

@DonalFellows Non sono del tutto sicuro di cosa tu stia parlando ("fare" è ambiguo). Stai parlando di un problema con più chiamate ai @Beanmetodi quando usi Spring Proxy AOP?
Chrylis

1
Ciao, sto riscontrando un problema simile, tuttavia quando uso il tuo primo suggerimento, la mia applicazione pensa che "calc" sia nullo quando chiamo il metodo "mileageFee". È come se non inizializzasse mai il file @Autowired MileageFeeCalculator calc. qualche idea?
Theo,

Penso che dovresti aggiungere una voce nella parte superiore della tua risposta che spieghi che il recupero del primo bean, la radice da cui fai tutto, dovrebbe essere fatto attraverso il ApplicationContext. Alcuni utenti (per i quali ho chiuso come duplicati) non lo capiscono.
Sotirios Delimanolis,

@SotiriosDelimanolis Spiegare il problema; Non sono sicuro di che punto stai facendo.
Chrylis-cautiouslyoptimistic-

59

Se non stai codificando un'applicazione web, assicurati che la tua classe in cui @Autowiring sia eseguita sia un bean di primavera. In genere, il contenitore a molla non è a conoscenza della classe che potremmo pensare come un fagiolo a molla. Dobbiamo dire al container Spring delle nostre lezioni di primavera.

Ciò può essere ottenuto configurando in appln-contxt o il modo migliore è annotare la classe come @Component e non creare la classe annotata usando un nuovo operatore. Assicurati di averlo dal contesto di Appln come di seguito.

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

    }

}

ciao, ho esaminato la tua soluzione, è corretto. E qui vorrei sapere "Perché non creiamo un'istanza di classe annotata usando un nuovo operatore, posso sapere il motivo dietro questo.
Ashish,

3
se crei l'oggetto usando new, gestirai il ciclo di vita del bean che contraddice il concetto di IOC. Dobbiamo chiedere al contenitore di farlo, il che lo fa in modo migliore
Shirish Coolkarni,

41

In realtà, è necessario utilizzare oggetti gestiti da JVM o oggetti gestiti da Spring per richiamare metodi. dal codice sopra riportato nella classe del controller, stai creando un nuovo oggetto per chiamare la tua classe di servizio che ha un oggetto cablato automaticamente.

MileageFeeCalculator calc = new MileageFeeCalculator();

quindi non funzionerà in questo modo.

La soluzione rende questo MileageFeeCalculator come un oggetto cablato automaticamente nel controller stesso.

Cambia la tua classe Controller come di seguito.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

4
Questa è la risposta Poiché stai creando un'istanza di un nuovo MilageFeeCalculator da solo, Spring non è coinvolta nell'istanza, quindi Spring spring non ha conoscenza dell'oggetto esistente. Pertanto, non può farci nulla, come iniettare dipendenze.
Robert Greathouse,

26

Una volta ho riscontrato lo stesso problema quando non ero del tutto abituato the life in the IoC world. Il @Autowiredcampo di uno dei miei bean è nullo in fase di esecuzione.

La causa principale è, invece di utilizzare il bean creato automaticamente gestito dal contenitore Spring IoC (il cui @Autowiredcampo è indeedcorrettamente iniettato), io sono la newingmia istanza di quel tipo di bean e lo utilizzo. Naturalmente questo @Autowiredcampo è nullo perché Spring non ha alcuna possibilità di iniettarlo.


22

Il tuo problema è nuovo (creazione di oggetti in stile java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Con annotazione @Service, @Component, @Configurationi fagioli vengono creati nella
contesto di applicazione della primavera all'avvio del server. Ma quando creiamo oggetti usando un nuovo operatore l'oggetto non viene registrato nel contesto dell'applicazione che è già stato creato. Per esempio ho usato la classe Employee.java.

Controllalo:

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

Sono nuovo di primavera, ma ho scoperto questa soluzione funzionante. Per favore, dimmi se è un modo deprecabile.

Faccio Spring Inject applicationContextin questo 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;       
    }
}

Se lo desideri, puoi inserire questo codice anche nella classe principale dell'applicazione.

Altre classi possono usarlo in questo modo:

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

In questo modo qualsiasi bean può essere ottenuto da qualsiasi oggetto nell'applicazione (anche intenzionato con new) e in modo statico .


1
Questo modello è necessario per rendere i bean Spring accessibili al codice legacy ma dovrebbe essere evitato nel nuovo codice.
Chrylis

2
Non sei nuovo in primavera. Sei un professionista. :)
sapy

mi hai salvato ...
Govind Singh,

Nel mio caso l'ho richiesto perché c'erano poche classi di terze parti. Spring (IOC) non aveva il controllo su di loro. Queste classi non sono mai state chiamate dalla mia app di avvio di primavera. Ho seguito questo approccio e ha funzionato per me.
Joginder Malik,

12

Sembra essere un caso raro ma ecco cosa mi è successo:

Abbiamo usato @Injectinvece il @Autowiredquale è lo standard javaee supportato da Spring. Ogni posto ha funzionato bene e i fagioli sono stati iniettati correttamente, invece di un posto. L'iniezione di fagioli sembra la stessa

@Inject
Calculator myCalculator

Alla fine abbiamo scoperto che l'errore era che (in realtà, la funzione di completamento automatico di Eclipse) abbiamo importato com.opensymphony.xwork2.Injectinvece dijavax.inject.Inject !

Quindi, per riassumere, assicurarsi che il vostro annotazioni ( @Autowired, @Inject, @Service, ...) hanno pacchetti corretti!


5

Penso che ti sei perso di istruire Spring a scansionare le classi con annotazioni.

Puoi usare @ComponentScan("packageToScan") la classe di configurazione dell'applicazione Spring per istruire Spring alla scansione.

@Service, @Component le annotazioni ecc. aggiungono una meta descrizione.

Spring inietta solo istanze di quelle classi che vengono create come bean o contrassegnate con annotazioni.

Le classi contrassegnate con l'annotazione devono essere identificate entro la primavera prima di iniettare, @ComponentScanistruire la primavera a cercare le classi contrassegnate con l'annotazione. Quando Spring trova @Autowired, cerca il bean correlato e inietta l'istanza richiesta.

L'aggiunta di solo annotazioni, non risolve o facilita l'iniezione di dipendenze, Spring deve sapere dove cercare.


mi sono imbattuto in questo quando ho dimenticato di aggiungere <context:component-scan base-package="com.mypackage"/>al mio beans.xmlfile
Ralph Callaway il

5

Se ciò accade in una classe di test, assicurati di non aver dimenticato di annotare la classe.

Ad esempio, in Spring Boot :

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

Trascorre del tempo ...

Spring Boot continua ad evolversi . Non è più necessario @RunWith se si utilizza la versione corretta di JUnit .

Per @SpringBootTestfunzionare autonomamente, è necessario utilizzare @Testda JUnit5 invece di JUnit4 .

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

@SpringBootTest
public class MyTests {
    ....

Se si ottiene questo torto di configurazione i test saranno compilare, ma @Autowirede @Valuecampi (per esempio) saranno null. Poiché Spring Boot funziona per magia, potresti avere poche strade per il debug diretto di questo errore.



Nota: @Valuesarà nullo se utilizzato con i staticcampi.
nobar

Spring offre numerosi modi per fallire (senza l'aiuto del compilatore). Quando le cose vanno male, la soluzione migliore è tornare al punto di partenza - usando solo la combinazione di annotazioni che sai funzioneranno insieme.
nobar,

4

Un'altra soluzione sarebbe quella di chiamare: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
al costruttore MileageFeeCalculator in questo modo:

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

Questo utilizza pubblicazioni non sicure.
Chrylis

3

AGGIORNAMENTO: Le persone davvero intelligenti hanno rapidamente puntato su questa risposta, che spiega la stranezza, descritta di seguito

RISPOSTA ORIGINALE:

Non so se possa aiutare nessuno, ma ero bloccato con lo stesso problema anche mentre facevo le cose apparentemente bene. Nel mio metodo principale, ho un codice come questo:

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

e in un token.xmlfile ho avuto una linea

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

Ho notato che package.path non esiste più, quindi ho appena abbandonato la linea per sempre.

E dopo ciò, l'NPE ha iniziato a entrare. In un pep-config.xmlho avuto solo 2 fagioli:

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

e la classe SomeAbac ha una proprietà dichiarata come

@Autowired private Settings settings;

per qualche ragione sconosciuta, settings è null in init (), quando l' <context:component-scan/>elemento non è presente affatto, ma quando è presente e ha alcuni bs come pacchetto base, tutto funziona bene. Questa riga ora appare così:

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

e funziona. Potrebbe essere qualcuno in grado di fornire una spiegazione, ma per me è abbastanza adesso)


5
Questa risposta è la spiegazione. <context:component-scan/>abilita implicitamente <context:annotation-config/>necessario per il @Autowiredfunzionamento.
ForNeVeR,

3

Questo è il colpevole di dare NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();Stiamo usando Spring - non è necessario creare manualmente l'oggetto. La creazione di oggetti sarà curata dal contenitore IoC.


2

È anche possibile risolvere questo problema utilizzando l'annotazione @Service sulla classe di servizio e passando la classe bean A richiesta come parametro all'altro costruttore di classe bean B e annotare il costruttore di classe B con @Autowired. Snippet di esempio qui:

@Service
public class ClassB {

    private ClassA classA;

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

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

questo ha funzionato per me ma puoi per favore approfondire come questo sta risolvendo il problema?
CruelEngine,

1
@CruelEngine, guarda che è l'iniezione del costruttore (dove si imposta esplicitamente un oggetto) invece di usare semplicemente l'iniezione di campo (questo è fatto principalmente dalla configurazione della molla per lo più). Quindi, se si sta creando un oggetto di ClassB utilizzando un "nuovo" operatore è un altro ambito, questo non sarebbe visibile o impostato automaticamente per ClassA. Quindi, mentre si chiama classB.useClassAObjectHere (), si getta NPE poiché l'oggetto classA non è stato autowired se si dichiara semplicemente il campo Injection. Leggi Chrylis sta cercando di spiegare lo stesso. E questo è il motivo per cui l'iniezione del costruttore è consigliata rispetto all'iniezione sul campo. Ha senso adesso?
Abhishek,

1

Ciò che non è stato menzionato qui è descritto in questo articolo nel paragrafo "Ordine di esecuzione".

Dopo aver "appreso" che dovevo annotare una classe con @Component o i derivati ​​@Service o @Repository (credo ce ne siano altri), per autorizzare altri componenti al loro interno, mi è sembrato che questi altri componenti fossero ancora nulli all'interno del costruttore del componente padre.

L'uso di @PostConstruct risolve che:

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

e:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

1

Questo è valido solo in caso di Unit test.

La mia classe di servizio aveva un'annotazione di servizio ed era @autowiredun'altra classe di componenti. Quando ho testato la classe del componente stava diventando nulla. Perché per la classe di servizio stavo creando l'oggetto usandonew

Se stai scrivendo unit test assicurati di non creare oggetti usando new object(). Usa invece injectMock.

Questo risolto il mio problema. Ecco un link utile


0

Si noti inoltre che se, per qualsiasi motivo, si crea un metodo in un @Serviceas final, i bean autowired a cui si accederà saranno sempre null.


0

In parole semplici ci sono principalmente due ragioni per essere un @Autowiredcamponull

  • LA TUA CLASSE NON È UN FAGIOLO DI PRIMAVERA.

  • IL CAMPO NON È UN FAGIOLO.


0

Non interamente correlato alla domanda, ma se l'iniezione sul campo è nulla, l'iniezione basata sul costruttore continuerà a funzionare correttamente.

    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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.