java - predestroy - Por qué no se llama al método @PostConstruct cuando autowiring prototype bean con un argumento constructor




spring ioc (4)

Tengo un bean de alcance de prototipo, que quiero que se inyecte mediante la anotación @Autowired. En este bean, también hay un método @PostConstruct que no es llamado por Spring y no entiendo por qué.

Mi definición de frijol:

package somepackage;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
@Scope("prototype")
public class SomeBean {

    public SomeBean(String arg) {
        System.out.println("Constructor called, arg: " + arg);
    }

    @PostConstruct
    private void init() {
        System.out.println("Post construct called");
    }

}

Clase de JUnit donde quiero inyectar frijol:

package somepackage;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:applicationContext-test.xml")
public class SomeBeanTest {

    @Autowired
    ApplicationContext ctx;

    @Autowired
    @Value("1")
    private SomeBean someBean;

    private SomeBean someBean2;

    @Before
    public void setUp() throws Exception {
        someBean2 = ctx.getBean(SomeBean.class, "2");
    }

    @Test
    public void test() {
        System.out.println("test");
    }
}

Configuración de la primavera:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>

La salida de la ejecución:

Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test

Cuando inicializo el bean llamando a getBean desde ApplicationContext todo funciona como se esperaba. Mi pregunta es por qué inyectar bean mediante la combinación @Autowire y @Value no está llamando @PostConstruct método @PostConstruct

https://code.i-harness.com



Cuando ejecuta la prueba, se crea un nuevo bean para la prueba (es decir, no la clase SomeBean, la clase SomeBeanTest). @Value se creará como un valor miembro (no un bean) y, por lo tanto, BeanPostProcessor (AutowiredAnnotationBeanPostProcessor) no intentará inicializarlo.

Para mostrar que he movido su System.out.println () a log.info () (mantenga las líneas sincronizadas). Habilitar el registro de depuración muestra:

DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - Procesando el elemento inyectado del bean 'somepackage.SomeBeanTest': AutowiredFieldElement para org.springframework.context.ApplicationContext somepackage.SomeBeanTest.ctx

DEBUG org.springframework.core.annotation.AnnotationUtils - Falló la anotación de meta-introspección [interfaz org.springframework.beans.factory.annotation.Autowired]: java.lang.NullPointerException

DEBUG org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowiring por tipo desde el nombre de bean 'somepackage.SomeBeanTest' para bean denominado '[email protected]cffa.

DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - Procesando el elemento inyectado del frijol 'somepackage.SomeBeanTest': AutowiredFieldElement para el somepackage privado.SomeBean somepackage.SomeBeanTest.someBean

DEBUG org.springframework.core.annotation.AnnotationUtils - Fallo en la anotación de meta-introspección [interfaz org.springframework.beans.factory.annotation.Value]: java.lang.NullPointerException

DEBUG org.springframework.beans.BeanUtils: no se ha encontrado ningún editor de propiedades [somepackage.SomeBeanEditor] para el tipo somepackage.SomeBean según la convención de sufijo 'Editor'

INFO somepackage.SomeBean - Constructor llamado, arg: 0

DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Antes del método de prueba: ....

INFO somepackage.SomeBeanTest - prueba

DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Después del método de prueba:

Una forma de solucionar el problema es inicializar el bean manualmente:

@Value("0")
private SomeBean someBean;

@PostConstruct
private void customInit() {
    log.info("Custom Init method called");
    someBean.init();
}

Que producirá:

INFO somepackage.SomeBean - Constructor llamado, arg: 0

INFO somepackage.SomeBeanTest - Método de inicio personalizado llamado

INFO somepackage.SomeBean - Post constructo llamado

INFO somepackage.SomeBeanTest - prueba


¿Por qué se usa @Value en lugar de @Autowired?

La anotación @Value se usa para inyectar valores y normalmente tiene como cadenas de destino, primitivas, tipos de caja y colecciones Java.

Según la documentación de Spring :

La anotación @Value se puede colocar en los campos, métodos y parámetros del método / constructor para especificar un valor predeterminado.

Value recibe una expresión de cadena que Spring utiliza para manejar la conversión al objeto de destino. Esta conversión puede realizarse a través de la conversión de tipos de Spring , el editor de propiedades de java y las expresiones SpEL de Spring . El objeto resultante de esta conversión, en principio, no se administra mediante Spring (aunque puede devolver un bean ya administrado desde cualquiera de estos métodos).

Por otro lado, el AutowiredAnnotationBeanPostProcessor es un

Implementación de BeanPostProcessor que autoriza campos anotados, métodos de establecimiento y métodos de configuración arbitrarios. Los miembros a inyectar se detectan mediante una anotación de Java 5: de forma predeterminada, las anotaciones @Autowired y @Value de Spring.

