Pourquoi mon champ Spring @Autowired est-il nul?


608

Remarque: Il s'agit d'une réponse canonique à un problème courant.

J'ai une @Serviceclasse Spring ( MileageFeeCalculator) qui a un @Autowiredchamp ( rateService), mais le champ est nulllorsque j'essaie de l'utiliser. Les journaux montrent que le MileageFeeCalculatorbean et le MileageRateServicebean sont en cours de création, mais j'obtiens un NullPointerExceptionchaque fois que j'essaie d'appeler la mileageChargeméthode sur mon bean de service. Pourquoi Spring ne fait-il pas le câblage automatique sur le terrain?

Classe de contrôleur:

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

Classe de service:

@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 de service qui devrait être câblé automatiquement MileageFeeCalculatormais ce n'est pas le cas:

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

Quand j'essaye GET /mileage/3, j'obtiens cette exception:

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 autre scénario peut être lorsque le bean Fest appelé à l'intérieur du constructeur d'un autre bean S. Dans ce cas, passez le bean requis en Ftant que paramètre à l'autre Sconstructeur de beans et annotez le constructeur de Swith @Autowire. N'oubliez pas d'annoter la classe du premier bean Favec @Component.
aliopi

J'ai codé quelques exemples très similaires à celui-ci en utilisant Gradle ici: github.com/swimorsink/spring-aspectj-examples . J'espère que quelqu'un le trouvera utile.
Ross117

Réponses:


649

Le champ annoté @Autowiredest nulldû au fait que Spring ne connaît pas la copie de MileageFeeCalculatorcelle que vous avez créée avec newet ne savait pas comment la câbler automatiquement.

Le conteneur Spring Inversion of Control (IoC) comprend trois composants logiques principaux: un registre (appelé le ApplicationContext) de composants (beans) disponibles pour être utilisés par l'application, un système de configuration qui leur injecte les dépendances des objets en faisant correspondre le dépendances avec des beans dans le contexte, et un solveur de dépendance qui peut examiner une configuration de nombreux beans différents et déterminer comment les instancier et les configurer dans l'ordre nécessaire.

Le conteneur IoC n'est pas magique, et il n'a aucun moyen de connaître les objets Java, sauf si vous en informez-les d'une manière ou d'une autre. Lorsque vous appelez new, la JVM instancie une copie du nouvel objet et vous la remet directement - elle ne passe jamais par le processus de configuration. Il existe trois façons de configurer vos beans.

J'ai publié tout ce code, en utilisant Spring Boot pour lancer, sur ce projet GitHub ; vous pouvez consulter un projet en cours d'exécution pour chaque approche pour voir tout ce dont vous avez besoin pour le faire fonctionner. Tag avec le NullPointerException:nonworking

Injectez vos grains

L'option la plus préférable est de laisser Spring auto-câbler tous vos grains; cela nécessite le moins de code et est le plus facile à gérer. Pour que le câblage automatique fonctionne comme vous le vouliez, procédez également au câblage automatique MileageFeeCalculatorcomme ceci:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Si vous devez créer une nouvelle instance de votre objet de service pour différentes demandes, vous pouvez toujours utiliser l'injection en utilisant les étendues de bean Spring .

Balise qui fonctionne en injectant l' @MileageFeeCalculatorobjet de service:working-inject-bean

Utilisez @Configurable

Si vous avez vraiment besoin que les objets créés avec newsoient câblés automatiquement, vous pouvez utiliser l' @Configurableannotation Spring avec le tissage au moment de la compilation AspectJ pour injecter vos objets. Cette approche insère du code dans le constructeur de votre objet qui avertit Spring qu'il est en cours de création afin que Spring puisse configurer la nouvelle instance. Cela nécessite un peu de configuration dans votre build (comme la compilation avec ajc) et l'activation des gestionnaires de configuration d'exécution de Spring ( @EnableSpringConfiguredavec la syntaxe JavaConfig). Cette approche est utilisée par le système Roo Active Record pour permettre aux newinstances de vos entités d'obtenir les informations de persistance nécessaires injectées.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Balise qui fonctionne en utilisant @Configurablesur l'objet de service:working-configurable

Recherche manuelle de bean: non recommandé

