Heim >Java >JavaInterview Fragen >Java-Interviewfrage: Wissen Sie, was zirkuläre Abhängigkeit ist? Wie löst Spring zirkuläre Abhängigkeiten?

Java-Interviewfrage: Wissen Sie, was zirkuläre Abhängigkeit ist? Wie löst Spring zirkuläre Abhängigkeiten?

王林
王林nach vorne
2021-01-08 10:33:063485Durchsuche

Java-Interviewfrage: Wissen Sie, was zirkuläre Abhängigkeit ist? Wie löst Spring zirkuläre Abhängigkeiten?

Zunächst möchte ich Ihnen vorstellen, was zirkuläre Abhängigkeit ist.

(Teilen von Lernvideos: Java-Video-Tutorial)

Die sogenannte zirkuläre Abhängigkeit besteht darin, dass A von B abhängt und B gleichzeitig von A abhängt. Die Abhängigkeitsbeziehung zwischen den beiden bildet einen Kreis Dies liegt in der Regel an einer falschen Kodierung. Spring kann nur das Problem der zirkulären Abhängigkeit von Eigenschaften lösen, nicht jedoch das Problem der zirkulären Abhängigkeit von Konstruktoren, da es für dieses Problem keine Lösung gibt.

Als nächstes schreiben wir zunächst eine Demo, um zu demonstrieren, wie Spring mit dem Problem der zirkulären Abhängigkeiten von Eigenschaften umgeht.

Sprechen ist günstig.

Schritt 1: Definieren Sie eine Klasse ComponentA, die eine private Eigenschaft ComponentB hat.

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

}

Schritt 2: Definieren Sie eine Klasse ComponentB, die von ComponentA abhängt. Und definieren Sie eine Say-Methode, um das Drucken von Daten zu erleichtern.

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

}

Schritt 3: Konzentrieren Sie sich auf das Schreiben einer Klasse – SimpleContainer, die Springs zugrunde liegende Behandlung zirkulärer Abhängigkeiten imitiert. Wenn Sie diesen Code verstehen, wird es sehr einfach sein, einen Blick auf die Spring-Logik für den Umgang mit zirkulären Abhängigkeiten zu werfen.

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

Wenn Sie den obigen Code gelesen und verstanden haben, werden wir eine echte Quellcode-Analyse des zirkulären Abhängigkeitsproblems von Spring durchführen. Ich glaube, es wird einfach sein, ihn erneut zu lesen.

Analyse des zugrunde liegenden Quellcodes

Die Analyse beginnt mit der doGetBean-Methode von AbstractBeanFactory. Sie können sehen, dass transformedBeanName in dieser Methode zuerst aufgerufen wird (eigentlich um das BeanName-Problem zu lösen), was den gleichen Effekt hat wie die getBeanName-Methode, die wir selbst geschrieben haben, aber Spring hält es für viel komplizierter, da es FactoryBean- und Alias-Probleme gibt .

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

Die getSingleton-Methode verfügt über eine überladene Methode. Beachten Sie, dass der hier übergebene boolesche Parameterwert wahr ist, da dieser Wert bestimmt, ob die Offenlegung früher Beans zulässig ist.

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

(Weitere Interviewfragen finden Sie unter: Java-Interviewfragen und -Antworten)

ok, nachdem wir gesehen haben, wie Spring Bean-Instanzen aus dem Cache erhält, werfen wir einen Blick darauf, wie die creatBean-Methode Beans erstellt

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

Lasst uns Analysieren Sie zuerst die Methode addSingletonFactory, da bei dieser Methode die Bean im Cache der dritten Ebene gespeichert wird.

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

Die Abhängigkeitsinjektionsverarbeitung von Beans wird durch die populateBean-Methode abgeschlossen, aber der gesamte Ausführungslink ist zu lang, daher werde ich hier nicht näher darauf eingehen, wie der IoC-Container die getBean-Methode Schritt für Schritt aufruft Verarbeitungsabhängigkeiten Dies entspricht der Logik, die wir selbst geschrieben haben, um die Feldinjektion zu handhaben.

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;
			}
		}
		// 删除与本次分析无关代码...
	}
	
}

