780 Stimmen

Warum ist mein Spring @Autowired-Feld null?

Hinweis: Dies soll eine kanonische Antwort für ein häufiges Problem sein.

Ich habe eine Spring @Service-Klasse (MileageFeeCalculator), die ein @Autowired-Feld (rateService) hat, aber das Feld ist null, wenn ich versuche, es zu verwenden. Die Protokolle zeigen, dass sowohl das MileageFeeCalculator-Bean als auch das MileageRateService-Bean erstellt werden, aber ich erhalte eine NullPointerException, wenn ich versuche, die Methode mileageCharge auf meinem Service-Bean aufzurufen. Warum verkabelt Spring das Feld nicht automatisch?

Controller-Klasse:

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

Service-Klasse:

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- sollte autowired sein, ist null

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); // <--- wirft NPE
    }
}

Service-Bean, das in MileageFeeCalculator autowired werden sollte, aber nicht ist:

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

Wenn ich versuche, GET /mileage/3, erhalte ich diese Ausnahme:

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)
    ...

791voto

Das mit @Autowired annotierte Feld ist null, weil Spring nichts über die Kopie von MileageFeeCalculator weiß, die Sie mit new erstellt haben und nicht wusste, wie es verkabelt werden soll.

Der Spring Inversion of Control (IoC) Container hat drei Hauptkomponenten: ein Register (genannt der ApplicationContext) von Komponenten (Beans), die von der Anwendung verwendet werden können, ein Konfigurationssystem, das Objektabhängigkeiten in sie injiziert, indem es die Abhängigkeiten mit Beans im Kontext abgleicht, und ein Abhängigkeitslöser, der eine Konfiguration vieler verschiedener Beans betrachten kann und bestimmen kann, wie sie in der erforderlichen Reihenfolge instanziiert und konfiguriert werden sollen.

Der IoC-Container ist keine Magie und hat keine Möglichkeit, über Java-Objekte Bescheid zu wissen, es sei denn, Sie informieren ihn somehow. Wenn Sie new aufrufen, instanziiert die JVM eine Kopie des neuen Objekts und übergibt es direkt an Sie - es durchläuft niemals den Konfigurationsprozess. Es gibt drei Möglichkeiten, wie Sie Ihre Beans konfigurieren können.

Ich habe den gesamten Code mit Spring Boot zur Ausführung in diesem GitHub-Projekt veröffentlicht: dieses GitHub-Projekt; Sie können sich ein komplettes laufendes Projekt für jeden Ansatz ansehen, um zu sehen, was alles benötigt wird, um es zum Laufen zu bringen. Tag mit der NullPointerException: nonworking

Inject your beans

Die bevorzugte Option besteht darin, Spring all Ihre Beans verkabeln zu lassen; dies erfordert den geringsten Codeaufwand und ist am einfachsten zu warten. Um das Verkabeln so zu machen, wie Sie es möchten, verkabeln Sie auch den MileageFeeCalculator wie folgt:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Wenn Sie eine neue Instanz Ihres Service-Objekts für unterschiedliche Anfragen erstellen müssen, können Sie immer noch Injektion verwenden, indem Sie die Spring Bean Scopes verwenden.

Tag, der funktioniert, indem das @MileageFeeCalculator Service-Objekt injiziert wird: working-inject-bean

Verwenden Sie @Configurable

Wenn Sie wirklich möchten, dass Objekte, die mit new erstellt wurden, verkabelt werden, können Sie die Spring @Configurable Annotation zusammen mit dem AspectJ-Kompilierzeit-Weaving verwenden, um Ihre Objekte einzufügen. Dieser Ansatz fügt Code in den Konstruktor Ihres Objekts ein, der Spring signalisiert, dass es erstellt wird, so dass Spring die neue Instanz konfigurieren kann. Dies erfordert etwas Konfiguration in Ihrem Build (wie das Kompilieren mit ajc) und das Aktivieren der Laufzeitkonfigurationshandler von Spring (@EnableSpringConfigured mit der JavaConfig-Syntax). Dieser Ansatz wird vom Roo Active Record-System verwendet, um new Instanzen Ihrer Entitäten die notwendigen Persistenzinformationen einzuspeisen.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Tag, der funktioniert, indem @Configurable auf das Service-Objekt angewendet wird: working-configurable

