惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

豆逗子的小黑屋

GEMINI-CLI settings 参数详情 使用 sing-box Tun 模式实现 V2rayU 的透明代理 浅析 Claude Code 的执行与提示词 多模态模型是如何处理和理解图片的? 从配料表出发:便秘的猫应该怎么选主食 盘点开源的 DeepResearch 实现方案 浅谈 DeepSeek-R1 和 Kimi k1.5 论文中的思维链 + 强化学习 使用 TiDB Vector 构建 LightRAG 知识库 从论文到源码:详解 RAG 算法 云南之行——游在大理食在昆明 浅入浅出 Rerank 模型 一年同行:我的TiDB社区之旅 读书笔记《大语言模型》 TiDB Vector + Dify 快速构建 AI Agent 基于 LLM 推动游戏叙事 HTTP/2 和 CONTINUATION Flood 混合专家模型 (MoE) 笔记 报告分享: IMF第四次磋商报告 和 美联储研究笔记 使用 Coze 搭建 TiDB 助手 2023年总结 读书笔记《大规模语言模型:从理论到实践》 TiDB知识点梳理 (PCTA 笔记) 向量相似性检索方法 Java & Go 线程模式对比 Hugo + umami 博客统计面板 资产配置 101 探究 Spring-Boot 内置Server 搭建Go版本Kubernetes微服务示例 Go语言指针性能 从固定走向浮动 ——《时运变迁》读书笔记 Netty 源码分析及内存溢出思路 博客搭建简述 推荐 笔记 看得见的手——《置身事内》读书笔记 2022年初读书回顾 荐书:《走出唯一真理观》 nintendo switch 关于 邮箱
为什么Spring可以“自己注入自己”
2023-06-21 · via 豆逗子的小黑屋

引言 #

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