Byte interview, I answer the circular dependence in spring like this, and get 20K offer!

Java Architect 2021-06-23 17:01:55
byte interview answer circular dependence


Preface

Spring The circular dependency in has always been Spring It's a very important topic in English , On the one hand, it is because a lot of processing has been done in the source code to solve the circular dependency , On the other hand, during the interview , If you ask Spring The higher-order problems in , Then circular dependence must not escape . If you answer well , So that's your kill , Anyway , That's the killer of an interviewer , That's the reason for the title , Of course , The purpose of this article is to let you have one more must kill skill in all the subsequent interviews , It's used to kill the interviewer !

At the end of the article, please remember to receive the welfare !

The core idea of this paper is , When the interviewer asks :

“ Please talk about it Spring Circular dependence in .” When ,

How on earth should we answer ?

It is mainly divided into the following points

  1. What is circular dependence ?
  2. When circular dependencies can be handled ?
  3. Spring How to solve the circular dependency ?

At the same time, this paper hopes to correct some common mistakes about circular dependency in the industry

  1. Only in setter In the case of mode injection , Circular dependency is the solution ( wrong
  2. The purpose of level 3 caching is to improve efficiency ( wrong

OK, The bedding is done , Let's start with the text

What is circular dependence ?

It's literally A rely on B At the same time B I also rely on A, It looks like this

That's what it looks like at the code level

@Component
public class A {
// A In the B
@Autowired
private B b;
}
@Component
public class B {
// B It's also infused with A
@Autowired
private A a;
}

Of course , This is the most common kind of circular dependency , More special is

// Depend on yourself
@Component
public class A {
// A In the A
@Autowired
private A a;
}

Although the forms are different , But it's actually the same problem -----> Cyclic dependence

When circular dependencies can be handled ?

Before you answer this question, make it clear ,Spring There are preconditions for solving circular dependency

  1. There is a circular dependency Bean It has to be a single case
  2. The way of dependency injection can't be the way of constructor injection ( A lot of blogs say , Can only solve setter Circular dependency of method , This is wrong )

The first of these should be well understood , Second point : It can't be all constructors. What does injection mean ? We still talk in code

@Component
public class A {
// @Autowired
// private B b;
public A(B b) {
}
}
@Component
public class B {
// @Autowired
// private A a;
public B(A a){
}
}

In the example above ,A In the injection B The way to do this is through constructors ,B In the injection A It's also through constructors , At this time, circular dependency cannot be solved , If you have two such interdependencies in your project Bean, The following error will be reported at startup :

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

In order to test the relationship between the solution of cyclic dependency and the injection mode , We do the following four tests

Dependence Dependency injection Is circular dependency resolved
AB Interdependence ( Cyclic dependence ) All adopt setter Methods to inject yes
AB Interdependence ( Cyclic dependence ) All of them were injected by constructors no
AB Interdependence ( Cyclic dependence ) A In the injection B By setter Method ,B In the injection A The way to do it is the constructor yes
AB Interdependence ( Cyclic dependence ) B In the injection A By setter Method ,A In the injection B The way to do it is the constructor no

The specific test code is simple , I won't let it go . From the test results above, we can see that , It's not just in setter In the case of method injection, the circular dependency can be solved , Even in the case of constructor Injection , Circular dependencies can still be disposed of normally .

So why on earth ?Spring How to deal with circular dependency ? Don't worry , Let's move on

Spring How to solve the circular dependency ?

There are two ways to solve circular dependency

  1. Simple circular dependency ( No, AOP)
  2. Combined with the AOP The cycle of dependence

Simple circular dependency ( No, AOP)

Let's start with the simplest example , It's the one mentioned above demo

@Component
public class A {
// A In the B
@Autowired
private B b;
}
@Component
public class B {
// B It's also infused with A
@Autowired
private A a;
}

As we know from the above, circular dependency in this case can be solved , So what is the specific process ? We analyze step by step

First , We need to know Spring Creating Bean By default, it is created in natural order , So step one Spring Will create A.

meanwhile , We should know ,Spring Creating Bean There are three steps in this process

  1. Instantiation , Corresponding method :AbstractAutowireCapableBeanFactory Medium createBeanInstance Method
  2. Attribute injection , Corresponding method :AbstractAutowireCapableBeanFactory Of populateBean Method
  3. initialization , Corresponding method :AbstractAutowireCapableBeanFactory Of initializeBean

These methods have been explained in detail in previous articles on source code analysis , If you haven't read my article before , So you just need to know

  1. Instantiation , The simple understanding is new An object
  2. Attribute injection , For instance new The object that comes out fills the property
  3. initialization , perform aware Methods in interfaces , Initialization method , complete AOP agent

Based on the above knowledge , We begin to understand the whole process of circular dependency processing , The whole process should be based on A From the creation of , As I said before , The first step is to create A Well !

establish A The process of calling is actually to call getBean Method , This method has two meanings

  1. Create a new Bean
  2. Get the created object from the cache

What we're analyzing now is the first level of meaning , Because at this time, there is no A Well !

call getSingleton(beanName)

First call getSingleton(a) Method , This method will call getSingleton(beanName, true), In the picture above, I omitted this step

public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}

getSingleton(beanName, true) This method is actually trying to get... In the cache Bean, The whole cache is divided into three levels

  1. singletonObjects, First level cache , All the created singletons are stored Bean
  2. earlySingletonObjects, Complete instantiation , But the object has not been injected and initialized yet
  3. singletonFactories, A single factory with early exposure , The second level cache stores the objects obtained from this factory

because A It's the first time it's been created , So no matter which cache is not available , So will enter getSingleton Another overload method for getSingleton(beanName, singletonFactory).

call getSingleton(beanName, singletonFactory)

This method is used to create Bean Of , The source code is as follows :

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) {
// ....
// Omit exception handling and logging
// ....
// Make a mark before creating a singleton object
// take beanName Put in singletonsCurrentlyInCreation In this collection
// It marks this single example Bean Creating
// If the same singleton Bean Created many times , An exception will be thrown here
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// From upstream lambda It will be executed here , call createBean Method to create a Bean After the return
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// ...
// Omit catch exception handling
// ...
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// After creation, the corresponding beanName from singletonsCurrentlyInCreation remove
afterSingletonCreation(beanName);
}
if (newSingleton) {
// Add to the first level cache singletonObjects in
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}

