Home  >  Article  >  Java  >  Spring Cloud source code analysis: Part 1

Spring Cloud source code analysis: Part 1

Java后端技术全栈
Java后端技术全栈forward
2023-08-15 16:04:501146browse

Don’t laugh at friends who read the source code. Nowadays, interviews are only about eight-part essays and are unreliable. They are more about asking project questions, source code and questions. I have no choice but to make wheels, otherwise it would be very tiring and boring!

Personally, I think the prerequisite for reading the source code is that you must be able to use it. Once you are familiar with it, you can guess how others implemented it. If there are relevant official documents, then read the official documents.

However, it is a pity that many official documents are poorly written, leaving you confused after reading them for a while.

When I was studying the openfeign source code recently, I found a key annotation in the source code: @Import.

Project startup class:

/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年07月07日 16:47
 * 在线刷题 1200+题和1000+篇干货文章:<a href="http://woaijava.cc/">博客地址</a>
 */
@EnableFeignClients(basePackages = {"com.tian.feign"})
@SpringBootApplication()
public class    MqApplication {
    public static void main(String[] args) {
        SpringApplication.run(MqApplication.class, args);
    }
}

Then, there is our feignclientInterface:

/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年07月07日 16:47
 * 在线刷题 1200+题和1000+篇干货文章:<a href="http://woaijava.cc/">博客地址</a>
 */
@FeignClient(contextId = "userFeignClient", value = "charge-user-service")
public interface UserFeignClient {

    /**
     * 邀请成功增加收益
     *
     * @param invitedDto 邀请增加收益
     * @return 邀请成功
     */
    @PostMapping("/user/invited/register")
    CommonResult<Boolean> invitedRegister(@RequestBody InvitedDto invitedDto);
}

Use case:

/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年07月07日 16:47
 * 在线刷题 1200+题和1000+篇干货文章:<a href="http://woaijava.cc/">博客地址</a>
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    UserFeignClient userFeignClient;

    @PostMapping("/invited")
    public CommonResult invitedRegister(){
        //省略不想搞的代码
        return userFeignClient.invitedRegister(invitedDto);
    }

}

From above In the code, we can see that the key codes of openfeign are:

@EnableFeignClients(basePackages = {"com.tian.feign"})

@FeignClient(contextId = "userFeignClient", value = "charge-user-service")

##userFeignClient.invitedRegister(invitedDto);

@EnableFeignClients

@EnableFeignClients这个注解在启动类上,我们肯定要重点关注。

小技巧:凡是以@Enable开头的各种注解基本上都是开启xxxx。比如:@EnableFeignClients表示开启feign客户端。

我们进入@EnableFeignClients

/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年07月07日 16:47
 * 在线刷题 1200+题和1000+篇干货文章:<a href="http://woaijava.cc/">博客地址</a>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients { 
 String[] value() default {}; 
 String[] basePackages() default {}; 
 Class<?>[] basePackageClasses() default {}; 
 Class<?>[] defaultConfiguration() default {}; 
 Class<?>[] clients() default {};
}

我们通常只需要关心属性basePackages,表示我们需要扫描的包目录。

如果既没有指定basePackages,也没有指定basePackageClasses,则采用启动类所在的目录作为包扫描路径。默认是这种情况。

本文重点来了,在这个注解@EnableFeignClients上有个注解@Import(FeignClientsRegistrar.class),这里到底是有什么作用?

@Import()

@Import()The annotation was introduced in spring version 3.0 , the literal meaning is import.

@ImportThe full class name of the annotation is org.springframework.context.annotation.Import. It has only one default value attribute, which is of type Class6b3d0130bba23ae47fe2b8e8cddf0195[], indicating that one or more Class objects can be passed in.

As can be seen from the annotation, the annotation has the following functions:

You can import one or more component classes (usually the @Configuration configuration class). The function of this annotation is the same as # in Spring XML. ##de3db5173ba162146856f74f89e5f81eThe elements are the same. You can import the @Configuration configuration class, ImportSelect and ImportBeanDefinitionRegistrar implementation classes.

Starting from spring 4.2, you can also reference regular component classes (ordinary classes). This function is similar to the

AnnotationConfigApplicationContext.register() method.

This annotation can be declared in the class or in the meta-annotation. If you need to import XML or other resources not defined by

