자바 인터뷰 질문: 순환 의존성이 무엇인지 아시나요? Spring은 순환 종속성을 어떻게 해결합니까?

王林
풀어 주다: 2021-01-08 10:33:06
앞으로
3411명이 탐색했습니다.

자바 인터뷰 질문: 순환 의존성이 무엇인지 아시나요? Spring은 순환 종속성을 어떻게 해결합니까?

먼저 순환 의존성이 무엇인지 소개하겠습니다.

(학습 영상 공유: java 영상 튜토리얼)

소위 순환 종속성은 A가 B에 의존하고 동시에 B도 A에 의존하는 것입니다. 둘 사이의 종속 관계는 원을 형성합니다. 일반적으로 잘못된 인코딩으로 인해 발생합니다. Spring은 속성의 순환 종속성 문제만 해결할 수 있지만 생성자의 순환 종속성 문제는 해결할 수 없습니다. 왜냐하면 이 문제에 대한 해결책이 없기 때문입니다.

다음으로 먼저 데모를 작성하여 Spring이 속성 순환 종속성 문제를 처리하는 방법을 보여줍니다.

Talk는 저렴합니다. 코드를 보여주세요

1단계: private 속성인 componentB를 갖는 ComponentA 클래스를 정의합니다.

package com.tech.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author 君战
 * **/
@Component
public class ComponentA {

	@Autowired
	private ComponentB componentB;

	public void say(){
		componentB.say();
	}

}
로그인 후 복사

2단계: ComponentA에 의존하는 ComponentB 클래스를 정의합니다. 그리고 데이터 인쇄를 용이하게 하기 위해 say 메소드를 정의하세요.

package com.tech.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author 君战
 * **/
@Component
public class ComponentB {

	@Autowired
	private ComponentA componentA;

	public void say(){
		System.out.println("componentA field " + componentA);
		System.out.println(this.getClass().getName() + " -----> say()");
	}

}
로그인 후 복사

3단계: Spring의 순환 종속성에 대한 기본 처리를 모방하는 SimpleContainer 클래스 작성에 집중합니다. 이 코드를 이해하면 순환 종속성을 처리하기 위한 Spring의 논리를 보는 것이 매우 간단해질 것입니다.

package com.tech.ioc;

import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 演示Spring中循环依赖是如何处理的,只是个简版,真实的Spring依赖处理远比这个复杂。
 * 但大体思路都相同。另外这个Demo很多情况都未考虑,例如线程安全问题,仅供参考。
 * @author 君战
 *
 * **/
public class SimpleContainer {

	/***
	 * 用于存放完全初始化好的Bean,Bean处于就绪状态
	 * 这个Map定义和Spring中一级缓存命名一致
	 * */
	private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

	/***
	 * 用于存放刚创建出来的Bean,其属性还没有处理,因此存放在该缓存中的Bean还不可用。
	 * 这个Map定义和Spring中三级缓存命名一致
	 * */
	private final Map<String, Object> singletonFactories = new HashMap<>(16);


	public static void main(String[] args) {
		SimpleContainer container = new SimpleContainer();
		ComponentA componentA = container.getBean(ComponentA.class);
		componentA.say();
	}

	public <T> T getBean(Class<T> beanClass) {
		String beanName = this.getBeanName(beanClass);
		// 首先根据beanName从缓存中获取Bean实例
		Object bean = this.getSingleton(beanName);
		if (bean == null) {
			// 如果未获取到Bean实例,则创建Bean实例
			return createBean(beanClass, beanName);
		}
		return (T) bean;
	}
	/***
	 * 从一级缓存和二级缓存中根据beanName来获取Bean实例,可能为空
	 * */
	private Object getSingleton(String beanName) {
		// 首先尝试从一级缓存中获取
		Object instance = singletonObjects.get(beanName);
		if (instance == null) { // Spring 之所以能解决循环依赖问题,也是靠着这个三级缓存--singletonFactories
			instance = singletonFactories.get(beanName);
		}
		return instance;
	}

