ホームページ  >  記事  >  Java  >  Spring Cloud のソースコード分析: パート 1

Spring Cloud のソースコード分析: パート 1

Java后端技术全栈
Java后端技术全栈転載
2023-08-15 16:04:501088ブラウズ

ソース コードを読んでいる友人を笑わないでください。最近のインタビューは 8 部構成のエッセイ程度であり、信頼性がありません。インタビューでは、プロジェクト、ソース コード、その他の質問をすることが目的です。 。ホイールを作るしかありません。そうしないととても疲れて退屈になってしまいます。

個人的には、ソースコードを読むための前提条件は、それを使用できることであると思います。一度慣れてしまえば、他の人がどのように実装しているかを推測できるようになります。関連する公式があれば、ドキュメントを読んでから公式ドキュメントを読んでください。

しかし、多くの公式文書の書き方が悪く、しばらく読むと混乱してしまうのが残念です。

最近、openfeign ソース コードを研究していたときに、ソース コード内に重要な注釈 @Import を見つけました。

プロジェクト起動クラス:

/**
 * @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);
    }
}

次に、feignclientインターフェース:

/**
 * @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);
}

ユースケース:

/**
 * @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);
    }

}

上記よりコードでは、openfeign のキー コードが次のとおりであることがわかります:

@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()アノテーションは Spring バージョン 3.0 で導入されました。リテラルの意味は import.

@Import アノテーションの完全なクラス名は org.springframework.context.annotation.Import です。これには Class6b3d0130bba23ae47fe2b8e8cddf0195[] 型のデフォルト値属性が 1 つだけあり、1 つ以上の Class オブジェクトを渡すことができることを示します。

アノテーションからわかるように、アノテーションには次の機能があります:

1 つ以上のコンポーネント クラス (通常は @Configuration 構成クラス) をインポートできます。このアノテーションの機能は次のとおりです。 Spring XML の # と同じ ##de3db5173ba162146856f74f89e5f81e要素は同じです。 @Configuration 構成クラス、ImportSelect および ImportBeanDefinitionRegistrar 実装クラスをインポートできます。

Spring 4.2 からは、通常のコンポーネント クラス (通常のクラス) も参照できるようになりました。この関数は、

AnnotationConfigApplicationContext.register() メソッドに似ています。

このアノテーションは、クラスまたはメタアノテーションで宣言できます。

@Configuration で定義されていない XML またはその他のリソースをインポートする必要がある場合は、@ImportResource アノテーションを使用できます。

通常、使用方法は 3 つあります:

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

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

openfeign中作用

回答上面的代码里

@Import(FeignClientsRegistrar.class)

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

Spring Cloud のソースコード分析: パート 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 のソースコード分析: パート 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);
            }
        }
    }
}

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

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

  • まず、主にスキャン パッケージ パス (basePackages) を取得するために、@EnableFeignClients アノテーションのすべての属性を取得します。
    ##clients 属性は通常、
  • @EnableFeignClients
  • アノテーションでは構成されないため、clients 属性が空のときにロジックに入ります。 pass
    getScanner() メソッドはスキャナー ClassPathScanningCandidateComponentProvider
    を取得し、コンテキスト
  • AnnotationConfigServletWebServerApplicationContext
  • をスキャナーの
    ResourceLoader;## として使用します。 #次に、スキャナー ClassPathScanningCandidateComponentProvider にアノテーション フィルター (AnnotationTypeFilter) を追加し、@FeignClient アノテーションを含む
    BeanDefinition
  • のみをフィルターで除外します。
  • ##次に、getBasePackages(metadata) メソッドを使用して、指定されたパッケージのスキャン パスまたは @EnableFeingClients アノテーション内のスキャン クラスを取得します (取得できない場合)。 、デフォルトでスキャンされます スタートアップ クラスが配置されているパッケージ パス;
  • 次に、コア ロジックに入ります。scanner.findCandidateComponents(basePackage) メソッドを使用して、@FeignClient の注釈が付けられたすべてのインターフェイスをスキャンし、アセンブリの条件を満たします。パッケージ パス ;
  • ##最後に、
    FeignClientConfigurationBeanDefinitionRegistry に登録し、FeignClient の実際の登録操作を実行します。

概要

ソース コード

openfeign の @Import アノテーションがここで使用されます。 FeignClient アノテーションでスキャンされたすべてのインターフェイス クラスは、Bean の形式で Spring IOC コンテナに登録されます。

@Import アノテーションの使用方法を強調しましょう:

  • @Import共通クラス スプリングはクラスをスプリング コンテナにロードします
  • @Import A ImportBeanDefinitionRegistrar インターフェースを実装するクラス。オーバーライドされた registerBeanDefinitions メソッドで、BeanDefinitionRegistry レジスタを取得し、それを beanDefinitionMap の Register に手動で追加できます。 #beanDefinition
  • @Import#ImportSelector OverrideselectImportsMethod このメソッドを実装するクラスString[] 配列のオブジェクトを返し、配列内のクラスが Spring コンテナーに挿入されます。
  • わかりました、今日共有するのはこれですべてです。この Openfeign には興味深いものがたくさんあるので、次回はそれを共有します。

以上がSpring Cloud のソースコード分析: パート 1の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はJava后端技术全栈で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。