In the above code, we mainly focus on one point , adopt createBean Method Bean It's finally put in the first level cache , That is, in the singleton pool .

So here we can come to a conclusion : The first level cache stores fully created singletons Bean

call addSingletonFactory Method

As shown in the figure below :

At the completion of Bean After instantiation of , Before property Injection Spring take Bean Packaged as a factory and added to the third level cache , The corresponding source code is as follows :

// The parameter passed in here is also a lambda expression ,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// Add to the third level cache
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

Here's just a factory added , Through this factory (ObjectFactory) Of getObject Method to get an object , And this object is actually through getEarlyBeanReference This method creates . that , When will the factory be called getObject Methods? ? It's time to create B The process .

When A After instantiating and adding to the third level cache , It's time to start working for A The property Injection , It was found during injection that A Rely on B, So at this point Spring I'll go again getBean(b), Then the reflection calls setter Method to complete attribute injection .

because B Need injection A, So in creating B When , It will call again getBean(a), It's time to go back to the previous process , But here's the difference , Previous getBean To create Bean, And then call getBean Not to create , Instead, get it from the cache , Because before A After instantiation, it has been put into the third level cache singletonFactories in , So at this time getBean(a) That's how it works

From here we can see that , Injection into B Medium A It's through getEarlyBeanReference Method to expose an object in advance , It's not a complete Bean, that getEarlyBeanReference What did you do , Let's take a look at the source code

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

It's actually a call to the postprocessor getEarlyBeanReference, There is only one post processor that really implements this method , It is through @EnableAspectJAutoProxy Annotation imported AnnotationAwareAspectJAutoProxyCreator. That is to say, if we don't consider AOP Under the circumstances , The above code is equivalent to :

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
return exposedObject;
}

In other words, the factory did nothing , The object created in the instantiation phase is directly returned to ! So I'm not thinking about AOP In this case, L3 cache is useful ? Be reasonable , It's really useless , I put this object directly into the secondary cache. Isn't there no problem at all ? If you say it improves efficiency , Then you tell me where to improve the efficiency ?

