Introduction #
Using @Transactional on the same bean is a very common scenario, and programmers basically all know how to use self-injection to solve it.
This article just wants to examine the Spring Framework to see where exactly the code supports self-injection.
The invalidation of @Transactional in similar calls #
Occasionally, when we want to call a method in the same class, we add a transaction to the called method, as follows
@Service
public class MyService {
public void doSomething() {
// ...
doSomethingElse();
}
@Transactional
public void doSomethingElse() {
// ...
}
}
However, @Transactional will be invalid at this time and will not function as a transaction. The main reason for this is that @Transactional uses the AOP mechanism in Spring. For an introduction to the AOP mechanism, see the following link
Chapter 6. Aspect Oriented Programming with Spring
One thing to note is
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 a proxy system and acts on the target object behind the proxy, so directly calling the method of its own class is invalid. Unless the class is injected as the proxy object (self injection).
How to self-inject (Self-Injection) #
For information on how to inject yourself, see the following article
Self-Injection With Spring | Baeldung
From this we can see that there are two ways to do this: one is to use the @Autowired annotation, and the other is to use ApplicationContextAware. Let’s list the two methods
Use @Autowired #
@Component
public class MyBean {
@Autowired
private MyBean self;
public void doSomething() {
// use self reference here
}
}
@Component
public class MyBean {
@Autowired
private MyBean self;
public void doSomething() {
// use self reference here
}
}
Using ApplicationContextAware #
@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 does self-injection not cause circular dependencies #
The Spring Framework officially supports it after version 4.3. For details, see this PR
[Injection support for Collection/Map beans and self references · spring-projects/spring-framework@4a0fa69](https://github.com/spring-projects/spring-framework/commit/4a0fa69ce469cae2e8c8a1a45f0b43 f74a74481d#diff-7647ef528e5ffcfdb8ce8b88d189a906a1e7b46778323bc92cb9783a4991d5ab)
You can see that the most critical method for determining Self-Injection is isSelfReference. The input parameter beanName is the self bean to be injected, and candidateName is the bean currently being initialized
/**
* 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, we look at the method findAutowireCandidates called by isSelfReference. beanName refers to the bean that needs to be registered, requiredType is the class type that needs to be injected, and descriptor mainly describes the specific descriptor of the bean to be injected, such as name, required, etc. Considering that requiredType may be an interface class, the BeanFactoryUtils. * beanNamesForTypeIncludingAncestors * method can be used to get the specific beanNames to be injected and put them in candidateNames.
/**
* 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 will test with Spring 4.2. We use the 1.3.8.RELEASE version of spring-boot-starter-parent, and the corresponding spring.version is 4.2.8.RELEASE. The test class is as follows:
- BeanA uses @Autowired to inject itself
- BeanC uses ApplicationContextAware to inject itself
@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 following error is reported:
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
From this, we can see that the way BeanA is written causes a circular reference, resulting in a NoSuchBeanDefinitionException exception; BeanC is written in a way that does not report an error.
Spring 4.3 version test #
The 1.4.0.RELEASE version of spring-boot-starter-parent is used, and the corresponding spring.version is 4.3.2.RELEASE. Similar to the above-mentioned BeanA, BeanB and BeanC, no exceptions were found after startup.
Interrupt the point to the DefaultListableBeanFactory and you can see:
Summary #
Spring supports “injecting itself” in version 4.3, corresponding to the springboot version 1.4.0.