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




spring @autowired null value (10)

Nota: se pretende que sea una respuesta canónica para un problema común.

Tengo una clase Spring @Service ( MileageFeeCalculator ) que tiene un campo rateService ( rateService ), pero el campo es null cuando trato de usarlo. Los registros muestran que tanto el bean MileageFeeCalculator como el bean MileageRateService se están creando, pero recibo una NullPointerException cada vez que trato de llamar al método mileageCharge en mi bean del servicio. ¿Por qué Spring no está conectando automáticamente 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
    }
}

Bean de servicio que debe ser autowired en MileageFeeCalculator pero no es:

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

Cuando intento obtener 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)
    ...

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

RESPUESTA ORIGINAL:

No sé si ayuda a alguien, pero me quedé con el mismo problema, incluso cuando hacía las cosas aparentemente bien. En mi método principal, 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 archivo token.xml he tenido una línea

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

Noté 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 entrar. En un pep-config.xml solo tenía 2 beans:

<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 elemento <context:component-scan/> no está presente en absoluto, pero cuando está presente y tiene algunos bs como paquete base, 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 ahora mismo.


Otra solución sería poner call: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Al constructor MileageFeeCalculator como este:

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

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

La causa principal es que, en lugar de utilizar el bean creado automáticamente mantenido por el contenedor Spring IoC (cuyo campo @Autowired se inyecta correctamente), estoy newing mi propia instancia de ese tipo de bean y usándolo. Por supuesto, el campo @Autowired de @Autowired es nulo porque Spring no tiene posibilidad de inyectarlo.


También puede solucionar este problema utilizando la anotación @Service en la clase de servicio y pasando la clase A de bean requerida como un parámetro al otro constructor de classB de beans y anotar el constructor de classB 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();
    }
}

Si no está codificando una aplicación web, asegúrese de que su clase en la que se realiza @Autowiring es un bean spring. Normalmente, el contenedor de primavera no estará al tanto de la clase que podríamos considerar como un frijol de primavera. Tenemos que decirle al contenedor de primavera sobre nuestras clases de primavera.

Esto se puede lograr configurando en appln-contxt o la mejor manera es anotar la clase como @Component y por favor no cree la clase anotada usando un nuevo operador. Asegúrese de obtenerlo desde el contexto de Appln 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();

    }

}

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

MileageFeeCalculator calc = new MileageFeeCalculator();

Con la anotación @Service , @Component , @Configuration beans se crean en el
contexto de la aplicación de Spring cuando se inicia el servidor. Pero cuando creamos objetos utilizando 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());
        }
    }
}

}

Creo que te has perdido la instrucción de saltar a las clases con anotación.

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

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

Spring solo inyecta instancias de aquellas clases que se crean como bean o se marcan con anotación.

Las clases marcadas con una anotación deben identificarse con la primavera antes de inyectarlas, @ComponentScan instruye a las clases marcadas con anotaciones en la primavera. Cuando Spring encuentra @Autowired , busca el bean relacionado e inyecta la instancia requerida.

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


El campo anotado @Autowired es null porque Spring no conoce la copia de MileageFeeCalculator que creó con la new y no sabía cómo realizarla 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 en ellos mediante la comparación las 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 IoC no es mágico, y no tiene forma de saber acerca de los objetos de Java a menos que de alguna manera se lo informe. Cuando llama a new , la JVM crea una copia de la copia del nuevo objeto y se lo entrega directamente a usted, nunca pasa por el proceso de configuración. Hay tres formas de configurar tus beans.

He publicado todo este código, utilizando Spring Boot para iniciar, en este proyecto de GitHub ; Puede ver un proyecto en ejecución completa para cada enfoque para ver todo lo que necesita para que funcione. Etiqueta con la NullPointerException : nonworking

Inyecta tus frijoles

La opción más preferible es dejar que Spring conecte automáticamente todos tus beans; 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 quería, también conecte el MileageFeeCalculator esta 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 utilizando los ámbitos de beans de Spring .

Etiqueta que funciona al inyectar el objeto de servicio @MileageFeeCalculator : working-inject-bean

Use @Configurable

Si realmente necesita que los objetos creados con new se enciendan automáticamente, puede usar la anotación Spring @Configurable junto con el tejido en tiempo de compilación de AspectJ para inyectar sus objetos. Este enfoque inserta código en el constructor de su objeto que alerta a Spring de que se está creando 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 ( @EnableSpringConfigured con la sintaxis JavaConfig). El sistema Roo Active Record utiliza este enfoque para permitir que las new instancias 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 utilizando @Configurable en el objeto de servicio: working-configurable

Búsqueda manual de frijoles: no recomendado

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

Para hacer esto, necesita una clase a la que Spring pueda dar una referencia al objeto 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;
    }
}

Luego, su código heredado puede llamar a 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 al buscar manualmente el objeto de servicio en el contexto de Spring: working-manual-lookup


En realidad, debe utilizar objetos gestionados por JVM o objetos gestionados 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 de conexión automática.

MileageFeeCalculator calc = new MileageFeeCalculator();

así que no funcionará de esa manera.

La solución hace que este MileageFeeCalculator sea un objeto de conexión automática en el propio Controlador.

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

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




autowired