跳过正文
  1. 文章/

为什么Spring可以“自己注入自己”

·2032 字·5 分钟
编程框架
Weaxs
作者
Weaxs

引言
#

在相同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

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

可以看到判断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

相关文章

Go语言指针性能
·2205 字·5 分钟
编程框架
Go值对象和指针对象在存储、性能和使用上的区别
Netty 源码分析及内存溢出思路
·4372 字·9 分钟
编程框架
主要介绍了Java NIO、Netty和Spring Reactor中Buffer的使用和继承关系。
博客搭建简述
·1668 字·4 分钟
编程框架
简述一下博客搭建过程。
框架使用的是用GO语言编写的Hugo,Hugo模板用的是blowfish。