Cette approche ne convient que pour l'interfaçage avec du code hérité dans des situations particulières. Il est presque toujours préférable de créer une classe d'adaptateur singleton que Spring peut câbler automatiquement et le code hérité peut appeler, mais il est possible de demander directement au contexte de l'application Spring un bean.

Pour ce faire, vous avez besoin d'une classe à laquelle Spring peut donner une référence à l' ApplicationContextobjet:

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

Ensuite, votre code hérité peut appeler getContext()et récupérer les beans dont il a besoin:

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

Balise qui fonctionne en recherchant manuellement l'objet de service dans le contexte Spring: working-manual-lookup


1
L'autre chose à regarder est de créer des objets pour les beans dans un @Configurationbean, où la méthode pour créer une instance d'une classe de bean particulière est annotée @Bean.
Donal Fellows

@DonalFellows Je ne sais pas trop de quoi vous parlez ("faire" est ambigu). Parlez-vous d'un problème avec plusieurs appels aux @Beanméthodes lors de l'utilisation de Spring Proxy AOP?
chrylis

1
Bonjour, je rencontre un problème similaire, mais lorsque j'utilise votre première suggestion, mon application pense que "calc" est nul lors de l'appel de la méthode "mileageFee". C'est comme si cela n'initialisait jamais le @Autowired MileageFeeCalculator calc. Des pensées?
Theo

Je pense que vous devriez ajouter une entrée en haut de votre réponse qui explique que la récupération du premier bean, la racine à partir de laquelle vous faites tout, doit être effectuée via le ApplicationContext. Certains utilisateurs (pour lesquels j'ai fermé comme doublons) ne comprennent pas cela.
Sotirios Delimanolis

@SotiriosDelimanolis Veuillez expliquer le problème; Je ne sais pas exactement quel point vous faites valoir.
chrylis

59

Si vous ne codez pas une application Web, assurez-vous que votre classe dans laquelle @Autowiring est effectué est un bean à ressort. En règle générale, le conteneur de printemps ne connaîtra pas la classe que nous pourrions considérer comme un haricot de printemps. Nous devons parler au conteneur Spring de nos cours de printemps.

Ceci peut être réalisé en configurant dans appln-contxt ou la meilleure façon est d'annoter la classe en tant que @Component et veuillez ne pas créer la classe annotée en utilisant un nouvel opérateur. Assurez-vous de l'obtenir à partir du contexte Appln comme ci-dessous.

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

    }

}

salut, je suis passé par votre solution, c'est exact. Et ici, je voudrais savoir "Pourquoi nous ne créons pas d'instance de classe annotée en utilisant un nouvel opérateur, puis-je en connaître la raison.
Ashish

3
si vous créez l'objet en utilisant new, vous manipulerez le cycle de vie du bean qui contredit le concept de l'IOC. Nous devons demander au conteneur de le faire, ce qui le fait d'une meilleure manière
Shirish Coolkarni

41

En fait, vous devez utiliser des objets gérés JVM ou des objets gérés par Spring pour appeler des méthodes. à partir de votre code ci-dessus dans votre classe de contrôleur, vous créez un nouvel objet pour appeler votre classe de service qui a un objet auto-câblé.

MileageFeeCalculator calc = new MileageFeeCalculator();

donc ça ne marchera pas comme ça.

La solution fait de ce MileageFeeCalculator un objet auto-câblé dans le contrôleur lui-même.

Modifiez votre classe de contrôleur comme ci-dessous.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

4
Telle est la réponse. Parce que vous instanciez un nouveau MilageFeeCalculator par vous-même, Spring n'est pas impliqué dans l'instanciation, donc Spring spring n'a aucune connaissance que l'objet existe. Ainsi, il ne peut rien y faire, comme injecter des dépendances.
Robert Greathouse

26

J'ai rencontré une fois le même problème quand je n'étais pas tout à fait habitué the life in the IoC world. Le @Autowiredchamp de l'un de mes beans est nul au moment de l'exécution.

La cause principale est qu'au lieu d'utiliser le bean créé automatiquement maintenu par le conteneur Spring IoC (dont le @Autowiredchamp est indeedcorrectement injecté), je suis newingma propre instance de ce type de bean et je l'utilise. Bien sûr, le @Autowiredchamp de celui-ci est nul car Spring n'a aucune chance de l'injecter.


22

