Spring@Lazy是如何解决构造函数循环依赖问题

Spring实例化源码解析之循环依赖CircularReference这章的最后我们提了一个构造函数形成的循环依赖问题,本章就是讲解利用@Lazy注解如何解决构造函数循环依赖和其原理。

准备工作

首先创建两个构造函数循环依赖的类,TestA和TestB,代码如下:

@Service
public class TestA {

	public TestA(@Lazy TestB testB) {
		this.testB = testB;
	}

	TestB testB;

	public void testA(){
		testB.testB();
	}
}

@Service
public class TestB {


	public TestB(TestA testA) {
		this.testA = testA;
	}

	TestA testA;

	public void testB(){
		System.out.println("有人掉我了 ^^");
	}
}

这样并不会有循环依赖的异常,所以说@Lazy确实可以解决构造函数循环依赖的问题。

@使用@Lazy注解

@Lazy注解since 3.0,

/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates whether a bean is to be lazily initialized.
 *
 * 

May be used on any class directly or indirectly annotated with {@link * org.springframework.stereotype.Component @Component} or on methods annotated with * {@link Bean @Bean}. * *

If this annotation is not present on a {@code @Component} or {@code @Bean} definition, * eager initialization will occur. If present and set to {@code true}, the {@code @Bean} or * {@code @Component} will not be initialized until referenced by another bean or explicitly * retrieved from the enclosing {@link org.springframework.beans.factory.BeanFactory * BeanFactory}. If present and set to {@code false}, the bean will be instantiated on * startup by bean factories that perform eager initialization of singletons. * *

If Lazy is present on a {@link Configuration @Configuration} class, this * indicates that all {@code @Bean} methods within that {@code @Configuration} * should be lazily initialized. If {@code @Lazy} is present and false on a {@code @Bean} * method within a {@code @Lazy}-annotated {@code @Configuration} class, this indicates * overriding the 'default lazy' behavior and that the bean should be eagerly initialized. * *

In addition to its role for component initialization, this annotation may also be placed * on injection points marked with {@link org.springframework.beans.factory.annotation.Autowired} * or {@link javax.inject.Inject}: In that context, it leads to the creation of a * lazy-resolution proxy for all affected dependencies, as an alternative to using * {@link org.springframework.beans.factory.ObjectFactory} or {@link javax.inject.Provider}. * Please note that such a lazy-resolution proxy will always be injected; if the target * dependency does not exist, you will only be able to find out through an exception on * invocation. As a consequence, such an injection point results in unintuitive behavior * for optional dependencies. For a programmatic equivalent, allowing for lazy references * with more sophistication, consider {@link org.springframework.beans.factory.ObjectProvider}. * * @author Chris Beams * @author Juergen Hoeller * @since 3.0 * @see Primary * @see Bean * @see Configuration * @see org.springframework.stereotype.Component */ @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Lazy { /** * Whether lazy initialization should occur. */ boolean value() default true; }

这个注释描述了@Lazy注解在Spring框架中的用法和行为。我将其翻译如下:

表示一个bean是否应该被懒初始化。可以用于任何直接或间接被 @Component 注解标记的类,或者用于被 @Bean 注解标记的方法上。

如果在 @Component 或 @Bean 定义上没有使用这个注解,bean 将会被急切初始化。如果使用了该注解并设置为 true,那么被 @Bean 或 @Component 标记的类将不会在被另一个bean引用或者在封装它的BeanFactory中显式获取之前进行初始化。如果使用了该注解并设置为 false,则bean将会在启动时由急切初始化单例的bean工厂中被实例化。

如果 @Lazy 注解放在一个 @Configuration 类上,表示该配置中的所有 @Bean 方法都应该被懒初始化。如果在一个 @Lazy 注解的 @Configuration 类中的 @Bean 方法上使用了 @Lazy 并设置为 false,这表示覆盖了“默认懒初始化”行为,该bean将会被急切初始化。

除了在组件初始化中的作用外,该注解还可以放在标记为 org.springframework.beans.factory.annotation.Autowired 或 javax.inject.Inject 的注入点上:在这个上下文中,它会为所有受影响的依赖关系创建一个懒解析的代理,作为使用 org.springframework.beans.factory.ObjectFactory 或 javax.inject.Provider 的替代方案。请注意,这种懒解析的代理会被始终注入;如果目标依赖不存在,你只能在调用时通过异常找出。因此,这样的注入点对于可选依赖关系来说会导致行为不直观。对于具备更多复杂性的懒引用的编程等价方式,请考虑使用 org.springframework.beans.factory.ObjectProvider。

@Lazy原理

我在TestA的构造函数中使用了@Lazy懒加载TestB,根据经验,TestA必须会去走Bean的创建流程,以往的普通循环依赖问题都是在populateBean期间(属性注入)做的。但是有个问题,如果我使用了@Lazy说明是在调用构造函数的时候懒加载TestB,那么我TestA中注入了个什么对象?在调用的时候怎么找到正确的TestB,因为TestB并没有懒加载。

分析流程

根据普通Bean循环依赖的经验,我判断会在AbstractAutowireCapableBeanFactory#doCreateBean方法中的属性注入完成后,TestA中就会注入TestB对象。所以我在populateBean打了个条件断点。

Spring@Lazy是如何解决构造函数循环依赖问题_第1张图片

发现不对劲,因为在populateBean之前,TestA中就有了TestB的代理对象。搞错了,再来。

Spring@Lazy是如何解决构造函数循环依赖问题_第2张图片

然后在AbstractAutowireCapableBeanFactory#doCreateBean方法中的第一行开始打断点,发现在createBeanInstance方法执行完之后就创建了TestB的代理对象。

Spring@Lazy是如何解决构造函数循环依赖问题_第3张图片

所以基本定位到在调用构造函数创建TestA的时候就把TestB的代理对象创建好了。

源码跟踪

源码查看足迹可以参考下面的类:

AbstractApplicationContext#refresh

AbstractApplicationContext#finishBeanFactoryInitialization

DefaultListableBeanFactory#preInstantiateSingletons

AbstractBeanFactory#getBean

AbstractBeanFactory#doGetBean

DefaultSingletonBeanRegistry#getSingleton

AbstractBeanFactory#lambda

AbstractAutowireCapableBeanFactory#createBean

AbstractAutowireCapableBeanFactory#doCreateBean

AbstractAutowireCapableBeanFactory#createBeanInstance

AbstractAutowireCapableBeanFactory#autowireConstructor

ConstructorResolver#autowireConstructor

ConstructorResolver#createArgumentArray

ConstructorResolver#resolveAutowiredArgument

DefaultListableBeanFactory#resolveDependency

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary

ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy:83

这里有个小技巧,如果你使用的时IDEA,快捷键double Shift copy上述一行的栈信息可以直接跳转到次方法中

buildLazyResolutionProxy

ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy方法是用于创建懒解析(lazy resolution)的代理对象。

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
		BeanFactory beanFactory = getBeanFactory();
		Assert.state(beanFactory instanceof DefaultListableBeanFactory,
				"BeanFactory needs to be a DefaultListableBeanFactory");
		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<>(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);
		}
		return pf.getProxy(dlbf.getBeanClassLoader());
	}

很明显这里其实就是通过定义TargeSource来创建一个代理对象,然后注入到TestA中,在TestA需要使用注入的TestB时,会执行这个getTarget方法来获取,如果容器里面有TestB就从容器中获取,如果没有就走创建流程。

你可能感兴趣的:(Spring专题,spring,java,1024程序员节,后端,spring,cloud,spring,boot)