Remarque: Ceci est destiné à être une réponse canonique à un problème commun.
J'ai une classe @Service
de printemps (MileageFeeCalculator
) qui a un champ @Autowired
(rateService
), mais le champ est null
lorsque j'essaie de l'utiliser. Les journaux indiquent que le bean MileageFeeCalculator
et le bean MileageRateService
sont en cours de création, mais je reçois un NullPointerException
chaque fois que j'essaie d'appeler la méthode mileageCharge
sur mon bean service. Pourquoi Spring n’autorise-t-il pas le câblage automatique?
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
}
}
Le haricot de service qui devrait être câblé automatiquement dans MileageFeeCalculator
mais ce n'est pas:
@Service
public class MileageRateService {
public float ratePerMile() {
return 0.565f;
}
}
Lorsque j'essaie de 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)
...
Le champ annoté @Autowired
est null
car Spring ne connaît pas la copie de MileageFeeCalculator
que vous avez créée avec new
et ne savait pas la transférer automatiquement.
Le conteneur Spring Inversion of Control (IoC) comporte trois composants logiques principaux: un registre (appelé ApplicationContext
) des composants (beans) disponibles pour être utilisés par l'application, un système de configuration qui injecte les dépendances des objets dans en comparant les dépendances avec les beans dans le contexte et un ré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 à moins que vous ne l'informiez d'une manière ou d'une autre. Lorsque vous appelez new
, la machine virtuelle Java instancie une copie du nouvel objet et vous la remet directement. Elle ne passe jamais par le processus de configuration. Vous pouvez configurer vos beans de trois manières différentes.
J'ai posté tout ce code, en utilisant Spring Boot pour lancer, à ce projet GitHub ; vous pouvez consulter un projet en cours d'exécution pour chaque approche afin de voir tout ce dont vous avez besoin pour le faire fonctionner. Tag avec le NullPointerException
: nonworking
L'option la plus préférable consiste à laisser Spring relier automatiquement tous vos haricots; Cela nécessite le moins de code possible et est le plus facile à gérer. Pour que le câblage automatique fonctionne comme vous le souhaitez, connectez également le MileageFeeCalculator
comme 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 service pour différentes demandes, vous pouvez toujours utiliser l'injection à l'aide de les portées du bean Spring .
Tag qui fonctionne en injectant l'objet service @MileageFeeCalculator
: working-inject-bean
Si vous avez vraiment besoin que les objets créés avec new
soient automatiquement connectés, vous pouvez utiliser l'annotation Spring @Configurable
avec le tissage à la compilation AspectJ pour injecter vos objets. Cette approche insère du code dans le constructeur de votre objet qui avertit Spring que celui-ci est créé afin que Spring puisse configurer la nouvelle instance. Cela nécessite un peu de configuration dans votre construction (compiler avec ajc
) et activer les gestionnaires de configuration d'exécution de Spring (@EnableSpringConfigured
avec la syntaxe JavaConfig). Cette approche est utilisée par le système Roo Active Record pour permettre aux instances new
de vos entités de recevoir les informations de persistance nécessaires.
@Service
@Configurable
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService;
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}
Tag qui fonctionne en utilisant @Configurable
sur l'objet de service: working-configurable
Cette approche ne convient que pour l'interface avec le code existant dans des situations spéciales. Il est presque toujours préférable de créer une classe d'adaptateur singleton que Spring puisse autoriser et que le code hérité puisse appeler, mais il est possible de demander directement le contexte de l'application Spring pour un bean.
Pour ce faire, vous avez besoin d’une classe à laquelle Spring peut donner une référence à l’objet 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;
}
}
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);
}
}
Tag qui fonctionne en recherchant manuellement l'objet de service dans le contexte Spring: working-manual-lookup
Si vous ne codez pas une application Web, assurez-vous que votre classe dans laquelle @Autowiring est effectuée est un haricot printanier. En règle générale, les contenants de printemps ne connaissent pas la classe que nous pourrions considérer comme un haricot de printemps. Nous devons informer le conteneur Spring de nos cours de printemps.
Ceci peut être réalisé en configurant dans appln-contxt ou le meilleur moyen est d’annoter la classe en tant que @Component et de ne pas créer la classe annotée avec un nouvel opérateur ..__ Appln-context 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();
}
}
En fait, vous devez utiliser des objets gérés par JVM ou un objet géré par Spring pour appeler des méthodes . À partir du code ci-dessus dans votre classe de contrôleur, vous créez un nouvel objet pour appeler votre classe de service comportant un objet auto-câblé.
MileageFeeCalculator calc = new MileageFeeCalculator();
donc ça ne marchera pas comme ça.
La solution permet à MileageFeeCalculator de devenir un objet auto-câblé dans le contrôleur lui-même.
Changez 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);
}
}
Une fois, j'ai rencontré le même problème alors que je n'étais pas tout à fait habitué à the life in the IoC world
. Le champ @Autowired
de l'un de mes beans est null au moment de l'exécution.
La cause principale est qu'au lieu d'utiliser le bean créé automatiquement et maintenu par le conteneur Spring IoC (dont le champ @Autowired
est indeed
correctement injecté), je suis newing
ma propre instance de ce type de bean et je l'utilise. Bien sûr, le champ @Autowired
de celui-ci est nul car Spring n'a aucune chance de l'injecter.
Votre problème est nouveau (création d'objet en style Java)
MileageFeeCalculator calc = new MileageFeeCalculator();
Avec les annotations @Service
, @Component
, @Configuration
, des beans sont créés dans le
contexte d'application de Spring lorsque le serveur est démarré. Mais quand on crée des objets En utilisant l'opérateur new, l'objet n'est pas enregistré dans le contexte d'application déjà créé. Par exemple, classe Employee.Java que j’ai utilisée.
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());
}
}
}
}
Il semble que ce soit un cas rare mais voici ce qui m'est arrivé:
Nous avons utilisé @Inject
au lieu de @Autowired
qui est la norme Java prise en charge par Spring. Tous les endroits ont bien fonctionné et les haricots 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 découvert que l’erreur était que nous (en fait, la fonction de complétion automatique d’Eclipse) importait com.opensymphony.xwork2.Inject
au lieu de javax.inject.Inject
!
Donc, pour résumer, assurez-vous que vos annotations (@Autowired
, @Inject
, @Service
, ...) ont les packages corrects!
Je suis nouveau à Spring, mais j'ai découvert cette solution de travail. S'il vous plaît dites-moi si c'est une façon déconseillée.
Je fais printemps injecter applicationContext
dans cette fève:
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 mettre 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 manière n'importe quel haricot peut être obtenu par n'importe quel objet de l'application (également avec new
) et de manière statique .
Une autre solution serait de passer l’appel: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Pour le constructeur MileageFeeCalculator, procédez comme suit:
@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());
}
}
Je pense que vous avez manqué d'instruire Spring à scanner les classes avec des annotations.
Vous pouvez utiliser @ComponentScan("packageToScan")
sur la classe de configuration de votre application Spring pour demander à Spring d'analyser.
@Service, @Component
etc annotations add meta description .
Spring n'injecte que les occurrences des classes créées en tant que bean ou marquées avec une annotation.
Les classes marquées avec une annotation doivent être identifiées par un ressort avant l'injection. @ComponentScan
instruire un printemps recherche les classes marquées avec une annotation. Lorsque Spring trouve @Autowired
, il recherche le bean associé et injecte l'instance requise.
L'ajout d'une annotation uniquement ne corrige ni ne facilite l'injection de dépendance. Spring doit savoir où chercher.
Si cela se produit dans une classe de test, assurez-vous de ne pas avoir oublié d'annoter la classe.
Par exemple, dans Spring Boot :
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
....
Vous pouvez également résoudre ce problème en utilisant l'annotation @Service sur la classe de service et en transmettant la classe A du bean requise en tant que paramètre au constructeur des classes B des autres beans et annotez le constructeur de la classe B avec @Autowired. Extrait extrait ici:
@Service
public class ClassB {
private ClassA classA;
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}
public void useClassAObjectHere(){
classA.callMethodOnObjectA();
}
}
UPDATE: Les personnes très intelligentes ont rapidement indiqué la this réponse, ce qui explique l'étrangeté décrite ci-dessous.
RÉPONSE ORIGINALE:
Je ne sais pas si cela peut aider qui que ce soit, mais je me suis retrouvé confronté au même problème, même si je faisais les choses apparemment bien. Dans ma méthode principale, 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 fichier token.xml
j'ai eu une ligne
<context:component-scan base-package="package.path"/>
J'ai remarqué que le package.path n'existe plus, alors je viens de laisser tomber la ligne pour de bon.
Et après cela, NPE a commencé à arriver. Dans un pep-config.xml
, je 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 null dans init (), lorsque l'élément <context:component-scan/>
n'est pas présent du tout, mais lorsqu'il est présent et qu'il contient des bs comme basePackage, tout fonctionne correctement. 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 maintenant)
Notez également que si, pour une raison quelconque, vous créez une méthode dans un @Service
sous la forme final
, les beans autochargés auxquels vous aurez accès seront toujours null
.
Ce qui n'a pas été mentionné ici est décrit dans l'article this du 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 autoriser le câblage d'autres composants à l'intérieur d'eux, il m'a semblé que ces autres composants étaient toujours nuls au constructeur. du composant parent.
Utiliser @PostConstruct résout les problèmes suivants:
@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
}
}
C'est le coupable d'avoir donné NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();
Nous utilisons Spring - il n'est pas nécessaire de créer l'objet manuellement. La création d'objet sera effectuée par conteneur IoC.