主頁 > 後端開發 > spring-cloud-square原始碼速讀(retrofit + okhttp篇)

spring-cloud-square原始碼速讀(retrofit + okhttp篇)

2021-11-04 12:59:32 後端開發

歡迎訪問我的GitHub

這里分類和匯總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

spring-cloud-square系列文章

  1. 五分鐘搞懂spring-cloud-square
  2. spring-cloud-square開發實戰(三種型別全覆寫)
  3. spring-cloud-square原始碼速讀(spring-cloud-square-okhttp篇)
  4. spring-cloud-square原始碼速讀(retrofit + okhttp篇)

本篇概覽

  • 本文是《spring-cloud-square學習》系列的終篇,上一篇咱們了解了spring-cloud-square-okhttp庫的原始碼和原理,今天提升一點難度,看看spring-cloud-square的另一種型別的原始碼:spring-cloud-square-retrofit,也就是下圖紅框中的那種:

在這里插入圖片描述

原始碼分析目標

  • 接下來開始分析spring-cloud-square-retrofit工程的原始碼,如下圖紅框所示:

在這里插入圖片描述

  • 本篇目標非常明確,只搞清楚一件事:在使用spring-cloud-square的時候,以前文的consumer-retrofit-okhttp子工程為例,為什么咱們只寫了HelloService介面,但卻能通過Autowired注解使用HelloService的實作類?

提前小結

  • 如果您想了解spring-cloud-square的retrofit部分的原理,卻又苦于沒有時間深入研究,可以看看下面這份提前小結的內容:
  1. 整個機制的運轉,可以分為相對獨立的四個部分:業務應用編碼使用spring-cloud-square相關的注解、bean的factory注冊到spring環境、bean的factory類在spring環境實體化、通過factory的實體在spring生產HelloService介面的實作類

  2. 根據上面的分析,最重要的應該是bean的factory類:RetrofitClientFactoryBean,它實作了FactoryBean介面,其getObject方法就是根據HelloService介面生成實作類和關鍵,最侄訓呼叫下圖紅框中的Retrofit.create方法創建實體:

在這里插入圖片描述

  1. Retrofit類并非spring-cloud的專案,而是來自Retrofit庫,其create方法中使用了JDK的Proxy.newProxyInstance方法,該方法可以根據HelloService介面生成一個實作了該介面的實體:

在這里插入圖片描述

  1. 在使用spring-cloud-square的retrofit + okhttp方案時,HelloService介面中使用的還是遠程服務的服務名,而不是地址和埠,這是因為使用了spring-cloud-square-okhttp庫,所以服務名轉為地址+埠的邏輯與前文《spring-cloud-square原始碼速讀(spring-cloud-square-okhttp篇)》保持一致
  • 以上就是整個原始碼分析的結論了,我將涉及到的關聯代碼流程整理成簡圖,如下所示:

在這里插入圖片描述

回顧應用如何使用spring-cloud-square-retrofit

  • 在分析原始碼之前,先回顧一下《spring-cloud-square開發實戰》中的代碼,咱們當時是如何使用spring-cloud-square-retrofit的(對應demo中的consumer-retrofit-okhttp子工程)
  1. 新建配置類OkHttpClientConfig,使用了EnableRetrofitClients注解,向spring環境注冊OkHttpClient.Builder實體:
@Configuration
@EnableRetrofitClients
class OkHttpClientConfig{
    @Bean
    @LoadBalanced
    public OkHttpClient.Builder okHttpClientBuilder() {
        return new OkHttpClient.Builder();
    }
}
  1. 定義HelloService介面,用注解RetrofitClient修飾,注解的值是遠程呼叫的服務名,里面宣告hello方法,用注解GET修飾,注解的值是遠程呼叫的介面的path:
@RetrofitClient("provider")
public interface HelloService {
    @GET("/hello-obj")
    Call<HelloResponse> hello(@Query("name") String name);
}
  1. 在業務要做遠程呼叫的時候,用Autowired注解修飾HelloService介面,即可呼叫HelloService.hello方法,至于介面對應的實體來自哪里,開發者不必關注:
@RestController
public class RemoteHello {
    @Autowired(required = false)
    HelloService helloService;

