¿Por qué mi campo Spring @Autowired es nulo?


608

Nota: Esto pretende ser una respuesta canónica para un problema común.

Tengo una @Serviceclase Spring ( MileageFeeCalculator) que tiene un @Autowiredcampo ( rateService), pero el campo es nullcuando trato de usarlo. Los registros muestran que tanto el MileageFeeCalculatorbean como el MileageRateServicebean se están creando, pero recibo un NullPointerExceptionmensaje cada vez que intento llamar al mileageChargemétodo en mi bean de servicio. ¿Por qué no Spring autowiring el campo?

Clase de controlador:

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

Clase de servicio:

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

Service bean que debe conectarse automáticamente MileageFeeCalculatorpero no lo es:

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

Cuando intento GET /mileage/3, obtengo esta excepción:

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
Otro escenario puede ser cuando Fse llama a bean dentro del constructor de otro bean S. En este caso, pase el bean requerido Fcomo parámetro al otro Sconstructor de beans y anote el constructor de Swith @Autowire. Recuerde anotar la clase del primer bean Fcon @Component.
aliopi

Codifiqué algunos ejemplos muy similares a este usando Gradle aquí: github.com/swimorsink/spring-aspectj-examples . Esperemos que alguien lo encuentre útil.
Ross117

Respuestas:


649

El campo anotado @Autowiredse nulldebe a que Spring no sabe acerca de la copia de la MileageFeeCalculatorque creó newy no sabía cómo conectarla automáticamente.

El contenedor Spring Inversion of Control (IoC) tiene tres componentes lógicos principales: un registro (llamado ApplicationContext) de componentes (beans) que están disponibles para ser utilizados por la aplicación, un sistema configurador que inyecta las dependencias de los objetos haciendo coincidir los dependencias con beans en el contexto, y un solucionador de dependencias que puede ver una configuración de muchos beans diferentes y determinar cómo instanciarlos y configurarlos en el orden necesario.

El contenedor de IoC no es mágico, y no tiene forma de conocer los objetos de Java a menos que de alguna manera le informe de ellos. Cuando llama new, la JVM crea una copia del nuevo objeto y se lo entrega directamente; nunca pasa por el proceso de configuración. Hay tres formas de configurar los beans.

He publicado todo este código, usando Spring Boot para el lanzamiento, en este proyecto de GitHub ; puede ver un proyecto en ejecución completo para cada enfoque para ver todo lo que necesita para que funcione. Etiqueta con el NullPointerException:nonworking

Inyecta tus frijoles

La opción más preferible es dejar que Spring conecte automáticamente todos sus frijoles; esto requiere la menor cantidad de código y es el más fácil de mantener. Para hacer que el cableado automático funcione como usted desea, también realice el cableado automático de MileageFeeCalculatoresta manera:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Si necesita crear una nueva instancia de su objeto de servicio para diferentes solicitudes, aún puede usar la inyección usando los ámbitos de Spring Bean .

Etiqueta que funciona inyectando el @MileageFeeCalculatorobjeto de servicio:working-inject-bean

Use @Configurable

Si realmente necesita objetos creados con newautoconexión, puede usar la @Configurableanotación Spring junto con el tejido de tiempo de compilación AspectJ para inyectar sus objetos. Este enfoque inserta código en el constructor de su objeto que alerta a Spring de que está siendo creado para que Spring pueda configurar la nueva instancia. Esto requiere un poco de configuración en su compilación (como compilar con ajc) y activar los controladores de configuración de tiempo de ejecución de Spring ( @EnableSpringConfiguredcon la sintaxis JavaConfig). Este enfoque es utilizado por el sistema Roo Active Record para permitir que las newinstancias de sus entidades obtengan la información de persistencia necesaria inyectada.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Etiqueta que funciona mediante el uso @Configurableen el objeto de servicio:working-configurable

Búsqueda manual de frijoles: no recomendado