Manuelle Bean-Suche: nicht empfohlen

Dieser Ansatz ist nur geeignet, um in speziellen Situationen mit Legacy-Code zu interagieren. Es ist fast immer vorzuziehen, eine Singleton-Adapterklasse zu erstellen, die Spring verkabeln kann und die der Legacy-Code aufrufen kann, aber es ist möglich, den Spring-Anwendungscontext direkt nach einem Bean zu fragen.

Dazu benötigen Sie eine Klasse, der Spring einen Verweis auf das ApplicationContext-Objekt geben kann:

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

Dann kann Ihr Legacy-Code getContext() aufrufen und die Beans abrufen, die er benötigt:

@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, der funktioniert, indem das Service-Objekt im Spring-Kontext manuell gesucht wird: working-manual-lookup

72voto

Shirish Coolkarni Punkte 927

Wenn Sie keine Webanwendung codieren, stellen Sie sicher, dass Ihre Klasse, in der @Autowiring erfolgt, ein Spring Bean ist. Normalerweise ist dem Spring-Container die Klasse nicht bekannt, die wir als Spring Bean betrachten könnten. Wir müssen dem Spring-Container unsere Spring-Klassen mitteilen.

Dies kann durch Konfiguration in appln-contxt erreicht werden oder der bessere Weg ist, die Klasse als @Component zu kennzeichnen und bitte erstellen Sie die annotierte Klasse nicht mit dem new-Operator. Stellen Sie sicher, dass Sie diese aus Appln-Kontext wie folgt erhalten.

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

    }

}

54voto

Ravi Durairaj Punkte 771

Eigentlich sollten Sie entweder von der JVM verwaltete Objekte oder von Spring verwaltete Objekte nutzen, um Methoden aufzurufen. aus Ihrem obigen Code in Ihrer Controller-Klasse erstellen Sie ein neues Objekt, um Ihre Service-Klasse aufzurufen, das ein auto-verdrahtetes Objekt hat.

MileageFeeCalculator calc = new MileageFeeCalculator();

daher wird es so nicht funktionieren.

Die Lösung besteht darin, diesen MileageFeeCalculator als auto-verdrahtetes Objekt im Controller selbst zu erstellen.

Ändern Sie Ihre Controller-Klasse wie unten dargestellt.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

36voto

smwikipedia Punkte 56976

Ich bin auf dasselbe Problem gestoßen, als ich mich nicht ganz an das Leben in der IoC-Welt gewöhnt hatte. Das @Autowired-Feld eines meiner Beans ist zur Laufzeit null.

Der eigentliche Grund dafür ist, dass ich anstelle des automatisch erstellten Beans, das vom Spring IoC-Container verwaltet wird (dessen @Autowired-Feld tatsächlich ordnungsgemäß injiziert ist), meine eigene Instanz dieses Bean-Typs mit new erstelle und verwende. Natürlich ist das @Autowired-Feld dieses Beans null, weil Spring keine Möglichkeit hat, es zu injizieren.

28voto

Deepak Punkte 1572

Ihr Problem ist neu (Objekterstellung im Java-Stil)

MileageFeeCalculator calc = new MileageFeeCalculator();

Mit der Annotation @Service, @Component, @Configuration werden Beans im Anwendungskontext von Spring erstellt, wenn der Server gestartet wird. Wenn wir jedoch Objekte mit dem neuen Operator erstellen, wird das Objekt nicht im bereits erstellten Anwendungskontext registriert. Zum Beispiel habe ich die Klasse Employee.java verwendet.

Überprüfen Sie das:

public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String name = "tenant";
        System.out.println("Beanfactory-Postprozessor wird initialisiert");
        beanFactory.registerScope("employee", new Employee());

        Assert.state(beanFactory instanceof BeanDefinitionRegistry,
                "BeanFactory war kein BeanDefinitionRegistry, also kann CustomScope nicht verwendet werden.");
        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());
            }
        }
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X