引言
在相同 Bean 中使用 @Transactional 时一个很常见的场景,程序员基本也都知道使用自我注入(Self-Injection)的方式去解决,这个本身没什么难度。
本文只是想考据一下 Spring Framework,具体是哪里的代码支持了自我注入。
同类调用中 @Transactional 的失效问题
我们偶尔会在想调用同一个类的方法时,对调用的方法添加事务,如下
@Service
public class MyService {
public void doSomething() {
// ...
doSomethingElse();
}
@Transactional
public void doSomethingElse() {
// ...
}
}
但是此时 @Transactional 会失效,起不到事务的作用。主要原因在于 @Transactional 采用的是 Spring 中的 AOP 机制。AOP 机制的介绍可以看以下链接
Chapter 6. Aspect Oriented Programming with Spring
其中有一句需要注意
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’).
此处告诉我们 AOP 是基于代理系统的,作用于代理的目标对象,所以直接调用自身 class 的方法时是失效的。除非注入自身 class 作为代理对象 (self injection)。
如何自我注入 (Self-Injection)
关于如何注入自身,可以看以下文章
Self-Injection With Spring | Baeldung
从这里我们可以看出,有两种方式,一种是使用 @Autowired 注解,一种是使用 ApplicationContextAware,下面我们列举一下这两种方式
使用 @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
}
}
使用 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);
// ...
}
}
为什么自我注入 (Self-Injection) 不会引发循环引用 (circular dependency)
Spring Framework 官方是在 4.3 版本之后支持的,具体可以看这条 PR
可以看到判断 Self-Injection 最关键的方法是 isSelfReference,入参 beanName 是需要注入的 self bean,candidateName 是当前正在初始化的 bean
/**
* 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()))));
}
其次我们看一下 isSelfReference 的调用方法 findAutowireCandidates,beanName 指的是当前需要注册的 bean,requiredType 是需要注入的 class 类型,descriptor 主要是对需要注入的 bean 的具体描述,如 name、required 等等。考虑到 requiredType 有可能是接口类,故通过 BeanFactoryUtils.beanNamesForTypeIncludingAncestors 方法可以拿到具体的需要注入的 beanNames,并放在 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 版本测试
现在我们使用 Spring 4.2 的版本做一下测试。我们使用 1.3.8.RELEAS 版本的 spring-boot-starter-parent,其对应的 spring.version 是 4.2.8.RELEASE。测试类如下:
-
BeanA 使用 @Autowired 自我注入
-
BeanC 使用 ApplicationContextAware 自我注入
@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);
}
}
启动之后报错如下:
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
由此可以看到,BeanA 的写法造成了循环引用导致 NoSuchBeanDefinitionException 异常;BeanC 的写法没有报错。
Spring 4.3 版本测试
使用 1.4.0.RELEASE 版本的 spring-boot-starter-parent,其对应的 spring.version 是 4.3.2.RELEASE。类似构建如上的 BeanA、BeanB 和 BeanC,启动后发现并无异常。
打断点到 DefaultListableBeanFactory 可以看到:
总结
Spring 是在 4.3 版本支持了”自己注入自己”,对应到 springboot 的版本是 1.4.0