Este enfoque es adecuado solo para interactuar con código heredado en situaciones especiales. Casi siempre es preferible crear una clase de adaptador singleton que Spring pueda autoalambrar y que el código heredado pueda llamar, pero es posible solicitar directamente un bean en el contexto de la aplicación Spring.

Para hacer esto, necesita una clase a la que Spring pueda dar una referencia al ApplicationContextobjeto:

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

Luego, su código heredado puede llamar getContext()y recuperar los beans que necesita:

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

Etiqueta que funciona buscando manualmente el objeto de servicio en el contexto Spring: working-manual-lookup


1
La otra cosa a tener en cuenta es hacer objetos para beans en un @Configurationbean, donde se anota el método para hacer una instancia de una clase de bean particular @Bean.
Donal Fellows

@DonalFellows No estoy completamente seguro de lo que estás hablando ("hacer" es ambiguo). ¿Estás hablando de un problema con múltiples llamadas a @Beanmétodos cuando usas Spring Proxy AOP?
chrylis -cautiouslyoptimistic-

1
Hola, me encuentro con un problema similar, sin embargo, cuando uso su primera sugerencia, mi aplicación piensa que "calc" es nulo al llamar al método "kilometrajeFee". Es como si nunca inicializara el @Autowired MileageFeeCalculator calc. ¿Alguna idea?
Theo

Creo que debería agregar una entrada en la parte superior de su respuesta que explique que la recuperación del primer bean, la raíz desde la que hace todo, debe hacerse a través de ApplicationContext. Algunos usuarios (para los cuales he cerrado como duplicados) no entienden esto.
Sotirios Delimanolis

@SotiriosDelimanolis Por favor explique el problema; No estoy seguro exactamente qué punto estás haciendo.
chrylis -cautelosamente optimista-

59

Si no está codificando una aplicación web, asegúrese de que su clase en la que se realiza @Autowiring sea un bean de primavera. Por lo general, el contenedor de primavera no se dará cuenta de la clase que podríamos considerar un grano de primavera. Tenemos que decirle al contenedor Spring acerca de nuestras clases de primavera.

Esto se puede lograr configurando en appln-contxt o la mejor manera es anotar la clase como @Component y no cree la clase anotada con un nuevo operador. Asegúrese de obtenerlo de Appln-context como se muestra a continuación.

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

    }

}

hola, revisé tu solución, eso es correcto. Y aquí me gustaría saber "¿Por qué no creamos una instancia de clase anotada utilizando un nuevo operador? ¿Puedo saber la razón detrás de eso
Ashish

3
si creas el objeto usando new, estarás manejando el ciclo de vida del bean que contradice el concepto de COI. Tenemos que pedirle al contenedor que lo haga, lo que lo hace de una mejor manera
Shirish Coolkarni

41

En realidad, debe usar Objetos administrados por JVM u Objeto administrado por Spring para invocar métodos. a partir de su código anterior en su clase de controlador, está creando un nuevo objeto para llamar a su clase de servicio que tiene un objeto con cableado automático.

MileageFeeCalculator calc = new MileageFeeCalculator();

entonces no funcionará de esa manera.

La solución hace que este MileageFeeCalculator sea un objeto con cableado automático en el Controlador mismo.

Cambie su clase de controlador como a continuación.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

44
Esta es la respuesta. Debido a que está creando una instancia de un nuevo MilageFeeCalculator por su cuenta, Spring no participa en la creación de instancias, por lo que Spring spring no tiene conocimiento de que el objeto existe. Por lo tanto, no puede hacerle nada, como inyectar dependencias.
Robert Greathouse

26

Una vez me encontré con el mismo problema cuando no estaba acostumbrado the life in the IoC world. El @Autowiredcampo de uno de mis beans es nulo en tiempo de ejecución.

La causa raíz es que, en lugar de usar el bean creado automáticamente mantenido por el contenedor Spring IoC (cuyo @Autowiredcampo se indeedinyectó correctamente), soy newingmi propia instancia de ese tipo de bean y lo uso. Por supuesto, este @Autowiredcampo es nulo porque Spring no tiene oportunidad de inyectarlo.