So what's the role of L3 cache ? Don't worry , Let's go through the whole process first , In combination with AOP When analyzing circular dependencies, you can see the role of level 3 caching !

I don't know if my friends will have any questions here ,B An uninitialized A No problem with type objects ?

answer : Can't

At this point we need to create the whole A This Bean The process is finished , Here's the picture :

As can be seen from the picture above , Although creating B I will give you in advance B Injected an uninitialized A object , But creating A We have been using injection into B Medium A References to objects , After that, we will use this quotation to A To initialize , So there's no problem .

Combined with the AOP The cycle of dependence

We've already said , In the case of ordinary circular dependencies , Level 3 caching doesn't work . The L3 cache is actually the same as Spring Medium AOP relevant , Let's take another look at getEarlyBeanReference Code for :

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

If it's opening AOP Under the circumstances , So call to AnnotationAwareAspectJAutoProxyCreator Of getEarlyBeanReference Method , The corresponding source code is as follows :

public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// If you need an agent , Returns a proxy object , There is no need for an agent , Directly return the current incoming bean object
return wrapIfNecessary(bean, beanName, cacheKey);
}

Back to the example above , We are right. A the AOP Agent's words , So at this time getEarlyBeanReference A post proxy object is returned , Instead of objects created in the instantiation phase , That means B In the injection A It will be a proxy object instead of A Object created in the instantiation phase of .

When you see this picture, you may have the following questions

  1. In giving B Why inject a proxy object when injecting ?

answer : When we are right A the AOP When the agent , What we want from the container is A The object after proxy instead of A In itself , So the A When injecting as a dependency, its proxy object should also be injected

  1. When it was initialized, it was A object , that Spring Where is the proxy object put into the container ?

After initialization ,Spring Again getSingleton Method , The parameters passed in this time are different ,false It can be understood as disabling the L3 cache , As mentioned in the previous figure , For B In the injection A The factory in the level 3 cache has been taken out , And get an object from the factory and put it into the second level cache , So this one here getSingleton The method takes time to get the proxy from the secondary cache A object .exposedObject == bean It can be regarded as certain , Unless you have to replace the normal process in the post processor of the initialization phase Bean, For example, add a post processor :

@Component
public class MyPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("a")) {
return new A();
}
return bean;
}
}

however , Please don't do this , Add to your troubles !

  1. Initialization is for A Object itself , And into the container and into B All of them are proxy objects , It's not going to be a problem ?

answer : Can't , It's because whether it's cglib The agent or jdk Dynamic proxy generated proxy class , Internally, there is a reference to the target class , When a method of a proxy object is called , The method of the target object is actually called ,A The completion of initialization is equivalent to the initialization of the proxy object itself

  1. Why use factory instead of reference for L3 cache ? In other words , Why do you need this level 3 cache , Can't you expose a reference directly through the L2 cache ?

answer : The purpose of this factory is to delay the proxy for objects generated in the instantiation phase , Only when circular dependency really happens , To generate proxy objects in advance , Otherwise, only a factory will be created and put into the third level cache , But we don't really create objects through this factory

We think about a simple situation , Just create it alone A For example , hypothesis AB There's no dependency between them right now , however A Was represented , At this time A After the completion of the instantiation, we will still enter the following code :

