¿Por qué la anotación @Lazy puede romper el bucle infinito?

El siguiente contenido se basa en Spring6.0.4.

En el último artículo, el hermano Song habló con usted sobre que no todas las dependencias circulares se pueden resolver en Spring, y algunas dependencias circulares no se pueden resolver con Spring de forma predeterminada. Los amigos que no estén familiarizados con él pueden leer primero el artículo anterior .

Tome el caso de la primera sección del artículo anterior como ejemplo. Inyectar los beans de cada uno en el método de construcción es completamente un bucle sin fin en este momento. ¿Existe realmente una solución para este bucle sin fin?

Spring proporciona una forma de solucionarlo, pero parece que no se ha solucionado.¿Por qué dices eso?, lo entenderás después de leer este artículo.

1. @Perezoso

Como se muestra en el título de este artículo, las tres dependencias circulares mencionadas en el artículo anterior que no se pueden resolver automáticamente se pueden resolver agregando la anotación @Lazy .

Si es una inyección de constructor, de la siguiente manera:

@Service
public class AService {

    BService bService;

    @Lazy
    public AService(BService bService) {
        this.bService = bService;
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    AService aService;
    
    @Lazy
    public BService(AService aService) {
        this.aService = aService;
    }

    public AService getaService() {
        return aService;
    }
}

La anotación @Lazy se puede agregar al constructor de AService o BService, o ambos.

Después de agregarlo, comenzaremos el proyecto nuevamente y no habrá ningún error. Parece que el problema se ha solucionado, pero sigue sin tener casi sentido. Amigos, echen un vistazo a mi código de inicio:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AService aService = ctx.getBean(AService.class);
BService bService = ctx.getBean(BService.class);
System.out.println("aService.getClass() = " + aService.getClass());
System.out.println("bService.getClass() = " + bService.getClass());
System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass());
System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());

El resultado final de la impresión es el siguiente:

Los amigos pueden ver que los beans que obtenemos de AService y BService son todos objetos normales sin proxy. De hecho, nuestro código original no necesita ser proxy. Sin embargo, BService en AService y AService en BService son objetos proxy. Es lógico pensar que BService en AService debe ser consistente con el BService que obtuvimos del contenedor Spring, y AService en BService también debe ser consistente con el AService obtenido en el contenedor Spring, pero de hecho, los dos no son lo mismo.

Pero esto es fácil de entender. Por qué Spring puede desatar un nudo muerto es porque los beans inyectados por AService y BService no son beans originales, sino beans proxy. El BService inyectado en AService es un objeto proxy. De manera similar, el AService inyectado en BService también es un objeto proxy.

Es por eso que dije al principio que este problema lo resolvió Spring pero no lo resolvió.

De hecho, este es el principio de funcionamiento de la anotación @Lazy . Mire el nombre, el objeto con esta anotación se cargará de forma diferida. De hecho, el objeto marcado con esta anotación generará automáticamente un objeto proxy.

Los otros dos problemas mencionados en el artículo anterior también se pueden resolver mediante la anotación @Lazy , el código es el siguiente:

@Service
@Scope("prototype")
public class AService {
    @Lazy
    @Autowired
    BService bService;

}
@Service
@Scope("prototype")
public class BService {
    @Lazy
    @Autowired
    AService aService;
}

Aquí, @Lazy solo necesita uno para resolver el problema, o puede agregar ambos.

Para el caso de la anotación @Async, también se puede resolver con la anotación @Lazy:

@Service
public class AService {
    @Autowired
    @Lazy
    BService bService;

    @Async
    public void hello() {
        bService.hello();
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    @Autowired
    AService aService;

    public void hello() {
        System.out.println("xxx");
    }

    public AService getaService() {
        return aService;
    }
}

¡De esta manera, se puede romper la dependencia circular!

En una palabra, la anotación @Lazy rompe las dependencias circulares al establecer una capa intermedia de proxy.

2. Análisis de principios

A continuación, analicemos el código fuente del procesamiento de anotaciones @Lazy.

No analizaré el código fuente de esta pieza desde cero, porque ¿ cómo @Autowired inyectó variables en la parte anterior de todo el flujo de procesamiento y el artículo anterior? El contenido introducido es el mismo, para quien no lo conozca, se recomienda leer primero @Autowired ¿Cómo entra la inyección de variables? un articulo Aquí tomaré prestado el resumen de este artículo para guiar a mis amigos a revisar el proceso de inyección de atributos:

  1. Al crear un bean, después de crear el bean original, se llamará al método populateBean para completar las propiedades del bean.
  2. A continuación, llame al método postProcessAfterInstantiation para determinar si es necesario ejecutar el posprocesador, de lo contrario, regresa directamente.
  3. Llame al método postProcessProperties para desencadenar la ejecución de varios posprocesadores.

  1. En el método del paso 3, llame a findAutowiringMetadata, que activará aún más el método buildAutorwiringMetadata para buscar propiedades o métodos que contengan anotaciones @Autowired, @Value e @Inject, y encapsúlelas como InjectedElement para devolverlas.
  2. Llame al método InjectedElement#inject para la inyección de atributos.