Votre problème est nouveau (création d'objet en style java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Avec l' annotation @Service, @Component, les @Configurationharicots sont créés dans le
cadre de l' application du printemps lorsque le serveur est démarré. Mais lorsque nous créons des objets à l'aide d'un nouvel opérateur, l'objet n'est pas enregistré dans le contexte d'application qui est déjà créé. Pour l'exemple de classe Employee.java que j'ai utilisé.

Regarde ça:

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

Je suis nouveau sur Spring, mais j'ai découvert cette solution de travail. S'il vous plaît, dites-moi si c'est une manière dépréciable.

Je fais Spring injecter applicationContextdans ce 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;       
    }
}

Vous pouvez également placer ce code dans la classe d'application principale si vous le souhaitez.

D'autres classes peuvent l'utiliser comme ceci:

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

De cette façon, n'importe quel bean peut être obtenu par n'importe quel objet dans l'application (également initié avec new) et de manière statique .


1
Ce modèle est nécessaire pour rendre les beans Spring accessibles au code hérité mais doit être évité dans le nouveau code.
chrylis

2
Vous n'êtes pas nouveau au printemps. Tu es un pro. :)
sapy

tu m'as sauvé ...
Govind Singh

Dans mon cas, j'ai exigé cela car il y avait peu de cours tiers. Spring (IOC) n'avait pas le contrôle sur eux. Ces cours n'ont jamais été appelés depuis mon application Spring Boot. J'ai suivi cette approche et cela a fonctionné pour moi.
Joginder Malik

12

Cela semble être un cas rare mais voici ce qui m'est arrivé:

Nous avons utilisé @Injectau lieu de @Autowiredcela la norme javaee prise en charge par Spring. À tous les endroits, cela a bien fonctionné et les grains ont été injectés correctement, au lieu d'un seul endroit. L'injection de haricot semble la même

@Inject
Calculator myCalculator

Enfin, nous avons constaté que l'erreur était que nous (en fait, la fonction de saisie automatique Eclipse) importions com.opensymphony.xwork2.Injectau lieu de javax.inject.Inject!

Donc , pour résumer, assurez - vous que vos annotations ( @Autowired, @Inject, @Service, ...) ont des paquets corrects!


5

Je pense que vous avez manqué de demander au printemps de scanner les cours avec annotation.

Vous pouvez utiliser @ComponentScan("packageToScan")sur la classe de configuration de votre application Spring pour demander au printemps de scanner.

@Service, @Component etc les annotations ajoutent une méta description.

Spring injecte uniquement des instances de ces classes qui sont soit créées en tant que bean, soit marquées d'annotations.

Les classes marquées avec annotation doivent être identifiées par ressort avant l'injection, @ComponentScandemandez au printemps de rechercher les classes marquées avec annotation. Lorsque Spring le trouve, @Autowiredil recherche le bean associé et injecte l'instance requise.

L'ajout d'annotations uniquement, ne corrige pas ou ne facilite pas l'injection de dépendances, Spring doit savoir où chercher.


est tombé sur cela quand j'ai oublié d'ajouter <context:component-scan base-package="com.mypackage"/>à mon beans.xmlfichier
Ralph Callaway

5

Si cela se produit dans une classe de test, assurez-vous que vous n'avez pas oublié d'annoter la classe.

Par exemple, dans Spring Boot :

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

Il s'écoule du temps ...

Spring Boot continue d'évoluer . Il n'est plus nécessaire de l'utiliser @RunWith si vous utilisez la bonne version de JUnit .

Pour @SpringBootTesttravailler de manière autonome, vous devez utiliser à @Testpartir de JUnit5 au lieu de JUnit4 .

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

@SpringBootTest
public class MyTests {
    ....

Si vous obtenez ce mal de configuration de vos tests compilerons, mais @Autowiredet les @Valuechamps (par exemple) seront null. Étant donné que Spring Boot fonctionne par magie, vous pouvez avoir peu de possibilités de déboguer directement cet échec.



Remarque: @Valuesera nul lorsqu'il est utilisé avec des staticchamps.
nobar

Spring offre de nombreuses façons d'échouer (sans l'aide du compilateur). Lorsque les choses tournent mal, votre meilleur pari est de revenir à la case départ - en utilisant uniquement la combinaison d'annotations dont vous savez qu'elle fonctionnera ensemble.
nobar

4

Une autre solution serait de mettre l'appel: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
au constructeur MileageFeeCalculator comme ceci:

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