    @GetMapping("/remote-obj")
    public HelloResponse hello(@RequestParam("name") String name) throws IOException {
        return helloService.hello(name).execute().body();
    }
}
  • 以上就是咱們開發業務代碼時使用spring-cloud-square的關鍵操作,接下來就從原始碼角度來分析這些操作到底發揮了什么作用

原始碼分析(類定義注冊階段)

  • 回憶一下咱們寫的OkHttpClientConfig.java,里面使用了注解EnableRetrofitClients,這就是本次閱讀代碼的入口:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ RetrofitConfiguration.class, RetrofitClientsRegistrar.class })
public @interface EnableRetrofitClients {
	String[] value() default {};
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
	Class<?>[] defaultConfiguration() default {};
	Class<?>[] clients() default {};
}
  • 從上述代碼可見RetrofitConfiguration和RetrofitClientsRegistrar都會比實體化,RetrofitConfiguration過于簡單就不看了,重點關注RetrofitClientsRegistrar,先來看它的類圖,搞清楚繼承和實作

  • 如下圖所示,RetrofitClientsRegistrar集成自AbstractRetrofitClientsRegistrar,而AbstractRetrofitClientsRegistrar又集成自ImportBeanDefinitionRegistrar

在這里插入圖片描述

  • 所以,RetrofitClientsRegistrar被實體化的時候,就相當于ImportBeanDefinitionRegistrar介面的實作類被實體化了,這個ImportBeanDefinitionRegistrar介面,相信熟悉spring的同學對其不會陌生,它是用來動態注冊bean的,那么接下來的重點就是ImportBeanDefinitionRegistrar的registerBeanDefinitions方法的具體內容,看看它到底注冊了什么bean

  • registerBeanDefinitions方法的代碼在AbstractRetrofitClientsRegistrar.java中(請在上面的類圖中找到AbstractRetrofitClientsRegistrar的位置),如下所示,由于EnableRetrofitClients修飾的是咱們創建的OkHttpClientConfig.java,所以下面的入參AnnotationMetadata是OkHttpClientConfig類的注解資訊:

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerRetrofitClients(metadata, registry);
	}
  • 上述代碼的第一個方法registerDefaultConfiguration是注冊配置資訊的,非重點,跳過

  • 上述代碼的第二個方法registerRetrofitClients,這是本篇的關鍵,請重點關注下面代碼中的中文注釋:

public void registerRetrofitClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata.getAnnotationAttributes(getAnnotationClass().getName());
		// 過濾條件:有RetrofitClient注解修飾的類,對應咱們代碼中的HelloService.java
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(RetrofitClient.class);
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				// 找到的結果就是HelloService介面
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = https://www.cnblogs.com/bolingcavalry/archive/2021/11/04/beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),"@RetrofitClient can only be specified on an interface");

					// 取得修飾HelloService類的RetrofitClient注解的所有屬性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(RetrofitClient.class.getCanonicalName());
					// 根據這些屬性,得到遠程訪問的服務名是provider
					String name = getClientName(attributes);
					// 在spring注冊一個配置類,名為provider.RetrofitClientSpecification,
					// 由于修飾HelloService的RetrofitClient注解并沒有什么屬性,所以這個配置類沒有什么內容
					registerClientConfiguration(registry, name, attributes.get("configuration"));
					
					// 這個方法要重點關注,
					// 入參annotationMetadata是HelloService的元資訊,
					// attributes是修飾HelloService類的RetrofitClient注解的所有屬性
					registerRetrofitClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}
  • 將上述代碼中最后呼叫的registerRetrofitClient方法展開如下,這段代碼做了件很重要的事情:注冊BeanDefinition到spring,注冊的name等于com.bolingcavalry.consumer.service.HelloService,對應的BeanDefinition的beanClass等于RetrofitClientFactoryBean