22

Su problema es nuevo (creación de objetos en estilo java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Con la anotación @Service, @Component, @Configurationhabas se crean en el
contexto de la aplicación de la primavera cuando se inicia el servidor. Pero cuando creamos objetos usando un nuevo operador, el objeto no se registra en el contexto de la aplicación que ya está creado. Por ejemplo, la clase Employee.java que he usado.

Mira esto:

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

Soy nuevo en Spring, pero descubrí esta solución de trabajo. Por favor dime si es una forma despreciable.

Hago Spring inject applicationContexten este 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;       
    }
}

Puede poner este código también en la clase de aplicación principal si lo desea.

Otras clases pueden usarlo así:

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

De esta forma, cualquier objeto de la aplicación puede obtener cualquier bean (también se puede identificar new) y de forma estática .


1
Este patrón es necesario para que Spring Beans sea accesible al código heredado, pero debe evitarse en el nuevo código.
chrylis -cautiouslyoptimistic-

2
No eres nuevo en la primavera. Eres un profesional :)
sapy

me salvaste ...
Govind Singh

En mi caso, requería esto porque había pocas clases de terceros. Spring (COI) no tenía el control sobre ellos. Estas clases nunca fueron llamadas desde mi aplicación de arranque de primavera. Seguí este enfoque y funcionó para mí.
Joginder Malik

12

Parece ser un caso raro, pero esto es lo que me pasó:

Usamos en @Injectlugar de @Autowiredcuál es el estándar Java soportado por Spring. Todos los lugares funcionaron bien y los frijoles se inyectaron correctamente, en lugar de un solo lugar. La inyección de frijol parece igual

@Inject
Calculator myCalculator

¡Por fin descubrimos que el error fue que nosotros (en realidad, la función de autocompletar Eclipse) importamos en com.opensymphony.xwork2.Injectlugar de javax.inject.Inject!

Entonces, para resumir, asegúrese de que sus anotaciones ( @Autowired, @Inject, @Service, ...) tienen paquetes correctos!


5

Creo que te has perdido para indicarle a Spring que escanee las clases con anotaciones.

Puede usar @ComponentScan("packageToScan")en la clase de configuración de su aplicación Spring para indicarle a Spring que escanee.

@Service, @Component las anotaciones etc. agregan meta descripción.

Spring solo inyecta instancias de esas clases que se crean como bean o se marcan con anotaciones.

Las clases marcadas con anotación deben identificarse por resorte antes de inyectar, @ComponentScaninstruir la búsqueda de primavera para las clases marcadas con anotación. Cuando Spring lo encuentra @Autowired, busca el bean relacionado e inyecta la instancia requerida.

Agregando solo anotaciones, no repara ni facilita la inyección de dependencia, Spring necesita saber dónde buscar.


me encontré con esto cuando olvidé agregar <context:component-scan base-package="com.mypackage"/>a mi beans.xmlarchivo
Ralph Callaway

5

Si esto sucede en una clase de prueba, asegúrese de no haber olvidado anotar la clase.

Por ejemplo, en Spring Boot :

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

Transcurre algún tiempo ...

Spring Boot continúa evolucionando . Ya no es necesario usarlo @RunWith si usa la versión correcta de JUnit .

Para @SpringBootTesttrabajar de forma independiente, debe usar @Testdesde JUnit5 en lugar de JUnit4 .

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