@Configuration, you can use the @ImportResource annotation.

There are usually three ways to use it:

  • @Import一个普通类 spring会将该类加载到spring容器中
  • @Import一个类,该类实现了ImportBeanDefinitionRegistrar接口,在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry的注册器,能手工往beanDefinitionMap中注册 beanDefinition
  • @Import一个类 该类实现了ImportSelector 重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中。

下面我们来聊聊@Import在openfeign的这里是起到什么作用。

openfeign中作用

回答上面的代码里

@Import(FeignClientsRegistrar.class)

这里导入的是FeignClientsRegistrar类,我们再来看看他的类关系图:

Spring Cloud source code analysis: Part 1

从类关系图来看,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口。再结合@Import的三种使用方式中的第二种方式,能手工往beanDefinitionMap中注册 beanDefinition

/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年07月07日 16:47
 * 在线刷题 1200+题和1000+篇干货文章:<a href="http://woaijava.cc/">博客地址</a>
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    registerDefaultConfiguration(metadata, registry);
    registerFeignClients(metadata, registry);
}

这个方法registerBeanDefinitions()是feign的核心入口方法,其中会做两件事:

注册默认的配置和注册所有的FeignClient。

registerDefaultConfiguration(metadata, registry)

这个方法是负责注册OpenFeign的默认配置 ,逻辑相对简单:

private void registerDefaultConfiguration(AnnotationMetadata metadata,
                                          BeanDefinitionRegistry registry) {
    //获取@EnableFeignClients的全部属性
    //@EnableFeignClients(basePackages = {"com.tian.feign"}) 
    //这里的basePackages就是我们指定的熟悉
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            name = "default." + metadata.getClassName();
        }
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

defaultAttrs中内容如下:

Spring Cloud source code analysis: Part 1

但是这里我们只关注defaultConfiguration,我们并有对其进行设置,所以我们可以忽略他。重点是下面这个方法。

registerFeignClients(metadata, registry)

这里就是项目启动时和openfeign相关的核心代码,这也是@EnableFeignClients@FeignClient两个注解关联起来的地方。

我们进入源码中:

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

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.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) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                              "@FeignClient can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));

                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

代码一行一行看是不是觉得很累,我给你总结好了。

上面的方法分为以下七个步骤:

  • First get all the attributes of the @EnableFeignClients annotation, mainly to get the scanning package path (basePackages);
  • Because the clients attribute is generally not configured in the @EnableFeignClients annotation, it will enter the logic when the clients attribute is empty;
  • Then pass getScanner() method gets the scanner: ClassPathScanningCandidateComponentProvider, and uses the context AnnotationConfigServletWebServerApplicationContext as the scanner's ResourceLoader;
  • Then add an annotation filter (AnnotationTypeFilter) to the scanner ClassPathScanningCandidateComponentProvider, and only filter out BeanDefinition containing the @FeignClient annotation;
  • Then use the getBasePackages(metadata) method to obtain the specified package scanning path or scanning class in the @EnableFeingClients annotation; if not obtained, it will be scanned by default The package path where the startup class is located;
  • Then enter the core logic: use the scanner.findCandidateComponents(basePackage) method to scan out all interfaces annotated with @FeignClient and meet the conditions for assembly from the package path ;
  • ##Finally, register
    FeignClientConfiguration in BeanDefinitionRegistry, and then do the actual registration operation for FeignClient.

Summary

The @Import annotation in the

openfeign source code is used here. All interface classes scanned with FeignClient annotations will be registered in the spring IOC container in the form of beans.

Let’s emphasize how to use @Import annotation:

  • @ImportA common class spring will load the class into the spring container
  • @Import A class that implements the ImportBeanDefinitionRegistrar interface. In the overridden registerBeanDefinitions method, you can get the BeanDefinitionRegistry register and manually add it to Register in beanDefinitionMapbeanDefinition
  • @ImportA class that implements ImportSelector OverrideselectImportsMethod This method returns the object of the String[] array, and the classes in the array will be injected into the spring container.

Okay, that’s all I’ll share today. There are many interesting things in this openfeign, we will share them next time!

The above is the detailed content of Spring Cloud source code analysis: Part 1. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:Java后端技术全栈. If there is any infringement, please contact admin@php.cn delete