// A Is a singleton ,mbd.isSingleton() Conditions met
// allowCircularReferences: This variable represents whether circular dependencies are allowed , On by default , The conditions are also met
// isSingletonCurrentlyInCreation: Creating A, Also meet
// therefore earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// I'll still get into this code
if (earlySingletonExposure) {
// Or will a factory object be exposed in advance through the level 3 cache
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

See? , Even if there is no circular dependency , It will also be added to the third level cache , And it has to be added to the third level cache , Because so far Spring I'm not sure about this Bean Is there anything else Bean There's a circular dependency .

Suppose we use L2 cache directly here , So it means all the Bean In this step, we have to complete AOP agent . Is this necessary ?

It's not only unnecessary , And it's against Spring In combination with AOP Follow Bean The design of the life cycle of !Spring combination AOP Follow Bean The life cycle itself is through AnnotationAwareAspectJAutoProxyCreator This is done by the post processor , In this post-processing postProcessAfterInitialization Method after initialization Bean complete AOP agent . If there is a circular dependency , There's no way , Only for Bean First, create a proxy , But without circular dependency , The beginning of design is to let Bean Complete the agent at the end of the lifecycle, not immediately after instantiation .

Does level 3 caching really improve efficiency ?

Now we know what level 3 caching really does , But this answer may not convince you , So let's finally summarize and analyze a wave , Does level 3 caching really improve efficiency ? It is divided into two parts :

  1. no AOP Of Bean Circular dependence between

It can be seen from the above analysis that , In this case, level 3 caching doesn't work at all ! So there's no saying that it improves efficiency

  1. the AOP Of Bean Circular dependence between

Take our A、B For example , among A By AOP agent , Let's first analyze the case of using level 3 cache ,A、B The creation process of

Suppose you don't use level 3 caching , Directly in the L2 cache

The only difference between the above two processes is that they are A Objects create proxies at different times , In the case of using level 3 cache, it is A The time to create an agent is B Need injection in A When , Instead of using the level 3 cache, you can use the A After instantiation, you need to create A Create a proxy and put it in the secondary cache . For the entire A、B In terms of the creation process of , It takes the same amount of time

Sum up , In either case , It's wrong to say that level 3 caching improves efficiency !

summary

interviewer :”Spring How to solve the circular dependency ?“

answer :Spring Loop dependency is solved through three-level cache , The first level cache is the singleton pool (singletonObjects), Second level cache for early exposure objects earlySingletonObjects, Level 3 cache for early exposure object factory (singletonFactories). When A、B When a circular reference occurs between two classes , stay A After instantiation , Use the instantiated object to create an object factory , And add it to the third level cache , If A By AOP agent , So what we get through this factory is A The object after the agent , If A Has not been AOP agent , So what this factory gets is A Instantiated object . When A When injecting properties , Will create B, meanwhile B It depends on A, So create B At the same time, it will call getBean(a) To get the dependency you need , At this time getBean(a) Will get... From the cache , First step , Get the factory in the third level cache first ; The second step , Call the... Of the object factory getObject Method to get the corresponding object , Get this object and inject it into B in . Then B Will go through its life cycle process , Include initialization 、 Post processor, etc . When B After you create , Will B Then pour it into A in , here A And complete its entire life cycle . thus , Loop dependency ends !

interviewer :” Why use level 3 caching ? Can L2 cache solve circular dependency ?“

answer : If you want to use L2 cache to solve circular dependency , It means everything Bean After instantiation, we have to complete AOP agent , This is against Spring Principles of design ,Spring At the beginning of design, it was through AnnotationAwareAspectJAutoProxyCreator This post processor comes from Bean The last step in the life cycle to complete AOP agent , Not immediately after instantiation AOP agent .

A thinking question

Why can circular dependency be solved in the third case in the table below , And the fourth situation can't be solved ?

Tips :Spring Creating Bean By default, it will be created according to natural sorting , therefore A Will precede B Create

Dependence Dependency injection Is circular dependency resolved
AB Interdependence ( Cyclic dependence ) All adopt setter Methods to inject yes
AB Interdependence ( Cyclic dependence ) All of them were injected by constructors no
AB Interdependence ( Cyclic dependence ) A In the injection B By setter Method ,B In the injection A The way to do it is the constructor yes
AB Interdependence ( Cyclic dependence ) B In the injection A By setter Method ,A In the injection B The way to do it is the constructor no

If this article is helpful to you , Remember to like it ! And welcome to follow me , Follow me and study hard Java. As a reading welfare, I also organized some Java Learning materials , Including microservices 、MySQL、 Distributed 、SSM frame 、Java Basics 、Redis、 Data structure and algorithm 、 The Internet 、Linux、Spring Family bucket 、JVM、 High concurrency, etc , Now share it for free with those who have read this article Java The programmer , If you need it, you can collect it yourself ~

The most complete study notes are the real questions of Dachang + Microservices +MySQL+ Distributed +SSM frame +Java+Redis+ Data structure and algorithm + The Internet +Linux+Spring Family bucket +JVM+ High concurrency + Each big study thought brain map + Interview set

版权声明
本文为[Java Architect]所创,转载请带上原文链接,感谢
https://javamana.com/2021/06/20210623170032610H.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云