Cela utilise une publication non sécurisée.
chrylis

3

MISE À JOUR: Les gens vraiment intelligents ont rapidement indiqué cette réponse, ce qui explique l'étrangeté, décrite ci-dessous

RÉPONSE ORIGINALE:

Je ne sais pas si cela aide quelqu'un, mais j'étais coincé avec le même problème même en faisant les choses en apparence. Dans ma méthode Main, j'ai un code comme celui-ci:

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

et dans un token.xmlfichier j'ai eu une ligne

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

J'ai remarqué que le package.path n'existe plus, donc je viens de laisser tomber la ligne pour de bon.

Et après cela, NPE a commencé à entrer. Dans un pep-config.xmlje n'avais que 2 haricots:

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

et la classe SomeAbac a une propriété déclarée comme

@Autowired private Settings settings;

pour une raison inconnue, les paramètres sont nuls dans init (), lorsque l' <context:component-scan/>élément n'est pas présent du tout, mais lorsqu'il est présent et a des bs comme basePackage, tout fonctionne bien. Cette ligne ressemble maintenant à ceci:

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

et il fonctionne. Peut-être que quelqu'un peut fournir une explication, mais pour moi, c'est suffisant en ce moment)


5
Cette réponse est l'explication. <context:component-scan/>permet implicitement <context:annotation-config/>nécessaire @Autowiredau fonctionnement.
ForNeVeR

3

C'est le coupable de donner NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();Nous utilisons Spring - pas besoin de créer un objet manuellement. La création d'objets sera prise en charge par le conteneur IoC.


2

Vous pouvez également résoudre ce problème en utilisant l'annotation @Service sur la classe de service et en transmettant le bean classA requis en tant que paramètre à l'autre constructeur de classe B de beans et en annotant le constructeur de classB avec @Autowired. Exemple d'extrait ici:

@Service
public class ClassB {

    private ClassA classA;

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

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

cela a fonctionné pour moi, mais pouvez-vous expliquer comment cela résout le problème?
CruelEngine

1
@CruelEngine, regardez c'est une injection de constructeur (où vous définissez explicitement un objet) au lieu de simplement utiliser l'injection de champ (cela se fait principalement par la configuration de printemps principalement). Donc, si vous créez un objet de ClassB en utilisant un "nouvel" opérateur, c'est une autre étendue qui ne serait pas visible ou câblée pour ClassA. Par conséquent, lors de l'appel de classB.useClassAObjectHere (), le NPE serait lancé car l'objet classA n'était pas câblé automatiquement si vous déclarez simplement le champ Injection. Lire chrylis essaie d'expliquer la même chose. Et c'est pourquoi l'injection de constructeur est recommandée par rapport à l'injection sur le terrain. Est-ce que cela a du sens maintenant?
Abhishek

1

Ce qui n'a pas été mentionné ici est décrit dans cet article au paragraphe "Ordre d'exécution".

Après avoir "appris" que je devais annoter une classe avec @Component ou les dérivés @Service ou @Repository (je suppose qu'il y en a plus), pour câbler automatiquement d'autres composants à l'intérieur, j'ai été frappé par le fait que ces autres composants étaient toujours nuls à l'intérieur du constructeur du composant parent.

L'utilisation de @PostConstruct résout ce qui suit:

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

et:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

1

Ceci n'est valable qu'en cas de test unitaire.

Ma classe de service avait une annotation de service et c'était @autowiredune autre classe de composants. Lorsque j'ai testé la classe de composants était devenue nulle. Parce que pour la classe de service, je créais l'objet en utilisantnew

Si vous écrivez un test unitaire, assurez-vous que vous ne créez pas d'objet à l'aide de new object(). Utilisez plutôt injectMock.

Cela a résolu mon problème. Voici un lien utile


0

Notez également que si, pour une raison quelconque, vous créez une méthode dans un @Serviceas final, les beans câblés automatiquement auxquels vous accéderez seront toujours null.


0

En termes simples, il y a principalement deux raisons pour qu'un @Autowiredchamp soitnull

  • VOTRE CLASSE N'EST PAS UN HARICOT DE PRINTEMPS.

  • LE CHAMP N'EST PAS UN HARICOT.


0

Pas entièrement lié à la question, mais si l'injection de champ est nulle, l'injection basée sur le constructeur fonctionnera toujours correctement.

    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;
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.