	/***
	 * 创建指定Class的实例,返回完全状态的Bean(属性可用)
	 *
	 * */
	private <T> T createBean(Class<T> beanClass, String beanName) {
		try {
			Constructor<T> constructor = beanClass.getDeclaredConstructor();
			T instance = constructor.newInstance();
			// 先将刚创建好的实例存放到三级缓存中,如果没有这一步,Spring 也无法解决三级缓存
			singletonFactories.put(beanName, instance);
			Field[] fields = beanClass.getDeclaredFields();
			for (Field field : fields) {
				Class<?> fieldType = field.getType();
				field.setAccessible(true); 
				// 精髓是这里又调用了getBean方法,例如正在处理ComponentA.componentB属性,
				// 执行到这里时就会去实例化ComponentB。因为在getBean方法首先去查缓存,
				// 而一级缓存和三级缓存中没有ComponentB实例数据,所以又会调用到当前方法,
				// 而在处理ComponentB.componentA属性时,又去调用getBean方法去缓存中查找,
				// 因为在前面我们将ComponentA实例放入到了三级缓存,因此可以找到。
				// 所以ComponentB的实例化结束,方法出栈,返回到实例化ComponentA的方法栈中,
				// 这时ComponentB已经初始化完成,因此ComponentA.componentB属性赋值成功!
				field.set(instance, this.getBean(fieldType));
			}
			// 最后再将初始化好的Bean设置到一级缓存中。
			singletonObjects.put(beanName, instance);
			return instance;
		} catch (Exception e) {
			e.printStackTrace();
		}
		throw new IllegalArgumentException();
	}

	/**
	 * 将类名小写作为beanName,Spring底层实现和这个差不多,也是使用javaBeans的
	 * {@linkplain Introspector#decapitalize(String)}
	 **/
	private String getBeanName(Class<?> clazz) {
		String clazzName = clazz.getName();
		int index = clazzName.lastIndexOf(".");
		String className = clazzName.substring(index);
		return Introspector.decapitalize(className);
	}
}
로그인 후 복사

모든 학생들이 위의 코드를 읽고 이해했다면, Spring의 순환 의존성 문제에 대한 실제 소스 코드 분석을 다시 읽기 쉬울 것이라고 믿습니다.

기본 소스 코드 분석

분석은 AbstractBeanFactory의 doGetBean 메소드로 시작됩니다. 이 메서드에서TransformBeanName이 처음 호출되는 것을 볼 수 있습니다(실제로 BeanName 문제를 처리하기 위해). 이는 우리가 직접 작성한 getBeanName 메서드와 동일한 효과를 가지지만 Spring은 FactoryBean 및 별칭 문제가 있기 때문에 이보다 훨씬 더 복잡하다고 간주합니다. .

// AbstractBeanFactory#doGetBean
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object bean;

		// !!!重点是这里,首先从缓存中beanName来获取对应的Bean。
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			// 执行到这里说明缓存中存在指定beanName的Bean实例,getObjectForBeanInstance是用来处理获取到的Bean是FactoryBean问题
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		else {
			try {
				// 删除与本次分析无关代码....
				// 如果是单例Bean,则通过调用createBean方法进行创建
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						} catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
				
				}	
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
		return (T) bean;
	}
로그인 후 복사

getSingleton 메소드에는 오버로드된 메소드가 있습니다. 여기서 오버로드된 getSingleton 메소드가 호출됩니다. 여기에 전달된 부울 매개변수 값은 초기 Bean의 노출이 허용되는지 여부를 결정하므로 참고하세요.

// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName) {
	return getSingleton(beanName, true);
}
로그인 후 복사
// DefaultSingletonBeanRegistry#getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 首先从一级缓存中获取
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 如果一级缓存中未获取到,再从二级缓存中获取
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 如果未从二级缓存中获取到并且allowEarlyReference值为true(前面传的为true)
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
				   //Double Check 
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							// 最后尝试去三级缓存中获取
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								// 保存到二级缓存
								this.earlySingletonObjects.put(beanName, singletonObject);
								// 从三级缓存中移除
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}
로그인 후 복사

(자세한 인터뷰 질문은 다음을 방문하세요: java 인터뷰 질문 및 답변)

좋아요, Spring이 캐시에서 Bean 인스턴스를 얻는 방법을 살펴본 후 creatBean 메서드가 Bean을 생성하는 방법을 살펴보겠습니다

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// 删除与本次分析无关的代码...
	try {// createBean方法底层是通过调用doCreateBean来完成Bean创建的。
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean &#39;" + beanName + "&#39;");
		}
		return beanInstance;
	} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		throw ex;
	} catch (Throwable ex) {
		throw new BeanCreationException(
				mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
	}
}
로그인 후 복사
// AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// 创建Bean实例
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		// 如果允许当前Bean早期曝光。只要Bean是单例的并且allowCircularReferences 属性为true(默认为true)
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			// 这里调用了addSingletonFactory方法将刚创建好的Bean保存到了三级缓存中。
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// 删除与本次分析无关的代码.....
		Object exposedObject = bean;
		try {// Bean属性填充
			populateBean(beanName, mbd, instanceWrapper);
			// 初始化Bean,熟知的Aware接口、InitializingBean接口.....都是在这里调用
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		} catch (Throwable ex) {
			
		}
		// 删除与本次分析无关的代码.....
		return exposedObject;
	}
