Quick reading of spring cloud square source code (retrofit + okhttp)

Keywords: cloud computing

Welcome to my GitHub

Here we classify and summarize all the original works of Xinchen (including supporting source code): https://github.com/zq2599/blog_demos

Spring cloud square series articles

  1. Five minutes to understand spring cloud square
  2. Spring cloud square development practice (full coverage of three types)
  3. Quick reading of spring cloud square source code (spring cloud square okhttp)
  4. Quick reading of spring cloud square source code (retrofit + okhttp)

Overview of this article

  • This article is the end of the spring cloud square learning series, Last We have learned about the source code and principle of the spring cloud square okhttp library. Today, let's make it a little more difficult. Let's take a look at another type of source code of spring cloud Square: < font color = "blue" > spring cloud square retrofit < / font >, that is, the one in the red box below:

Source code analysis objectives

  • Next, start to analyze the source code of the spring cloud square retro fit project, as shown in the red box below:

  • The goal of this article is very clear. Only one thing is clear: when using spring cloud square, take the consumer retrofit okhttp subproject as an example, why do we only write the HelloService interface, but can use the HelloService implementation class through Autowired annotation?

Advance summary

  • If you want to understand the principle of the retro fit part of spring cloud square, but you don't have time for in-depth research, you can take a look at the following summary in advance:
  1. The operation of the whole mechanism can be divided into four relatively independent parts: business application coding uses spring cloud square related annotations, the bean factory is registered to the spring environment, the bean factory class is instantiated in the spring environment, and the implementation class of HelloService interface is produced in spring through the factory instance
  2. According to the above analysis, the most important thing should be the factory class of the bean: < font color = "blue" > retrofitclientfactorybean < / font >, which implements the FactoryBean interface. Its getObject method generates the implementation class and key according to the HelloService interface. Finally, it will call the Retrofit.create method in the red box in the following figure to create an instance:

  1. The retro fit class is not a spring cloud project, but comes from the retro fit library. Its create method uses the JDK Proxy.newProxyInstance method, which can generate an instance that implements the interface according to the HelloService interface:

  1. When using the retro fit + okhttp scheme of spring cloud square, the HelloService interface still uses the service name of the remote service instead of the address and port. This is because the spring cloud square okhttp library is used, so the logic of changing the service name to < font color = "red" > address + port < / font > is the same as the above Quick reading of spring cloud square source code (spring cloud square okhttp) bring into correspondence with
  • The above is the conclusion of the whole source code analysis. I will sort out the related code process into a diagram, as shown below:

Review how the application uses spring cloud square retro fit

  • Before analyzing the source code, review it Spring cloud square development practice How did we use < font color = "blue" > spring cloud square retrofit < / font > (corresponding to the consumer retrofit okhttp subproject in the demo)
  1. Create a new configuration class OkHttpClientConfig, use the EnableRetrofitClients annotation, and register the OkHttpClient.Builder instance with the spring environment:
@Configuration
@EnableRetrofitClients
class OkHttpClientConfig{
    @Bean
    @LoadBalanced
    public OkHttpClient.Builder okHttpClientBuilder() {
        return new OkHttpClient.Builder();
    }
}
  1. Define the HelloService interface and modify it with the annotation RetrofitClient. The annotation value is the service name of the remote call < font color = "blue" > < / font >, in which the hello method is declared. Modify it with the annotation GET. The annotation value is the path of the remote call interface:
@RetrofitClient("provider")
public interface HelloService {
    @GET("/hello-obj")
    Call<HelloResponse> hello(@Query("name") String name);
}
  1. When the business needs to make a remote call, modify the HelloService interface with the Autowired annotation to call the HelloService.hello method. The developer does not need to pay attention to where the corresponding instance of the interface comes from:
@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();
    }
}
  • The above are the key operations of using spring cloud square when developing business code. Next, we will analyze the role of these operations from the perspective of source code

Source code analysis (class definition registration phase)

  • Recall the OkHttpClientConfig.java we wrote, which uses the annotation < font color = "blue" > enableretrofitclients < / font >, which is the entrance to this code reading:
@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 {};
}
  • From the above code, it can be seen that both RetrofitConfiguration and retrofitclientregistrar will be instantiated. RetrofitConfiguration is too simple to read. Focus on retrofitclientregistrar. First look at its class diagram to understand inheritance and implementation
  • As shown in the following figure, retrofitclientregister is integrated from abstractretrofitclientregister, and abstractretrofitclientregister is integrated from importbeandefinitionregister

  • Therefore, when retrofitclientsregister is instantiated, the implementation class of ImportBeanDefinitionRegistrar interface is instantiated. I believe that students familiar with spring will not be unfamiliar with it. It is used to dynamically register beans, Then, the next focus is on the specific contents of the registerBeanDefinitions method of ImportBeanDefinitionRegistrar to see what beans are registered
  • The code of registerBeanDefinitions method is in abstractretrofitclientregister.java (please find the location of abstractretrofitclientregister in the class diagram above), as shown below. Since EnableRetrofitClients modifies OkHttpClientConfig.java we created, the following input parameter AnnotationMetadata is the annotation information of OkHttpClientConfig class:
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerRetrofitClients(metadata, registry);
    }
  • The first method of the above code, registerDefaultConfiguration, is to register configuration information. It is not important. Skip
  • The second method of the above code, registerRetrofitClients, is the key of this article. Please pay attention to the Chinese Notes in the following code:
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());
        // Filter condition: the class modified by RetrofitClient annotation corresponds to HelloService.java in our code
        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) {
                // The result is the HelloService interface
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(),
                            "@RetrofitClient can only be specified on an interface");

                    // Gets all properties that modify the RetrofitClient annotation of the HelloService class
                    Map<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(RetrofitClient.class.getCanonicalName());
                    // According to these properties, the service name for remote access is provider
                    String name = getClientName(attributes);
                    // Register a configuration class called provider.RetrofitClientSpecification in spring,
                    // Since the RetrofitClient annotation that modifies HelloService has no attributes, this configuration class has little content
                    registerClientConfiguration(registry, name, attributes.get("configuration"));
                    
                    // This method should focus on,
                    // The input parameter annotationMetadata is the meta information of HelloService,
                    // Attributes are all attributes that modify the RetrofitClient annotation of the HelloService class
                    registerRetrofitClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
  • The registerRetrofitClient method that was invoked in the above code is expanded as follows. This code has done a very important thing: register BeanDefinition to spring, registered name is equal to <font color= "blue" >com.bolingcavalry.consumer.service.HelloService</font>, the beanClass of the corresponding BeanDefinition is equal to the <font BeanDefinition.
private void registerRetrofitClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
            Map<String, Object> attributes) {
        // Since the annotation modifies the HelloService class, the className here is equal to com.bolingcavalry.consumer.service.HelloService
        String className = annotationMetadata.getClassName();
        // Note that the getFactoryBeanClass() method comes from the retrofitclientregistrar class, and the return value is RetrofitClientFactoryBean.class,
        // Therefore, the RetrofitClientFactoryBean is brought into the definition,
        // Note that the type of this definition variable is BeanDefinitionBuilder,
        // There is a member variable beanDefinition inside. At this time, the beanClass field of the member variable has been set to RetrofitClientFactoryBean.class
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(getFactoryBeanClass());
        validate(attributes);
        // The RetrofitClient annotation of HelloService does not set the url property, so this is an empty string
        definition.addPropertyValue("url", getUrl(attributes));
        // The value attribute of the RetrofitClient annotation is configured as the remote service name, here is the provider
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        // The type is HelloService
        definition.addPropertyValue("type", className);
        // by_type, which means that when the autowire annotation modifies HelloService, HelloService can be used to obtain the corresponding implementation class
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        String alias = name + "RetrofitClient";
        
        // beanDefinition is obtained through BeanDefinitionBuilder,
        // The bean class field of this bean definition has been set to RetrofitClientFactoryBean.class
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        
        beanDefinition.setPrimary(true);

        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }
        
        // Put the two parameters beanName and beandefinition required to register beandefinition into the BeanDefinitionHolder object
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
        // Complete the registration of BeanDefinition in the spring environment. name equals com.bolingcavalry.consumer.service.HelloService, and the beanClass of the corresponding BeanDefinition equals retrofitclientfactorybean (note that this is very important)
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
  • At this moment, the class definition of HelloService has been registered in spring. Next, it depends on where the implementation class of HelloService interface comes from;

