Blog: bugstack.cn
Precipitation, share, grow, let yourself and others can gain something! 😄
One, foreword
What does delayed gratification get you?
There are four years in college, but almost everyone finds it hard to find a good job until near graduation, especially in software development, which I’m familiar with. Even after graduation, you have to pay extra money to go to a training institute and learn programming skills before you can get a job. It’s like I haven’t learned anything at all in these years!
As far as I am concerned, it may be because I like programming when I was in school. I have also heard from senior teachers and senior teachers that it is not easy to find a job after graduation, and I have also learned about the level of requirements for programmer development skills in the society. So armed with this information, and my willingness to struggle, I set a small goal that I could accomplish every day:
Several Kings of the world of mortals, I refuse to accept. Typed code 200 lines a day, rushed into the world's top 500.Copy the code
Ha ha ha, so two hundred lines of code every day, a month is 6000 lines, one year is 60000 lines, 180000 line three years later started to practice, a new intern on nearly 200000 lines of code, almost can be very skilled to complete all kinds of simple work, in the practice of real chain scission of the whole project process, looking for a serious development work, It’s easy.
At this time, it is easy to find a job because you have been learning and precipitation, but if you do not make these efforts, you may become very flustered after graduation, and finally have to go to some institutions to learn again.
2. Interview questions
Thanks for the plane, note! “, I used to feel like Spring is nothing, read getBean, my God!
Interviewer: Recently I looked at Spring’s getBean and found a lot of things in it. Another one is to solve the problem of loop dependency. What should I ask about this in the interview?
Interviewer: Yes, how does Spring solve circular dependencies?
Xie Plane: well, through three levels of cache in advance to expose the object to solve.
Interviewer: Sure, what kind of object information are stored in the three caches?
Xie: Level 1 cache stores complete objects, also known as finished objects. The level 2 cache holds semi-finished objects, those that have not yet been assigned a value. Level 3 caches store ObjectFactory
are used to handle AOP loop dependencies.
Interviewer: Sure, thank you for your preparation! So if you don’t have a level 3 cache, if you have a level 2 cache or a level 1 cache, can you do that?
Xie Yijie: Actually, I have read the information and it can be solved, but Spring needs to ensure several things. Only the level 1 cache processing process cannot be split, and the complexity will increase. Meanwhile, semi-finished objects may have empty pointer exceptions. Separating semi-finished products from finished objects makes processing more elegant, simple, and extensible. Two of Spring’s major features are not only IOC but also AOP, which is where the bytecode-enhanced methods should be stored, and the three-level cache is the most important. The round-robin dependency is handled by AOP, but the two-level cache can also be handled if the creation of AOP proxy objects is advanced. However, this violates Spring’s object creation principles, which prefer to initialize all ordinary beans and handle the initialization of proxy objects.
Interviewer: Airplanes, not bad. I learned a lot about them this time. What about a simple solution to loop dependency that you’ve worked on?
Thank plane: oh, this have no, have not practiced!! You really should try it. Try it.
What is cyclic dependency?
1. Problem description
Understanding the nature of the problem and then analyzing the problem is often more conducive to a deeper understanding and research of the problem. So before we look at Spring’s source code for loop dependencies, let’s take a look at what they are.
- Cyclic dependencies can be divided into three types: self-dependent, inter-cyclic and multi-group cyclic dependencies.
- But the essence of circular dependencies is the same regardless of the number of them. That is, your full build depends on me, and my full build depends on you, but we can’t decouple from each other, so dependency build fails.
- So Spring provides a setter loop dependency injection solution in addition to constructor injection and stereotype injection. Then we can also try to first under such dependence, if we deal with the words of how to solve.
2. Problem presentation
public class ABTest {
public static void main(String[] args) {
newClazzA(); }}class ClazzA {
private ClazzB b = new ClazzB();
}
class ClazzB {
private ClazzA a = new ClazzA();
}
Copy the code
- This section of code is the loop depends on the original appearance, you have me, I have you, run on the error
java.lang.StackOverflowError
- This kind of loop-dependent code can’t be solved, and when you see get/set or annotations provided in Spring, it can be solved first by some decoupling. Separate the creation of classes from the filling of properties. Create semi-finished beans first, and then handle the filling of properties to complete the provision of finished beans.
3. Problem solving
In this part of the code for a core purpose, let’s solve the loop dependency ourselves, solution is as follows:
public class CircleTest {
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// instantiate the object into the cache
Object obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
// The property fills the completion object
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); Class<? > fieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase(); field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass)); field.setAccessible(false);
}
return(T) obj; }}class A {
private B b;
/ /... get/set
}
class B {
private A a;
/ /... get/set
}
Copy the code
-
This code provides two classes, A and B, that depend on each other. But the dependencies in both classes are populated using setters. This is the only way to prevent the creation of two classes that do not have to be strongly dependent on another object.
-
GetBean is the core content of the whole solution to the circular dependency. After creation, A depends on B to fill the property, so we create B. When creation B starts to fill, we find that IT depends on A, but the semi-finished object A has been cached in singletonObjects, so B can be created normally. I’ve created A completely by recursion.
4. Source code analysis
1. Go into details
As we can see from the above example, when A and B depend on each other, A fills property B after creation, continues to create B, and fills property A again, which can be obtained from the cache, as follows:
So what does this transaction loop dependency look like in Spring? Expand the details!
While the core principles are the same, it becomes a little more complicated when it comes to supporting IOC and AOP features throughout Spring. The whole process of handling Spring cycle dependencies is as follows.
- That’s all you need in Spring to get a looping dependent object
Tell me about the details
- At first glance, it looks like a lot of flow, but these are basically snippet of code that you have to go through when debugging code. It’s very convenient to get this execution flow and debug it again.
2. Process
The source code analysis of the cases covered in this chapter has been updated to github: github.com/fuzhengwei/… – interview-31
The following are the AB dependent Bean operations in the unit test, focusing on the source tracking of getBean;
@Test
public void test_alias(a) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class);
logger.info("Get Bean by alias: {}", bean_a.getBean_b());
}
Copy the code
org.springframework.beans.factory.support.AbstractBeanFactory.java
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null.false);
}
Copy the code
- After entering from getBean, the operation to get the bean goes to doGetBean.
- The reason for wrapping the layer this way is that doGetBean has a number of overloaded methods with different input parameters to facilitate external manipulation.
DoGetBean method
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
// Get the bean instance from the cache
Object sharedInstance = getSingleton(beanName);
Mbd.issingleton () is used to determine whether the bean is a singleton pattern
if (mbd.isSingleton()) {
// Get the bean instance
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject(a) throws BeansException {
try {
// Create the bean instance, createBean returns the bean instantiated
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throwex; }}});// Subsequent processing operations
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ...
// Return the bean instance
return (T) bean;
}
Copy the code
- As can be seen from the flow chart of source code analysis, this part is to determine whether there is an instance object from getSingleton first. For the first time, there is definitely no object, so continue to go down.
- After judging the MBD.Issingleton () singleton, the createBean is created using objectFactor-based wrappers, and the core logic is to perform the doCreateBean operation.
DoCreateBean method
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Create a bean instance and wrap the bean instance in a BeanWrapper object
instanceWrapper = createBeanInstance(beanName, mbd, args);
// Add the bean factory object to the singletonFactories cache
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject(a) throws BeansException {
// Get an early reference to the original object. In the getEarlyBeanReference method, AOP related logic is executed. If the bean is not intercepted by AOP, getEarlyBeanReference returns the bean as is.
returngetEarlyBeanReference(beanName, mbd, bean); }});try {
// Populate the properties and resolve the dependencies
populateBean(beanName, mbd, instanceWrapper);
if(exposedObject ! =null) { exposedObject = initializeBean(beanName, exposedObject, mbd); }}// Return the bean instance
return exposedObject;
}
Copy the code
- There’s a lot more involved in the doCreateBean method, but the core is creating instances, caching, and ultimately property populating, which is populating the classes involved in the individual property fields of a bean.
createBeanInstance
, creates a bean instance and wraps it in a BeanWrapper objectaddSingletonFactory
Add the bean factory object to the singletonFactories cachegetEarlyBeanReference
, gets an early reference to the original object, and in the getEarlyBeanReference method, AOP related logic is executed. If the bean is not intercepted by AOP, getEarlyBeanReference returns the bean as is.populateBean
, populates properties, and resolves dependencies. That is, start here to find property B in instance A, then create instance B, and then come back.
GetSingleton level 3 cache
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Get the instance from singletonObjects, which is a finished bean
Object singletonObject = this.singletonObjects.get(beanName);
/ / judge beanName, whether isSingletonCurrentlyInCreation corresponding bean is created
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// Get the pre-exposed raw bean from earlySingletonObjects
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// Get the corresponding bean factoryObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
if(singletonFactory ! =null) {
// Pre-expose bean instances, mainly used to resolve AOP loop dependencies
singletonObject = singletonFactory.getObject();
// Put singletonObject in the cache and remove singletonFactory from the cache
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName); }}}}return(singletonObject ! = NULL_OBJECT ? singletonObject :null);
}
Copy the code
singletonObjects.get(beanName)
Get the instance from singletonObjects, which is a finished beanisSingletonCurrentlyInCreation
BeanName, judgment, whether isSingletonCurrentlyInCreation corresponding bean is createdallowEarlyReference
Get the pre-exposed raw bean from earlySingletonObjectssingletonFactory.getObject()
, pre-exposure bean instances, primarily used to resolve AOP loop dependencies
To sum up, it is a code process that deals with cycle dependence. The content extracted from this part is mainly the core content, and it is not removed from all the long articles. We will be involved in more debugging, and we should debug several times according to the flow chart operation as far as possible.
3. Dependency resolution
To sum up, we learn the core solution principle of cyclic dependence by trying to solve it ourselves. Also analyzed the Spring solution of the cycle dependency processing process and core source code analysis. So next we summarize the different processing processes of the three levels of cache, which is a summary and convenient for everyone to understand.
1. Can level 1 cache be solved?
- In fact, only level 1 cache does not fail to solve circular dependencies, as we did in our own example.
- In Spring, however, it can be very cumbersome to handle as we did in our example, and NPE problems can also occur.
- Therefore, according to the process of Spring code processing, we analyze the process of storing finished beans like level 1 cache, which cannot solve the problem of loop dependence. Since the creation of finished products of A depends on B, and the creation of finished products of B depends on A, when B’s attributes need to be completed, A is still not created, so there will be an infinite loop.
2. Can level 2 cache be solved?
- This is actually easier to do with two levels of caching, one for finished objects and the other for semi-finished objects.
- After creating the semi-finished object, A stores it in the cache and then replenishes the B dependent properties of the A object.
- B continues to create, and the semi-finished product is also put into the cache. When the object’s A property is added, it can be retrieved from the semi-finished product cache. Now B is A complete object, and then A is A complete object like A recursive operation.
3. What does level 3 cache solve?
- Spring dependencies can be resolved with a level 2 cache. In fact, as we mentioned earlier in our analysis of the source code, level 3 caching primarily addresses Spring AOP features. AOP itself is an enhancement of methods, yes
ObjectFactory<? >
Type, and Spring’s principles do not want beans of this type to be created front-loaded, so they are stored in the level 3 cache for processing. - In fact, the overall processing process is similar, except that when B fills in attribute A, it first queries the finished product cache, then the semi-finished product cache, and finally checks whether the singleton engineering class is in the third-level cache. Finally, call the getObject method to return the proxy reference or the original reference.
- This solves the three-level caching problem with Spring AOP.The AOP dependencies covered in this section have source code examples that you can debug
Five, the summary
- The review begins with hands-on examples to give you an overview of loop dependencies, as well as hands-on examples of their solutions, so that you won’t be unfamiliar with the rest of Spring’s approach to loop dependencies.
- As you can see throughout this article, level 3 caching is not necessary, but is required to satisfy the principles that Spring created itself. If you can download the Spring source code to modify this part of the code, create AOP objects ahead of time and store them in the cache, then second-level caching can also solve the problem of loop dependencies.
- Circular dependencies may not be a good way to code, if you still want to use more reasonable design patterns in your own programs to avoid circular dependencies, maybe these methods will increase the amount of code, but will be more convenient in maintenance.Of course, this is not mandatory, can be based on your needs.
Six, series recommendation
- How do I stuff a Bean into a Spring container?
- What are the features of Spring IOC?
- Full process source code parsing for getBean in Spring
- For a long time, Little Fuge’s “Relearning Java Design Pattern” has finally been published, color printing & paper!
- A Bug, let me find the Java world. AJ (cone)!