private void registerRetrofitClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		// 由于注解修飾的是HelloService類,所以這里className等于com.bolingcavalry.consumer.service.HelloService
		String className = annotationMetadata.getClassName();
		// 注意getFactoryBeanClass()方法,來自RetrofitClientsRegistrar類,回傳值是RetrofitClientFactoryBean.class,
		// 因此,RetrofitClientFactoryBean就被帶入了definition中,
		// 注意,這個definition變數的型別是BeanDefinitionBuilder,
		// 其內部有個成員變數beanDefinition,此時該成員變數的beanClass欄位已經被設定為RetrofitClientFactoryBean.class
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(getFactoryBeanClass());
		validate(attributes);
		// HelloService的RetrofitClient注解沒有設定url屬性,因此這里是空字串
		definition.addPropertyValue("url", getUrl(attributes));
		// RetrofitClient注解的value屬性配置為遠程服務名,這里是provider
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		// 型別就是HelloService
		definition.addPropertyValue("type", className);
		// by_type,意味著autowire注解修飾HelloService的時候,可以用HelloService獲取對應的實作類
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = name + "RetrofitClient";
		
		// 通過BeanDefinitionBuilder得到了beanDefinition,
		// 這個beanDefinition的beanClass欄位在前面已經被設定為RetrofitClientFactoryBean.class
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		
		beanDefinition.setPrimary(true);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}
		
		// 將注冊BeanDefinition所需的兩個引數beanName和beanDefinition放入BeanDefinitionHolder物件中
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
		// 完成BeanDefinition在spring環境的注冊,name等于com.bolingcavalry.consumer.service.HelloService,對應的BeanDefinition的beanClass等于RetrofitClientFactoryBean(注意,這很重要)
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
  • 此刻,HelloService的類定義已經在spring完成了注冊,接下來要看HelloService介面的實作類來自何處;

BeanDefinition中的RetrofitClientFactoryBean被實體化

  • 在spring初始化程序中,上述紅框中的代碼會觸發spring環境對HelloService介面實作類的實體化,完整的觸發程序和詳細堆疊就不細說了,這都是spring的標準處理流程,接下來會挑這里面的重點去看

  • 首先就是大名鼎鼎的SpringApplication.refresh方法,這里面是bean的實體化邏輯,會執行一個重要方法,就是DefaultListableBeanFactory.doGetBeanNamesForType,這里面會遍歷所有已注冊的BeanDefinition,逐個處理,如下圖:

在這里插入圖片描述

  • DefaultListableBeanFactory.doGetBeanNamesForType繼續執行,會到下一個重點:根據BeanDefinition創建bean,堆疊如下,這是用條件斷點得到的:
doGetBean:256, AbstractBeanFactory (org.springframework.beans.factory.support) [2]
getTypeForFactoryBean:1709, AbstractBeanFactory (org.springframework.beans.factory.support)
getTypeForFactoryBean:899, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
isTypeMatch:637, AbstractBeanFactory (org.springframework.beans.factory.support)
doGetBeanNamesForType:583, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeanNamesForType:542, DefaultListableBeanFactory (org.springframework.beans.factory.support)
beanNamesForTypeIncludingAncestors:265, BeanFactoryUtils (org.springframework.beans.factory)
findAutowireCandidates:1546, DefaultListableBeanFactory (org.springframework.beans.factory.support)
doResolveDependency:1343, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1300, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:887, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:791, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:541, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1334, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1177, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:564, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:524, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 1485624601 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$488)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support) [1]
getBean:213, AbstractBeanFactory (org.springframework.beans.factory.support)
registerBeanPostProcessors:258, PostProcessorRegistrationDelegate (org.springframework.context.support)
registerBeanPostProcessors:762, AbstractApplicationContext (org.springframework.context.support)
refresh:567, AbstractApplicationContext (org.springframework.context.support)
refresh:769, SpringApplication (org.springframework.boot)
refresh:761, SpringApplication (org.springframework.boot)
refreshContext:426, SpringApplication (org.springframework.boot)
run:326, SpringApplication (org.springframework.boot)
loadContext:123, SpringBootContextLoader (org.springframework.boot.test.context)
loadContextInternal:99, DefaultCacheAwareContextLoaderDelegate (org.springframework.test.context.cache)
loadContext:124, DefaultCacheAwareContextLoaderDelegate (org.springframework.test.context.cache)
getApplicationContext:124, DefaultTestContext (org.springframework.test.context.support)
setUpRequestContextIfNecessary:190, ServletTestExecutionListener (org.springframework.test.context.web)
prepareTestInstance:132, ServletTestExecutionListener (org.springframework.test.context.web)
prepareTestInstance:244, TestContextManager (org.springframework.test.context)
postProcessTestInstance:138, SpringExtension (org.springframework.test.context.junit.jupiter)
lambda$invokeTestInstancePostProcessors$6:350, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
execute:-1, 2001115307 (org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$344)
executeAndMaskThrowable:355, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
lambda$invokeTestInstancePostProcessors$7:350, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
accept:-1, 1650113431 (org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$343)
accept:-1, 796667727 (java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda$107)
accept:193, ReferencePipeline$3$1 (java.util.stream)
accept:175, ReferencePipeline$2$1 (java.util.stream)
forEachRemaining:1384, ArrayList$ArrayListSpliterator (java.util)
copyInto:482, AbstractPipeline (java.util.stream)
wrapAndCopyInto:472, AbstractPipeline (java.util.stream)
forEachRemaining:312, StreamSpliterators$WrappingSpliterator (java.util.stream)
forEachRemaining:743, Streams$ConcatSpliterator (java.util.stream)
forEachRemaining:742, Streams$ConcatSpliterator (java.util.stream)
forEach:580, ReferencePipeline$Head (java.util.stream)
invokeTestInstancePostProcessors:349, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
lambda$instantiateAndPostProcessTestInstance$4:270, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
execute:-1, 1547883191 (org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$342)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
instantiateAndPostProcessTestInstance:269, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
lambda$testInstancesProvider$2:259, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
get:-1, 795748540 (org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$335)
orElseGet:267, Optional (java.util)
lambda$testInstancesProvider$3:258, ClassBasedTestDescriptor (org.junit.jupiter.engine.descriptor)
getTestInstances:-1, 361398902 (org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$234)
getTestInstances:31, TestInstancesProvider (org.junit.jupiter.engine.execution)
lambda$prepare$0:101, TestMethodTestDescriptor (org.junit.jupiter.engine.descriptor)
execute:-1, 451312813 (org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$334)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
prepare:100, TestMethodTestDescriptor (org.junit.jupiter.engine.descriptor)
prepare:65, TestMethodTestDescriptor (org.junit.jupiter.engine.descriptor)
lambda$prepare$1:111, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:-1, 1008315045 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$182)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
prepare:111, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:79, NodeTestTask (org.junit.platform.engine.support.hierarchical)
accept:-1, 1976870338 (org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$201)
forEach:1259, ArrayList (java.util)
invokeAll:38, SameThreadHierarchicalTestExecutorService (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$5:143, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:-1, 1647809929 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$197)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$7:129, NodeTestTask (org.junit.platform.engine.support.hierarchical)
invoke:-1, 928294079 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$196)
around:137, Node (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$8:127, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:-1, 728885526 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$195)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
executeRecursively:126, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:84, NodeTestTask (org.junit.platform.engine.support.hierarchical)
accept:-1, 1976870338 (org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$201)
forEach:1259, ArrayList (java.util)
invokeAll:38, SameThreadHierarchicalTestExecutorService (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$5:143, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:-1, 1647809929 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$197)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$7:129, NodeTestTask (org.junit.platform.engine.support.hierarchical)
invoke:-1, 928294079 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$196)
around:137, Node (org.junit.platform.engine.support.hierarchical)
lambda$executeRecursively$8:127, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:-1, 728885526 (org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$195)
execute:73, ThrowableCollector (org.junit.platform.engine.support.hierarchical)
executeRecursively:126, NodeTestTask (org.junit.platform.engine.support.hierarchical)
execute:84, NodeTestTask (org.junit.platform.engine.support.hierarchical)
submit:32, SameThreadHierarchicalTestExecutorService (org.junit.platform.engine.support.hierarchical)
execute:57, HierarchicalTestExecutor (org.junit.platform.engine.support.hierarchical)
execute:51, HierarchicalTestEngine (org.junit.platform.engine.support.hierarchical)
execute:108, EngineExecutionOrchestrator (org.junit.platform.launcher.core)
execute:88, EngineExecutionOrchestrator (org.junit.platform.launcher.core)
lambda$execute$0:54, EngineExecutionOrchestrator (org.junit.platform.launcher.core)
accept:-1, 607932305 (org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$150)
withInterceptedStreams:67, EngineExecutionOrchestrator (org.junit.platform.launcher.core)
execute:52, EngineExecutionOrchestrator (org.junit.platform.launcher.core)
execute:96, DefaultLauncher (org.junit.platform.launcher.core)
execute:75, DefaultLauncher (org.junit.platform.launcher.core)
startRunnerWithArgs:71, JUnit5IdeaTestRunner (com.intellij.junit5)
startRunnerWithArgs:33, IdeaTestRunner$Repeater (com.intellij.rt.junit)
prepareStreamsAndStart:221, JUnitStarter (com.intellij.rt.junit)
main:54, JUnitStarter (com.intellij.rt.junit)
  • 根據上述堆疊,要細看AbstractBeanFactory的doGetBean方法,請關注中文注釋:
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
		// 入參name等于"com.bolingcavalry.consumer.service.HelloService"
		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		// sharedInstance等于null,因此下面的if判斷不成立
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			// parentBeanFactory等于null,因此下面的if判斷不成立
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}
			
			// typeCheckOnly等于true,因此下面的if判斷不成立
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
					.tag("beanName", name);
			try {
				if (requiredType != null) {
					beanCreation.tag("beanType", requiredType::toString);
				}
				// 前面咱們分析過,BeanDefinition已經注冊到spring環境了,
				// 此處呼叫getMergedLocalBeanDefinition即可取得這個BeanDefinition
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				// HelloService的BeanDefinition沒有依賴,
				// 因此dependsOn等于null,下面的if不成立
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				// Create bean instance.
				// HelloService的bean是單例,因此下面的if判斷成立
				if (mbd.isSingleton()) {
					// 這里是創建bean的關鍵!!!
					// getSingleton傳入一個lambda運算式,方法內會呼叫該運算式,
					sharedInstance = getSingleton(beanName, () -> {
						try {
							// 根據BeanDefinition創建bean,
							// 實際上執行的是AbstractAutowireCapableBeanFactory.createBean方法
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ′" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}
				}
			}
			catch (BeansException ex) {
				beanCreation.tag("exception", ex.getClass().toString());
				beanCreation.tag("message", String.valueOf(ex.getMessage()));
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
			finally {
				beanCreation.end();
			}
		}

		return adaptBeanInstance(name, beanInstance, requiredType);
	}
  • 至此,RetrofitClientFactoryBean已經完成了實體化,接下來要去看HelloService介面背后的bean是怎么創建的

HelloService對應的bean是如何創建的

  • 回顧一下,咱們的應用代碼中用到HelloService的場景,如下圖紅框所示,使用Autowired注解修飾HelloService:

在這里插入圖片描述

  • 首先,上圖中的RemoteHello是一定會創建bean的,在創建的程序中,DefaultListableBeanFactory.doResolveDependency方法負責處理RemoteHello依賴的bean,如下圖所示,在此處觸發了HelloService的bean的實體化

在這里插入圖片描述

  • 輾轉反側,再次走到了AbstractBeanFactory.doGetBean方法,這次會執行下圖第二個紅框中的getObjectForBeanInstance方法:

在這里插入圖片描述

  • 然后到了最關鍵的位置:AbstractBeanFactory.getObjectForBeanInstance方法,這里面將RetrofitClientFactoryBean當做factory用了,用來生產HelloService:

在這里插入圖片描述

  • 將上圖紅框2中的getObjectFromFactoryBean方法繼續展開,進入FactoryBeanRegistrySupport.doGetObjectFromFactoryBean方法,這里完成了從spring框架到應用自定義之間的過渡:將bean的創建交給應用自己注冊的Factory來處理:

在這里插入圖片描述

  • 在RetrofitClientFactoryBean.getObject中,執行loadBalance(builder, context, serviceIdUrl):

在這里插入圖片描述

  • loadBalance的實作在RetrofitClientFactoryBean中:
	protected Object loadBalance(Retrofit.Builder builder, RetrofitContext context, String serviceIdUrl) {
		// 應用代碼的OkHttpClientConfig.java中,okHttpClientBuilder方法生成了OkHttpClient.Builder實體,此處的instances中就是這個實體
		Map<String, OkHttpClient.Builder> instances = context.getInstances(this.name, OkHttpClient.Builder.class);
		
		for (Map.Entry<String, OkHttpClient.Builder> entry : instances.entrySet()) {
			String beanName = entry.getKey();
			OkHttpClient.Builder clientBuilder = entry.getValue();
			// 應用代碼的OkHttpClientConfig.java中,okHttpClientBuilder方法上已經用了LoadBalanced注解,
			//所以下面這個if判斷為true
			if (applicationContext.findAnnotationOnBean(beanName, LoadBalanced.class) != null) {
				// 創建了OkHttpClient實體,傳給了這個Retrofit.Builder
				builder.client(clientBuilder.build());
				// 使用這個Retrofit.Builder去創建retrofit,相當于把上面創建的OkHttpClient實體帶給了retrofit
				// 所以,這個retrofit實體的底層就是OkHttpClient
				Retrofit retrofit = buildAndSave(context, builder);
				// type的型別是HelloService,
				// retrofit.create就是要創建一個實體,該實體實作了HelloService介面
				return retrofit.create(this.type);
			}
		}

		throw new IllegalStateException(
				"No Retrofit Client for loadBalancing defined. Did you forget to include spring-cloud-starter-square-okhttp?");
	}
  • 從上面的分析可見,咱們只寫HelloService介面不寫HelloService實作的關鍵就是retrofit.create方法,傳入了一個介面定義,就能回傳該介面的實作類的實體

  • 說實話retrofit.create的原始碼并不屬于spring-cloud-square,而是Retrofit自己的,在本文中看這段原始碼屬于超綱,但我還是忍不住要看一下:

  public <T> T create(final Class<T> service) {
  	// 一些檢查,例如service是不是介面
    validateServiceInterface(service);
    return (T)
    	// 這個實體是通過JDK的Proxy.newProxyInstance創建的
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];
				
			  // 業務應用執行HelloService的hello方法時,實際上執行的是下面的方法	
              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }
  • 至此終于真像大白,最侄訓是用Proxy.newProxyInstance生成了HelloService的代理類實體,作為HelloService.hello呼叫背后的真正實作

  • 最后似憾訓有一點疑問,就是HelloService的RetrofitClient注解的屬性是服務名provider,那么真正網路請求的時候,是如何轉成真實的地址和埠的呢?

  • 再回頭看看咱應用consumer-retrofit-okhttp的pom.xml檔案,如下圖紅框所示,和前文一樣,這里也使用了spring-cloud-square-okhttp,而且咱們寫的OkHttpClientConfig.java和前文也是一樣的,所以,根據服務名獲取地址和埠的操作依舊可以用前文的分析來解釋:

在這里插入圖片描述

  • 至于HelloService.hello方法,如何對應到web請求,請容我說一聲:這是retrofit和okhttp之間的事情,在這里算是超綱了,篇幅所限,實在展不開了...

尾記:關于另一種spring-cloud-square型別:retrofit + webflux

  • 之前的文章已經分析過,spring-cloud-square一共有三種型別,如下圖所示,兩個綠框中的原始碼都分析過了,還剩下的只有紅色的retrofit + webflux組合:

在這里插入圖片描述

  • 欣宸還要再寫一篇retrofit + webflux原始碼分析的文章?不不不,讀原始碼太累,寫出的文章,聰明的您讀起來也累,所以就此打住吧

  • 如果勤奮努力的您想獨立閱讀分析retrofit + webflux原始碼,這里給您一個建議,還記得本篇前面的那個類圖嗎,如下圖,使用retrofit + webflux的時候,會用到spring-cloud-square-retrofit-webclient.jar,這個jar里面也有OkHttpClientConfig注解,它的import會實體化下圖紅框中的類,這個類就是您閱讀原始碼的入口:

在這里插入圖片描述

  • 至此《spring-cloud-square學習》系列已經全部完成,希望這四篇文章可以幫助您全面掌握spring-cloud-square,在您的專案中對遠程呼叫的操作更加得心應手;

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中間件系列
  6. DevOps系列

歡迎關注公眾號:程式員欣宸

微信搜索「程式員欣宸」,我是欣宸,期待與您一同暢游Java世界...

https://github.com/zq2599/blog_demos

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/347264.html

標籤:其他

上一篇:Python代碼閱讀(第30篇):找到串列中的奇偶例外項

下一篇:Lambda-讓人又愛又恨的“->"

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more