로그인 후 복사

addSingletonFactory 메소드를 먼저 분석하십시오. 왜냐하면 이 메소드에서는 Bean이 세 번째 레벨 캐시에 저장되기 때문입니다.

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		// 如果一级缓存中不存在指定beanName的key
		if (!this.singletonObjects.containsKey(beanName)) {
			// 将刚创建好的Bean实例保存到三级缓存中
			this.singletonFactories.put(beanName, singletonFactory);
			// 从二级缓存中移除。
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
로그인 후 복사

PopulateBean 메소드로 Beans의 종속성 주입 처리가 완료되지만 전체 실행 링크가 너무 길어서 여기서는 자세히 설명하지 않겠습니다. 이는 종속성을 처리하기 위해 우리가 직접 작성한 논리와 일치합니다.

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	// 删除与本次分析无关代码...
	PropertyDescriptor[] filteredPds = null;
	if (hasInstAwareBpps) {
		if (pvs == null) {
			pvs = mbd.getPropertyValues();
		}
		// 遍历所有已注册的BeanPostProcessor接口实现类,如果实现类是InstantiationAwareBeanPostProcessor接口类型的,调用其postProcessProperties方法。
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				// 删除与本次分析无关代码...
				pvs = pvsToUse;
			}
		}
		// 删除与本次分析无关代码...
	}
	
}
로그인 후 복사

Spring에서 @Autowired 주석은 AutowiredAnnotationBeanPostProcessor 클래스에 의해 처리되고 @Resource 주석은 CommonAnnotationBeanPostProcessor 클래스에 의해 처리되며 두 클래스 모두 InstantiationAwareBeanPostProcessor 인터페이스를 구현하고 둘 다 재정의된 postProcessProperties 메서드에서 종속성 주입을 완료합니다. 여기서는 @Autowired 주석 처리를 분석합니다.

// AutowiredAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		// 根据beanName以及bean的class去查找Bean的依赖元数据-InjectionMetadata 
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {// 调用inject方法
			metadata.inject(bean, beanName, pvs);
		} catch (BeanCreationException ex) {
			throw ex;
		} catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}
로그인 후 복사

InjectionMetadata의 inject 메소드에서 처리가 필요한 현재 Bean의 모든 종속성 요소(InjectedElement)를 가져오고 컬렉션을 순회하며 각 종속성 주입 요소의 inject 메소드를 호출합니다.

// InjectionMetadata#inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	// 获取当前Bean所有的依赖注入元素(可能是方法,也可能是字段)
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		// 如果当前Bean的依赖注入项不为空,遍历该依赖注入元素
		for (InjectedElement element : elementsToIterate) {
			// 调用每一个依赖注入元素的inject方法。
			element.inject(target, beanName, pvs);
		}
	}
}
로그인 후 복사

AutowiredAnnotationBeanPostProcessor 클래스에는 두 개의 내부 클래스가 정의되어 있습니다. AutowiredFieldElement 및 AutowiredMethodElement는 각각 필드 주입 및 메서드 주입에 해당하는 InjectedElement에서 상속됩니다.

자바 인터뷰 질문: 순환 의존성이 무엇인지 아시나요? Spring은 순환 종속성을 어떻게 해결합니까?

AutowiredFieldElement의 주입 메소드에서 일반적으로 사용되는 필드 주입을 예로 들면, 먼저 현재 필드가 처리되었는지 여부를 확인하고, 그렇지 않은 경우에는 receiveDependency 메소드를 사용합니다. BeanFactory는 종속성을 처리하기 위해 호출됩니다.

// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Field field = (Field) this.member;
		Object value;
		if (this.cached) {// 如果当前字段已经被处理过,直接从缓存中获取
			value = resolvedCachedArgument(beanName, this.cachedFieldValue);
		} else {
			// 构建依赖描述符
			DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
			desc.setContainingClass(bean.getClass());
			Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
			Assert.state(beanFactory != null, "No BeanFactory available");
			TypeConverter typeConverter = beanFactory.getTypeConverter();
			try {// 调用BeanFactory的resolveDependency来解析依赖
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			} catch (BeansException ex) {
				throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
			}
			// 删除与本次分析无关代码....
		}
		if (value != null) {
			// 通过反射来对属性进行赋值
			ReflectionUtils.makeAccessible(field);
			field.set(bean, value);
		}
	}
}
로그인 후 복사

DefaultListableBeanFactory에 구현된solveDependency 메소드는 최종적으로 doResolveDependency 메소드를 호출하여 종속성 해결 기능을 완료합니다. Spring 소스 코드에 do 메소드가 있는 경우 이 메소드가 작업을 수행하는 실제 방법입니다.

// DefaultListableBeanFactory#resolveDependency
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		// .....
		// 如果在字段(方法)上添加了@Lazy注解,那么在这里将不会真正的去解析依赖
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			// 如果未添加@Lazy注解,那么则调用doResolveDependency方法来解析依赖
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
}
로그인 후 복사
// DefaultListableBeanFactory#doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	//.....
	try {
		// 根据名称以及类型查找合适的依赖
		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) {// 如果未找到相关依赖
			if (isRequired(descriptor)) { // 如果该依赖是必须的(例如@Autowired的required属性),直接抛出异常
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			return null;
		}

		String autowiredBeanName;
		Object instanceCandidate;
		// 如果查找到的依赖多于一个,例如某个接口存在多个实现类,并且多个实现类都注册到IoC容器中。
		if (matchingBeans.size() > 1) {// 决定使用哪一个实现类,@Primary等方式都是在这里完成
			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
			if (autowiredBeanName == null) {
				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
				} else { 
					return null;
				}
			}
			instanceCandidate = matchingBeans.get(autowiredBeanName);
		} else {
			// We have exactly one match.
			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
			autowiredBeanName = entry.getKey();
			instanceCandidate = entry.getValue();
		}

		if (autowiredBeanNames != null) {
			autowiredBeanNames.add(autowiredBeanName);
		}
		// 如果查找到的依赖是某个类的Class(通常如此),而不是实例,
		//调用描述符的方法来根据类型resolveCandidate方法来获取该类型的实例。
		if (instanceCandidate instanceof Class) {
			instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
		}
		//...
}
로그인 후 복사

종속성 설명자의 해결Candidate 메소드에서 BeanFactory의 getBean 메소드를 호출하여 종속 Bean 인스턴스 획득이 완료됩니다.

// DependencyDescriptor#resolveCandidate
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {

	return beanFactory.getBean(beanName);
}
로그인 후 복사

getBean 메소드 구현에서는 여전히 doGetBean 메소드를 호출하여 완료됩니다. 이는 우리가 직접 작성한 종속성 처리와 기본적으로 동일합니다. 단, 우리가 직접 작성한 내용은 상대적으로 간단하고 Spring은 복잡한 시나리오를 고려하고 처리해야 하므로 코드가 더 복잡하지만 일반적인 아이디어는 동일합니다.

// AbstractBeanFactory#getBean
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}
로그인 후 복사

핵심은 이전에 순환 종속성을 처리하기 위해 작성한 데모입니다. 해당 코드를 이해하고 Spring의 순환 종속성 처리를 살펴보면 매우 간단하다는 것을 알 수 있습니다.

요약:

순환 종속성은 두 Bean 사이에 상호 참조 관계가 있음을 의미합니다. 예를 들어 A는 B에 의존하고 B는 A에 의존합니다. 그러나 Spring은 속성의 순환 종속성만 해결할 수 있지만 생성자의 순환 종속성은 해결할 수 없습니다. 시나리오를 해결할 수 없습니다.

Spring의 순환 종속성 솔루션의 핵심은 Bean의 속성 종속성을 처리할 때 먼저 Bean을 세 번째 수준 캐시에 저장하는 것입니다. 순환 종속성이 있는 경우 세 번째 수준 캐시에서 관련 Bean을 가져온 다음 이동합니다. 단, 2단계 캐시에 저장하고, 최종 초기화가 완료된 후 1단계 캐시에 저장합니다.

관련 권장 사항: Java 입문 튜토리얼

위 내용은 자바 인터뷰 질문: 순환 의존성이 무엇인지 아시나요? Spring은 순환 종속성을 어떻게 해결합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:csdn.net
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!