In Spring wird die @Autowired-Annotation von der AutowiredAnnotationBeanPostProcessor-Klasse verarbeitet, und die @Resource-Annotation wird von der CommonAnnotationBeanPostProcessor-Klasse verarbeitet, und beide Klassen implementieren die Abhängigkeitsinjektion in der überschriebenen postProcessProperties-Methode. Hier analysieren wir die Verarbeitung der @Autowired-Annotation.

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

Rufen Sie in der Injektionsmethode von InjectionMetadata alle Abhängigkeitselemente (InjectedElement) der aktuellen Bean ab, die verarbeitet werden müssen. Durchlaufen Sie die Sammlung und rufen Sie die Injektionsmethode jedes Abhängigkeitsinjektionselements auf.

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

In der AutowiredAnnotationBeanPostProcessor-Klasse sind zwei interne Klassen definiert: AutowiredFieldElement und AutowiredMethodElement werden von InjectedElement geerbt, die der Feldinjektion bzw. der Methodeninjektion entsprechen.

Java-Interviewfrage: Wissen Sie, was zirkuläre Abhängigkeit ist? Wie löst Spring zirkuläre Abhängigkeiten?

Nehmen Sie als Beispiel die häufig verwendete Feldinjektion. Stellen Sie zunächst fest, ob das aktuelle Feld verarbeitet wurde. Andernfalls wird es direkt zwischengespeichert BeanFactory wird aufgerufen, um Abhängigkeiten zu verarbeiten.

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

Die in DefaultListableBeanFactory implementierte Methode „resolveDependency“ ruft letztendlich die Methode „doResolveDependency“ auf, um die Abhängigkeitsauflösungsfunktion abzuschließen. Wenn es im Spring-Quellcode eine Do-Methode gibt, dann ist diese Methode die eigentliche Art und Weise, die Arbeit zu erledigen.

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

In der Methode „resolveCandidate“ des Abhängigkeitsdeskriptors wird die Erfassung der abhängigen Bean-Instanz durch Aufrufen der Methode „getBean“ der BeanFactory abgeschlossen.

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

	return beanFactory.getBean(beanName);
}

Bei der Implementierung der getBean-Methode wird sie noch durch Aufrufen der doGetBean-Methode abgeschlossen. Dies ist im Grunde dasselbe wie die Abhängigkeitsverarbeitung, die wir selbst geschrieben haben, mit der Ausnahme, dass das, was wir selbst geschrieben haben, relativ einfach ist und Spring komplexe Szenarien berücksichtigen und verarbeiten muss, sodass der Code komplizierter ist, aber die allgemeine Idee dieselbe ist.

// AbstractBeanFactory#getBean
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

Der entscheidende Punkt ist die Demo, die wir zuvor geschrieben haben, um zirkuläre Abhängigkeiten zu verarbeiten. Wenn Sie diesen Code verstehen und sich die zirkuläre Abhängigkeitsverarbeitung von Spring ansehen, werden Sie feststellen, dass sie sehr einfach ist.

Zusammenfassung:

Zirkuläre Abhängigkeit bedeutet, dass zwischen zwei Beans eine gegenseitige Referenzbeziehung besteht. Beispielsweise hängt A von B und B von A ab. Spring kann jedoch nur die zirkuläre Abhängigkeit von Eigenschaften lösen, nicht jedoch die zirkuläre Abhängigkeit von Konstruktoren Szenario kann nicht gelöst werden.

Der Schlüssel zu Springs Lösung für zirkuläre Abhängigkeiten besteht darin, die Beans bei der Verarbeitung der Attributabhängigkeiten der Beans zunächst im Cache der dritten Ebene zu speichern, die relevanten Beans aus dem Cache der dritten Ebene abzurufen und dann zu verschieben Speichern Sie sie aus dem Cache der dritten Ebene und speichern Sie sie im Cache der ersten Ebene, nachdem die endgültige Initialisierung abgeschlossen ist.

Verwandte Empfehlungen: Java-Einführungs-Tutorial

Das obige ist der detaillierte Inhalt vonJava-Interviewfrage: Wissen Sie, was zirkuläre Abhängigkeit ist? Wie löst Spring zirkuläre Abhängigkeiten?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen
Vorheriger Artikel:Java-Interview-SperreNächster Artikel:Java-Interview-Sperre