Esta clase maneja la inyección de campo, resuelve las dependencias y, finalmente, llama al método doResolveDependency , es en este método donde se resuelve la 'prioridad' de la inyección, comprueba si hay un valor sugerido que normalmente es una cadena de expresión, esto sugerido value es el contenido de la anotación Value , por lo que en caso de que esté presente, se realiza una llamada a la clase SimpleTypeConverter , de lo contrario Spring busca frijoles candicate y resuelve el autowire.

Simplemente la razón @Autowired se ignora @Value y se usa @Value , es porque la estrategia de inyección de valor se verifica primero. Obviamente, siempre tiene que ser una prioridad, Spring también puede lanzar una excepción cuando se usan múltiples anotaciones en conflicto, pero en este caso está determinada por esa verificación previa al valor sugerido.

No pude encontrar nada relacionado con esta 'prioridad' es primavera, pero simple es porque no pretende usar estas anotaciones juntas, como por ejemplo, tampoco pretende usar @Autowired y @Resource juntos.

¿Por qué @Value crea una nueva intención del objeto?

Anteriormente dije que se llamaba a la clase SimpleTypeConverter cuando estaba presente el valor sugerido, la llamada específica es al método convertIfNecessary , este es el que realiza la conversión de la cadena en el objeto de destino, de nuevo, esto se puede hacer con el editor de propiedades o un convertidor personalizado, pero ninguno de ellos se utilizan aquí. Una expresión SpEL tampoco se usa, solo una cadena literal.

Spring comprueba primero si el objeto de destino es una cadena o una colección / matriz (puede convertir, por ejemplo, una lista delimitada por comas), luego verifica si el destino es una enumeración, si lo es, intenta convertir la cadena, si no lo es, y no es una interfaz sino una clase, verifica la existencia de un Constructor(String) para crear finalmente el objeto (no administrado por Spring). Básicamente, este convertidor intenta muchas formas diferentes de convertir la cadena al objeto final.

Esta creación de instancias solo funcionará usando una cadena como argumento, si usa, por ejemplo, una expresión SpEL para devolver un @Value("#{2L}") largo @Value("#{2L}") , y usa un objeto con un Constructor(Long) , lanzará una IllegalStateException con un mensaje similar:

No se puede convertir el valor del tipo 'java.lang.Long' en el tipo requerido 'com.fiberg.test.springboot.object.Hut': no ​​se encontraron editores que coincidan ni una estrategia de conversión

Solución posible

Usando una clase simple @Configuration como proveedor.

public class MyBean {
    public MyBean(String myArg) { /* ... */ }
    // ...
    @PostConstruct public init() { /* ... */ }
}

@Configuration
public class MyBeanSupplier {
    @Lazy
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
           proxyMode = ScopedProxyMode.NO)
    public MyBean getMyBean(String myArg) {
        return new MyBean(myArg);
    }
}

Podría definir MyBean como una clase estática en la clase MyBeanSupplier si es el único método que tendría. Además, no puede usar el modo proxy ScopedProxyMode.TARGET_CLASS, porque deberá proporcionar los argumentos como beans y los argumentos pasados ​​a getMyBean se ignorarán.

Con este enfoque, no podría auto cablear el frijol en sí mismo, sino que, por el contrario, podría autoalimentar al proveedor y luego llamar al método obtener.

// ...
public class SomeBeanTest {
    @Autowired private MyBeanSupplier supplier;
    // ...
    public void setUp() throws Exception {
        someBean = supplier.getMyBean("2");
    }
}

También puede crear el bean utilizando el contexto de la aplicación.

someBean = ctx.getBean(SomeBean.class, "2");

Y el método @PostConstruct debe llamarse sin importar cuál use, pero no se llama a @PreDestroy en los prototipos de beans .


Si no estoy equivocado: - La inyección de campo de REGLA de primavera ocurre después de que se construyen los objetos, ya que obviamente el contenedor no puede establecer una propiedad de algo que no existe. El campo siempre estará desarmado en el constructor.

Está intentando imprimir el valor inyectado (o hacer una inicialización real :)), utilizando PostConstruct: - en su código tiene dos beans. 1 SomeBean después de que el constructor llamado el valor archivado se establezca. 2 SomeBean2 le está pasando arg como valor 2 que se ha establecido en el segundo bean , puede usar un método anotado con @PostConstruct, que se ejecutará después del proceso de inyección.

@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:applicationContext-test.xml")
public class SomeBeanTest {
@Autowired
ApplicationContext ctx;
@Autowired
@Value("1")
private SomeBean someBean;

private SomeBean someBean2;

@Before
public void setUp() throws Exception {
    someBean2 = ctx.getBean(SomeBean.class, "2");
}

@Test
public void test() {
    System.out.println("test");
}
}




prototype-scope