Why Spring can "inject itself

Why Spring can "inject itself"

introduction

It is a very common scenario when using @Transactional in the same bean, and programmers basically know to use self-injection (Self-Injection) to solve it, which is not difficult in itself.

This article just wants to check the Spring Framework, specifically where the code supports self-injection.

Invalidation of @Transactional in similar calls

Occasionally, when we want to call a method of the same class, we add a transaction to the called method, as follows

 
 

java

copy code

@Service public class MyService { public void doSomething() { // ... doSomethingElse(); } @Transactional public void doSomethingElse() { // ... } }

But @Transactional will be invalid at this time, and it will not work as a transaction. The main reason is that @Transactional uses the AOP mechanism in Spring. The introduction of AOP mechanism can be seen in the following link

Chapter 6. Aspect Oriented Programming with Spring

One of them needs attention

In addition, AspectJ itself has type-based semantics and at an execution join point both 'this' and 'target' refer to the same object - the object executing the method. Spring AOP is a proxy based system and differentiates between the proxy object itself (bound to 'this') and the target object behind the proxy (bound to 'target').

This tells us that AOP is based on the proxy system and acts on the target object of the proxy, so it will be invalid when directly calling the method of its own class. Unless it injects its own class as a proxy object (self injection).

How to self-inject (Self-Injection)

For how to inject yourself, you can read the following article

Self-Injection With Spring | Baeldung

From here we can see that there are two ways, one is to use *** @Autowired annotation, the other is to use ApplicationContextAware ***, let’s list these two ways below

Use ***@Autowired***

 
 

java

copy code

@Component public class MyBean { @Autowired private MyBean self; public void doSomething() { // use self reference here } }

 
 

java

copy code

@Component public class MyBean { @Autowired private MyBean self; public void doSomething() { // use self reference here } }

Use ApplicationContextAware

 
 

java

copy code

@Component public class MyBean implements ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } public void doSomething() { MyBean self = context.getBean(MyBean.class); // ... } }

Why self-injection (Self-Injection) does not cause circular reference **(circular dependency)**

Spring Framework is officially supported after version 4.3. For details, please see this PR

Injection support for Collection/Map beans and self references · spring-projects/spring-framework@4a0fa69

It can be seen that the most critical method for judging Self-Injection is isSelfReference, the input parameter beanName is the self bean that needs to be injected, and candidateName is the bean currently being initialized

 
 

java

copy code

/** * Determine whether the given beanName/candidateName pair indicates a self reference, * i.e. whether the candidate points back to the original bean or to a factory method * on the original bean. */ private boolean isSelfReference(@Nullable String beanName, @Nullable String candidateName) { return (beanName != null && candidateName != null && (beanName.equals(candidateName) || (containsBeanDefinition(candidateName) && beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName())))); }

Next, let's look at the call method findAutowireCandidates of isSelfReference. beanName refers to the bean that needs to be registered currently, requiredType is the class type that needs to be injected, and descriptor is mainly a specific description of the bean that needs to be injected, such as name, required, etc. Considering that requiredType may be an interface class, the specific beanNames that need to be injected can be obtained through the BeanFactoryUtils. beanNamesForTypeIncludingAncestors method and placed in candidateNames.

 
 

java

copy code

/** * Find bean instances that match the required type. * Called during autowiring for the specified bean. * @param beanName the name of the bean that is about to be wired * @param requiredType the actual type of bean to look for * (may be an array component type or collection element type) * @param descriptor the descriptor of the dependency to resolve * @return a Map of candidate names and candidate instances that match * the required type (never {@code null}) * @throws BeansException in case of errors * @see #autowireByType * @see #autowireConstructor */ protected Map<String, Object> findAutowireCandidates( @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length); for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) { Class<?> autowiringType = classObjectEntry.getKey(); if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = classObjectEntry.getValue(); autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } } for (String candidate : candidateNames) { if (!***isSelfReference***(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty()) { boolean multiple = indicatesMultipleBeans(requiredType); // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidate : candidateNames) { if (!***isSelfReference***(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) && (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) { addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty() && !multiple) { // Consider self references as a final pass... // but in the case of a dependency collection, not the very same bean itself. for (String candidate : candidateNames) { if (***isSelfReference***(beanName, candidate) && (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && isAutowireCandidate(candidate, fallbackDescriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } } } return result; }

Spring 4.2 version test

Now we use the Spring 4.2 version to do some testing. We use the 1.3.8.RELEAS version of spring-boot-starter-parent, and its corresponding spring.version is 4.2.8.RELEASE . The test class is as follows:

  • BeanA injects itself using @Autowired
  • BeanC injects itself using ApplicationContextAware
 
 

java

copy code

@Component public class BeanA { @Autowired private BeanB b; @Autowired private BeanA a; } @Component public class BeanB { } @Component public class BeanC implements ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = context; } public void doSomething() { BeanC self = context.getBean(BeanC.class); } }

After starting, the error is as follows:

 
 

java

copy code

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.example.BeanA org.example.BeanA.a; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.example.BeanA] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:573) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] ... 16 common frames omitted Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.example.BeanA] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1380) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1126) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1021) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545) ~[spring-beans-4.2.8.RELEASE.jar:4.2.8.RELEASE] ... 18 common frames omitted

It can be seen from this that the writing method of BeanA caused a circular reference and caused NoSuchBeanDefinitionException; the writing method of BeanC did not report an error.

Spring 4.3 version test

Use the 1.4.0.RELEASE version of spring-boot-starter-parent, and its corresponding spring.version is 4.3.2.RELEASE . BeanA, BeanB, and BeanC, which are similar to the above construction, are found to be normal after startup.

Breaking point to DefaultListableBeanFactory can see:

Summarize

Spring supports "self-injection" in version 4.3 , and the version corresponding to springboot is 1.4.0

Guess you like

Origin blog.csdn.net/BASK2312/article/details/131419460