  1. A continuación, ejecute el método resolveCachedArgument para intentar encontrar el objeto Bean necesario en la memoria caché.
  2. Si no existe en el caché, llame al método resolveFieldValue para encontrar el Bean en el contenedor.
  3. Finalmente, llame a los métodos makeAccessible y set para completar la asignación de atributos.

En el paso 7, llame al método resolveFieldValue para resolver el Bean, y la lógica relevante de la anotación @Lazy se procesa en este método (correspondiente a ¿cómo @Autowired inyecta variables en él? Sección 3.2 del artículo).

El método resolveFieldValue finalmente se ejecutará en el método resolveDependency:

@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<string> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
	if (Optional.class == descriptor.getDependencyType()) {
		return createOptionalDependency(descriptor, requestingBeanName);
	}
	else if (ObjectFactory.class == descriptor.getDependencyType() ||
			ObjectProvider.class == descriptor.getDependencyType()) {
		return new DependencyObjectProvider(descriptor, requestingBeanName);
	}
	else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
		return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
	}
	else {
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
	}
}

En este método, primero determinará si el tipo de propiedad inyectada es Opcional, ObjectFactory o anotación en JSR- 330. No estamos aquí, así que tome la última rama.

En último lugar, llame primero al método getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary para ver si el objeto Bean debe cargarse de forma diferida y la anotación @Lazy se procesa aquí. Si la carga diferida es posible, entonces el valor de retorno de este método no es nulo y se puede devolver directamente, y no es necesario ejecutar el método doResolveDependency.

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary:

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
	return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

Echemos un vistazo, este método primero llamará a isLazy para determinar si se requiere la carga diferida, si es necesario, llame al método buildLazyResolutionProxy para construir un objeto con carga diferida; si no, simplemente devuelva un valor nulo directamente.

protected boolean isLazy(DependencyDescriptor descriptor) {
	for (Annotation ann : descriptor.getAnnotations()) {
		Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
		if (lazy != null &amp;&amp; lazy.value()) {
			return true;
		}
	}
	MethodParameter methodParam = descriptor.getMethodParameter();
	if (methodParam != null) {
		Method method = methodParam.getMethod();
		if (method == null || void.class == method.getReturnType()) {
			Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
			if (lazy != null &amp;&amp; lazy.value()) {
				return true;
			}
		}
	}
	return false;
}

Este método de juicio es principalmente para verificar si los diversos parámetros en la clase actual contienen la anotación @Lazy, si el método, la propiedad y el nombre de la clase contienen la anotación @Lazy.Si la hay, devuelve verdadero, de lo contrario, devuelve falso.

Veamos el método buildLazyResolutionProxy:

private Object buildLazyResolutionProxy(
		final DependencyDescriptor descriptor, final @Nullable String beanName, boolean classOnly) {
	BeanFactory beanFactory = getBeanFactory();
	final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
	TargetSource ts = new TargetSource() {
		@Override
		public Class<!--?--> getTargetClass() {
			return descriptor.getDependencyType();
		}
		@Override
		public boolean isStatic() {
			return false;
		}
		@Override
		public Object getTarget() {
			Set<string> autowiredBeanNames = (beanName != null ? new LinkedHashSet&lt;&gt;(1) : null);
			Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
			if (target == null) {
				Class<!--?--> type = getTargetClass();
				if (Map.class == type) {
					return Collections.emptyMap();
				}
				else if (List.class == type) {
					return Collections.emptyList();
				}
				else if (Set.class == type || Collection.class == type) {
					return Collections.emptySet();
				}
				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
						"Optional dependency not present for lazy injection point");
			}
			if (autowiredBeanNames != null) {
				for (String autowiredBeanName : autowiredBeanNames) {
					if (dlbf.containsBean(autowiredBeanName)) {
						dlbf.registerDependentBean(autowiredBeanName, beanName);
					}
				}
			}
			return target;
		}
		@Override
		public void releaseTarget(Object target) {
		}
	};
	ProxyFactory pf = new ProxyFactory();
	pf.setTargetSource(ts);
	Class<!--?--> dependencyType = descriptor.getDependencyType();
	if (dependencyType.isInterface()) {
		pf.addInterface(dependencyType);
	}
	ClassLoader classLoader = dlbf.getBeanClassLoader();
	return (classOnly ? pf.getProxyClass(classLoader) : pf.getProxy(classLoader));
}

Este método se usa para generar objetos proxy. El objeto proxy TargetSource se construye aquí. En su método getTarget, doResolveDependency se ejecutará para obtener el objeto proxy (para conocer la lógica de adquisición de doResolveDependency, consulte el artículo ¿Cómo inyectó variables @Autowired?), y el método getTarget solo se llamará cuando sea necesario . Por lo tanto, lo que hace la anotación @Lazy es encontrar el objeto inyectado en el contenedor Spring al inyectar valores en cada atributo en el Bean. Ahora, en lugar de buscarlo, primero coloque un objeto proxy en él y luego vaya al contenedor Spring para encontrarlo cuando sea necesario.

No diré más sobre la lógica de seguimiento. Amigos, consulte @Autowired sobre cómo inyectar variables en él. Solo un artículo.

Bueno, ahora mis amigos entienden cómo la anotación @Lazy resuelve la dependencia circular de Spring~ Aunque está resuelto, todavía tenemos que evitarlo en nuestro desarrollo diario si podemos evitarlo~ </string></string>

RustDesk 1.2: Usando Flutter para reescribir la versión de escritorio, apoyando a Wayland acusado de deepin V23 adaptándose con éxito a los lenguajes de programación WSL 8 con la mayor demanda en 2023: PHP es fuerte, la demanda de C/C++ se ralentiza ¿ React está experimentando el momento de Angular.js? El proyecto CentOS afirma estar "abierto a todos" Se lanzan oficialmente MySQL 8.1 y MySQL 8.0.34 Se lanza la versión estable de Rust 1.71.0
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/lenve/blog/10089610
Recomendado
Clasificación