RetrofitClientFactoryBean in BeanDefinition is instantiated

  • During spring initialization, the code in the above red box will trigger the spring environment to instantiate the HelloService interface implementation class. The complete triggering process and detailed stack will not be described in detail. These are spring's standard processing processes. Next, we will pick the key points
  • The first is the famous SpringApplication.refresh method, which is the bean instantiation logic. It will execute an important method, DefaultListableBeanFactory.doGetBeanNamesForType. It will traverse all registered beandefinitions and process them one by one, as shown in the following figure:

  • DefaultListableBeanFactory.doGetBeanNamesForType continues to execute, and the next key point will be: create beans according to BeanDefinition, and the stack is as follows, which is obtained by conditional breakpoints:
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)
  • According to the above stack, to take a closer look at the doGetBean method of AbstractBeanFactory, please pay attention to the Chinese Notes:
protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        // Input parameter name equals "com.bolingcavalry.consumer.service.HelloService"
        String beanName = transformedBeanName(name);
        Object beanInstance;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        // sharedInstance is equal to null, so the following if judgment is not true
        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 is equal to null, so the following if judgment is not true
            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 is equal to true, so the following if judgment is not true
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
                    .tag("beanName", name);
            try {
                if (requiredType != null) {
                    beanCreation.tag("beanType", requiredType::toString);
                }
                // As we analyzed earlier, BeanDefinition has been registered in the spring environment,
                // Call getMergedLocalBeanDefinition here to get this BeanDefinition
                RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                // There is no dependency on the BeanDefinition of HelloService,
                // Therefore, dependsOn is equal to null, and the following if does not hold
                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.
                // The HelloService bean is a singleton, so the following if judgment holds
                if (mbd.isSingleton()) {
                    // Here is the key to creating bean s!!!
                    // getSingleton passes in a lambda expression, which will be called in the method,
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            // Create beans according to BeanDefinition,
                            // What is actually executed is the AbstractAutowireCapableBeanFactory.createBean method
                            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);
    }
  • So far, the RetrofitClientFactoryBean has been instantiated. Next, let's see how the bean behind the HelloService interface is created