@SpringBootTest
public class MyTests {
    ....

Si se consigue este mal configuración de sus pruebas se compilan, pero @Autowiredy @Valuecampos (por ejemplo) serán null. Dado que Spring Boot funciona por arte de magia, es posible que tenga pocas vías para depurar directamente este error.



Nota: @Valueserá nulo cuando se use con staticcampos.
nobar

Spring ofrece numerosas formas de fallar (sin la ayuda del compilador). Cuando las cosas van mal, su mejor opción es volver al punto de partida, utilizando solo la combinación de anotaciones que sabe que funcionarán juntas.
nobar

4

Otra solución sería poner call: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
al constructor MileageFeeCalculator así:

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

Esto utiliza publicación insegura.
chrylis -cautiouslyoptimistic-

3

ACTUALIZACIÓN: las personas realmente inteligentes se apresuraron a señalar esta respuesta, que explica la rareza, que se describe a continuación

RESPUESTA ORIGINAL:

No sé si ayuda a alguien, pero me quedé atrapado con el mismo problema incluso mientras hacía las cosas aparentemente bien. En mi método Main, tengo un código como este:

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

y en un token.xmlarchivo he tenido una línea

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

Me di cuenta de que el package.path ya no existe, por lo que acabo de dejar la línea para siempre.

Y después de eso, NPE comenzó a llegar. En un pep-config.xmlsolo tenía 2 frijoles:

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

y la clase SomeAbac tiene una propiedad declarada como

@Autowired private Settings settings;

por alguna razón desconocida, la configuración es nula en init (), cuando el <context:component-scan/>elemento no está presente en absoluto, pero cuando está presente y tiene algunos bs como basePackage, todo funciona bien. Esta línea ahora se ve así:

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

y funciona. Puede ser que alguien pueda dar una explicación, pero para mí es suficiente en este momento)


55
Esa respuesta es la explicación. <context:component-scan/>implícitamente habilita lo <context:annotation-config/>necesario para @Autowiredque funcione.
ForNeVeR

3

Este es el culpable de dar NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();Estamos usando Spring, no es necesario crear objetos manualmente. La creación de objetos será atendida por el contenedor IoC.


2

También puede solucionar este problema utilizando la anotación @Service en la clase de servicio y pasando la clase de bean A requerida como parámetro al otro constructor de clase B de beans y anote el constructor de la clase B con @Autowired. Fragmento de muestra aquí:

@Service
public class ClassB {

    private ClassA classA;

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

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

esto funcionó para mí, pero ¿podría explicar cómo está resolviendo el problema?
CruelEngine

1
@CruelEngine, mira esto es la inyección del constructor (donde configuras explícitamente un objeto) en lugar de solo usar la inyección de campo (esto se hace principalmente mediante la configuración del resorte). Por lo tanto, si está creando un objeto de ClassB utilizando un operador "nuevo", hay otro ámbito que no sería visible o se establecería automáticamente para ClassA. Por lo tanto, al llamar a classB.useClassAObjectHere () arrojaría NPE ya que el objeto classA no se cableó automáticamente si solo declara la inyección de campo. Leer chrylis está tratando de explicar lo mismo. Y es por eso que se recomienda la inyección del constructor sobre la inyección de campo. ¿Tiene sentido ahora?
Abhishek

1

Lo que no se ha mencionado aquí se describe en este artículo en el párrafo "Orden de ejecución".

Después de "aprender" que tenía que anotar una clase con @Component o los derivados @Service o @Repository (supongo que hay más), para conectar automáticamente otros componentes dentro de ellos, me sorprendió que estos otros componentes aún fueran nulos dentro del constructor del componente padre.

El uso de @PostConstruct resuelve que:

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

y:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

1

Esto solo es válido en caso de prueba unitaria.

Mi clase de servicio tenía una anotación de servicio y era @autowiredotra clase de componente. Cuando probé, la clase de componente se estaba volviendo nula. Porque para la clase de servicio estaba creando el objeto usandonew

Si está escribiendo una prueba unitaria, asegúrese de no crear objetos utilizando new object(). Utilice en su lugar injectMock.

Esto solucionó mi problema. Aquí hay un enlace útil


0

También tenga en cuenta que si, por cualquier razón, realiza un método en un @Serviceas final, los beans con conexión automática a los que accederá siempre serán null.


0

En palabras simples, existen principalmente dos razones para que un @Autowiredcampo seanull

  • Tu clase no es un frijol de primavera.

  • El campo no es un frijol.


0

No está completamente relacionado con la pregunta, pero si la inyección de campo es nula, la inyección basada en el constructor seguirá funcionando bien.

    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;
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.