This is the 14th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge
Spring cyclic dependencies and level 3 caching
Circular dependencies occur when two classes A and B depend on each other, and when multiple classes depend on each other.
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public String say(a){
return "hello"; }}@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
Copy the code
When the constructor injects a dependency, the loop has no solution
The dependencies of some of the beans in the application context form a cycle: cycleController (field com.paw.cycle.dependency.service.ServiceA Com. Paw. Cycle. The dependency. Controller. CycleController. ServiceA) ┌ ─ ─ ─ ─ ─ ┐ | serviceA defined in the file [/ Users /... / target/classes/com/paw/cycle/dependency/service/ServiceA class] write left | serviceB defined in the file [/ Users /... / target/classes/com/paw/cycle/dependency/service/ServiceB class] └ ─ ─ ─ ─ ─ ┘Copy the code
How does Spring solve circular dependencies
Open the log
logging:
level:
org.springframework: trace
Copy the code
Run view Logs
main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'cycleDependencyApplication' main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory' main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'cycleController' main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'cycleController' main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'cycleController' to allow for resolving potential circular references main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'cycleController' main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'serviceA' main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'serviceA' main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [com.paw.cycle.dependency.service.ServiceA]: AutowiredFieldElement for private com.paw.cycle.dependency.service.ServiceB com.paw.cycle.dependency.service.ServiceA.serviceB main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'serviceA' to allow for resolving potential circular references main] o.s.b.f.annotation.InjectionMetadata : Processing injected element of bean 'serviceA': AutowiredFieldElement for private com.paw.cycle.dependency.service.ServiceB com.paw.cycle.dependency.service.ServiceA.serviceB main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'serviceB' main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'serviceB' main] o.s.b.f.annotation.InjectionMetadata : Registered injected element on class [com.paw.cycle.dependency.service.ServiceB]: AutowiredFieldElement for private com.paw.cycle.dependency.service.ServiceA com.paw.cycle.dependency.service.ServiceB.serviceA main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'serviceB' to allow for resolving potential circular references main] o.s.b.f.annotation.InjectionMetadata : Processing injected element of bean 'serviceB': AutowiredFieldElement for private com.paw.cycle.dependency.service.ServiceA com.paw.cycle.dependency.service.ServiceB.serviceA main] o.s.b.f.s.DefaultListableBeanFactory : Returning eagerly cached instance of singleton bean 'serviceA' that is not fully initialized yet - a consequence of a circular reference main] f.a.AutowiredAnnotationBeanPostProcessor : Autowiring by type from bean name 'serviceB' to bean named 'serviceA' main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'serviceB' main] f.a.AutowiredAnnotationBeanPostProcessor : Autowiring by type from bean name 'serviceA' to bean named 'serviceB' main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'serviceA' main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of sinleton bean 'serviceB'Copy the code
The basic process can be sorted out:
Create ing serviceA==> Inject serviceB==> Inject a cache semi-finished product ServiceA ==> Creating serviceB==> Injecting serviceB==> Creating serviceA.
DefaultSingletonBeanRegistry l3 cache
// Level 1 instantiated bean
/**Cache of singleton objects: bean name to bean instance.*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// The second-level semi-finished product has no attribute injection
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// Generate the ObjectFactory for the bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);
Copy the code
Gets the bean instance object
First from the first level, then from the second level, and finally from the third level
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Level 1 cache
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// Level 2 cache
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null&& allowEarlyReference) { ObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
if(singletonFactory ! =null) {
// Level 3 cache
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code
Add a conditional break point, and start moving
beanName.equals(“serviceA”)
Class
SimpleAliasRegistry (org.springframework.core)
|-DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
|-|-FactoryBeanRegistrySupport (org.springframework.beans.factory.support)
|-|-|-AbstractBeanFactory (org.springframework.beans.factory.support)
|-|-|-|-AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
|-|-|-|-|-DefaultListableBeanFactory (org.springframework.beans.factory.support)
|-|-|-|-|-XmlBeanFactory (org.springframework.beans.factory.xml)
L3 cache objectFactory by an anonymous inner class (anonymous class calls the AbstractAutowireCapableBeanFactory# getEarlyBeanReference method) getEarlyBeanReference Get early semi-finished objects.
AbstractAutowireCapableBeanFactory
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {... addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); . }Copy the code
singletonFactory AbstractAutowireCapableBeanFactory
// Add level 3 cache
protected void addSingletonFactory(String beanName, ObjectFactory
singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName); }}}Copy the code
// Create the bean with the ObjectFactory
AbstractBeanFactory#doGetBean
// Create the bean instance
sharedInstance = getSingleton(beanName, () -> {
try {
returncreateBean(beanName, mbd, args); }... } ==> DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory
singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!) ");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// Processing before creating the bean
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// Get the bean object instance from the level-three cached objectFactory
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throwex; }}catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); }}throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// Processing after creation
afterSingletonCreation(beanName);
}
if (newSingleton) {
// Added to level 1 cache successfullyaddSingleton(beanName, singletonObject); }}return singletonObject;
}
Copy the code
AbstractAutowireCapableBeanFactory create bean
Add it to the Level-iii cache ==> Create a caching bean. Inject bean properties and instantiate the bean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
finalObject bean = instanceWrapper.getWrappedInstance(); Class<? > beanType = instanceWrapper.getWrappedClass();if(beanType ! = NullBean.class) { mbd.resolvedTargetType = beanType; }// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if(! mbd.postProcessed) {try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true; }}// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// Add the level 3 cache beanFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// Fill the bean property to inject @autoWired field
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); }}if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if(earlySingletonReference ! =null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if(! actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); }}}}// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
Copy the code
Property injection serviceB
Fill attribute
//AbstractAutowireCapableBeanFactory
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)
Copy the code
AbstractAutowireCapableBeanFactory#postProcessProperties
InjectionMetadata class for attribute injection
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
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;
}
Copy the code
The condition of the AOP singletonObject = singletonFactory. GetObject (); Multiple calls generate new proxy objects (the same Bean object serviceA is represented by different proxy objects). Spring puts the singletonObject proxy object into the second-level cache, which is also injected into the serviceB proxy object singletonObject.
It’s true that without AOP, two-level caching would solve the problem of circular dependencies. With AOP, two-level caching would not solve the problem. It’s impossible to execute singleFactory.getobject () every time I generate a new proxy object, so I have to use another cache to hold the generated proxy object.
Conclusion:
Three-level cache to store in the spring the ObjectFactory (anonymous class calls the AbstractAutowireCapableBeanFactory # getEarlyBeanReference method), semi-finished products obtained through getObect object in the second level cache, After completing the property injection and bean initialization, it is put into the level 1 cache. In the case of an AOP proxy, the third-level cache generates a new proxy object at a time, so put the getObject generated proxy object singletonObject into the second-level cache and add the proxy object to the first-level cache after property injection.