How is the bean corresponding to HelloService created

  • Recall the scenario where HelloService is used in our application code, as shown in the red box in the following figure. Use Autowired annotation to modify HelloService:

  • Firstly, the RemoteHello in the figure above will definitely create beans. During the creation process, the DefaultListableBeanFactory.doResolveDependency method is responsible for processing the beans that RemoteHello depends on, as shown in the figure below. Here, the instantiation of the HelloService bean is triggered

  • After tossing and turning, I came to the AbstractBeanFactory.doGetBean method again. This time, I will execute the getObjectForBeanInstance method in the second red box in the figure below:

  • Then it comes to the most critical position: AbstractBeanFactory.getObjectForBeanInstance method, which uses RetrofitClientFactoryBean as a factory to produce HelloService:

  • Continue to expand the getObjectFromFactoryBean method in the red box 2 above and enter the FactoryBeanRegistrySupport.doGetObjectFromFactoryBean method. Here, the transition from spring framework to application customization is completed: the creation of beans is handled by the Factory registered by the application itself:

  • In RetrofitClientFactoryBean.getObject, execute loadBalance(builder, context, serviceIdUrl):

  • loadBalance is implemented in RetrofitClientFactoryBean:
    protected Object loadBalance(Retrofit.Builder builder, RetrofitContext context, String serviceIdUrl) {
        // In OkHttpClientConfig.java of the application code, the okHttpClientBuilder method generates an OkHttpClient.Builder instance, which is in the instances here
        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();
            // In OkHttpClientConfig.java of the application code, the LoadBalanced annotation has been used on the okHttpClientBuilder method,
            //So the following if is judged to be true
            if (applicationContext.findAnnotationOnBean(beanName, LoadBalanced.class) != null) {
                // The OkHttpClient instance is created and passed to the Retrofit.Builder
                builder.client(clientBuilder.build());
                // Using this Retrofit.Builder to create a retrofit is equivalent to bringing the OkHttpClient instance created above to retrofit
                // Therefore, the underlying layer of this retrofit instance is OkHttpClient
                Retrofit retrofit = buildAndSave(context, builder);
                // The type of type is HelloService,
                // retrofit.create is to create an instance that implements the HelloService interface
                return retrofit.create(this.type);
            }
        }

        throw new IllegalStateException(
                "No Retrofit Client for loadBalancing defined. Did you forget to include spring-cloud-starter-square-okhttp?");
    }
  • It can be seen from the above analysis that we only write the HelloService interface, not the HelloService implementation. The key is the retrofit.create method. If an interface definition is passed in, we can return the instance of the implementation class of the interface
  • To tell you the truth, the source code of retrofit.create does not belong to spring cloud square, but to Retrofit itself. In this article, I see that this source code belongs to a super outline, but I still can't help but look at it:
  public <T> T create(final Class<T> service) {
      // Some checks, such as whether the service is an interface
    validateServiceInterface(service);
    return (T)
        // This instance is created through Proxy.newProxyInstance of JDK
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];
                
              // When the business application executes the hello method of HelloService, it actually executes the following methods    
              @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);
              }
            });
  }
  • So far, it's really clear that the proxy class instance of HelloService is finally generated with Proxy.newProxyInstance as the real implementation behind the HelloService.hello call
  • Finally, there seems to be a little doubt that the attribute of the RetrofitClient annotation of HelloService is the service name provider, so how to convert the real network request to the real address and port?
  • Let's look back at the pom.xml file of consumer retrofit okhttp, as shown in the red box in the figure below. Like the previous text, spring cloud square okhttp is also used here, and OkHttpClientConfig.java is the same as the previous text. Therefore, the operation of obtaining address and port according to service name can still be explained by the previous analysis:

  • As for the HelloService.hello method, how to correspond to web requests, please allow me to say: This is between retrofit and okhttp. It is beyond the outline here. Due to space constraints, it can't be expanded

Postscript: about another spring cloud square type: retrofit + weblux

  • As analyzed in previous articles, there are three types of spring cloud square, as shown in the figure below. The source code in the two green boxes has been analyzed, and the only remaining is the red < font color = "blue" > retrofit + weblux < / font >:

  • Xinchen also wants to write another article on < font color = "blue" > retrofit + weblux < / font > source code analysis? No, no, no, no, it's too tired to read the source code. You're too tired to read the articles you write. So stop here
  • If you work hard and want to read and analyze the < font color = "blue" > retrofit + weblux < / font > source code independently, here's a suggestion. Remember the class diagram in front of this article, as shown in the following figure. When using < font color = "blue" > retrofit + weblux < / font >, you will use spring-cloud-square-retrofit-webclient.jar, which also has OkHttpClientConfig annotation, Its import instantiates the class in the red box below. This class is the entry to read the source code:

  • So far, the spring cloud square learning series has been completed. I hope these four articles can help you fully master spring cloud square and be more comfortable with remote call operation in your project;

You're not alone. Xinchen's original accompanies you all the way

  1. Java series
  2. Spring collection
  3. Docker series
  4. kubernetes series
  5. Database + middleware series
  6. DevOps series

Welcome to the official account: programmer Xin Chen

Wechat search "programmer Xinchen", I'm Xinchen, looking forward to traveling with you in the Java World

https://github.com/zq2599/blog_demos

Posted by ohjay on Wed, 03 Nov 2021 16:45:08 -0700