>  기사  >  Java  >  Spring Cloud 소스 코드 분석: 1부

Spring Cloud 소스 코드 분석: 1부

Java后端技术全栈
Java后端技术全栈앞으로
2023-08-15 16:04:501146검색

소스 코드를 읽는 친구들을 비웃지 마세요. 요즘 인터뷰는 8부작 에세이에 불과하고 프로젝트 질문, 소스 코드 및 질문에 관한 것이 더 많습니다. 바퀴를 만드는 것 외에는 선택의 여지가 없습니다. 그렇지 않으면 매우 피곤하고 지루할 것입니다!

개인적으로 소스 코드를 읽기 위해서는 먼저 사용할 수 있어야 한다고 생각합니다. 일단 익숙해지면 다른 사람들이 어떻게 구현하는지 짐작할 수 있습니다. 관련 공식 문서가 있으면 공식 문서를 읽어보세요.

그러나 많은 공식 문서가 부실하게 작성되어 한동안 읽다가 혼란스러워지는 점이 안타깝습니다.

최근에 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">openfeign 소스 코드 당시 소스 코드에서 다음과 같은 주요 주석을 발견했습니다. @가져오기. 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);

프로젝트 시작 클래스: 🎜
/**
 * @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 {};
}
🎜그런 다음 feignclient인터페이스: 🎜
@Import(FeignClientsRegistrar.class)
🎜사용 사례: 🎜
/**
 * @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);
}
🎜위 코드에서 openfeign키 코드는 다음과 같습니다: 🎜

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

@FeignClient(contextId = "userFeignClient", value = " 요금-사용자-서비스")🎜

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()

@Import()注解是在spring 3.0版本中引入的,字面意义就是导入.

@Import注解的全类名是org.springframework.context.annotation.Import。其只有一个默认的value属性,该属性类型为Class6b3d0130bba23ae47fe2b8e8cddf0195[],表示可以传入一个或多个Class对象。

通过注释可以看出,该注解有如下作用:

可以导入一个或多个组件类(通常是@Configuration配置类)该注解的功能与Spring XML中的de3db5173ba162146856f74f89e5f81e元素相同。可以导入@Configuration配置类、ImportSelectImportBeanDefinitionRegistrar的实现类。

从spring 4.2版本开始,还可以引用常规组件类(普通类),该功能类似于AnnotationConfigApplicationContext.register()方法。

该注解可以在类中声明,也可以在元注解中声明。如果需要导入XML或其他非@Configuration定义的资源,可以使用@ImportResource

@Import() 주석은 Spring 버전 3.0에서 도입되었으며 문자 그대로의 의미는 import입니다.

🎜@Import 주석의 전체 클래스 이름은 org.springframework.context.annotation.Import. 클래스&lt ;?>[]는 하나 이상의 Class 객체가 전달될 수 있음을 나타냅니다. 🎜🎜주석에서 볼 수 있듯이 주석에는 다음과 같은 기능이 있습니다. 🎜🎜하나 이상의 구성 요소 클래스(일반적으로 @Configuration 구성 클래스)를 가져올 수 있습니다. 이 주석의 기능은 de3db5173ba162146856f74f89e5f81e 요소는 동일합니다. . @Configuration구성 클래스, ImportSelect 및 ImportBeanDefinitionRegistrar 구현 클래스. 🎜🎜spring 4.2부터는 일반 컴포넌트 클래스(일반 클래스)도 참조할 수 있습니다. 이 함수는 AnnotationConfigApplicationContext.register() 메서드. 🎜🎜이 주석은 클래스 또는 메타 주석에서 선언할 수 있습니다. XML 또는 기타 비, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96)를 가져와야 하는 경우 );">@Configuration 정의된 리소스의 경우 @ImportResource 주석 . 🎜🎜일반적으로 세 가지 사용 방법이 있습니다: 🎜
  • @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);
            }
        }
    }
}

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

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

  • 먼저 @EnableFeignClients 주석이 달린 All 주로 스캔 패키지 경로를 가져오기 위한 속성(basePackages); <code style='font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);'>@EnableFeignClients注解的所有属性,主要为了拿到扫描包路径(basePackages);
  • 因为一般不会在@EnableFeignClients注解中配置clients属性,所以会进入到clients属性为空时的逻辑;
  • 然后通过getScanner()方法获取扫描器:ClassPathScanningCandidateComponentProvider,并将上下文AnnotationConfigServletWebServerApplicationContext作为扫描器的ResourceLoader
  • 接着给扫描器ClassPathScanningCandidateComponentProvider添加一个注解过滤器(AnnotationTypeFilter),只过滤出包含@FeignClient注解的BeanDefinition
  • 再通过getBasePackages(metadata)方法获取@EnableFeingClients
  • 🎜 일반적으로 @EnableFeignClients는 주석에서 클라이언트 속성을 구성하여 클라이언트 속성이 비어 있을 때 논리를 입력하도록 합니다. 🎜🎜🎜🎜 그런 다음 getScanner() 메소드는 스캐너를 가져옵니다: ClassPathScanningCandidateComponentProvider 및 컨텍스트 AnnotationConfigServletWebServerApplicationContext스캐너로ResourceLoader;🎜🎜🎜🎜그런 다음 스캐너에ClassPathScanningCandidateComponentProvider@FeignClient 주석BeanDefinition; 🎜🎜🎜🎜 그런 다음 getBasePackages(metadata) 메소드는 @EnableFeingClients 주석에 지정된 패키지 스캐닝 경로 또는 스캐닝 클래스. 획득하지 못한 경우 시작 클래스가 있는 패키지 경로가 기본적으로 스캔됩니다. 🎜🎜
  • 그런 다음 핵심 논리를 입력합니다. scanner.findCandidateComponents( basePackage) 메서드는 패키지 경로 @FeignClient어셈블리를 위한 주석이 달고 검증된 인터페이스;
  • scanner.findCandidateComponents(basePackage)方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;
  • 最后将FeignClientConfigurationBeanDefinitionRegistry中注册一下,再对FeignClient做真正的注册操作。
  • 总结

    openfeign源码中的@Import注解在这里的作用就是将扫描到带有FeignClient

    마지막으로 FeignClientConfiguration BeanDefinitionRegistry에 등록한 다음 FeignClient는 실제 등록 작업을 수행합니다.

    요약

    Inopenfeign소스 코드의 @Import 주석은 여기에서 FeignClient 주석이 달린 모든 인터페이스 클래스는 Bean 형식으로 등록됩니다. 스프링 IOC 컨테이너.

    🎜 @Import 주석을 사용하는 방법을 다시 강조하겠습니다. 🎜
    • @Import공통 클래스 스프링 이 클래스는 스프링 컨테이너@Import一个普通类 spring会将该类加载到spring容器中
    • @Import一个类,该类实现了ImportBeanDefinitionRegistrar接口,在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry的注册器,能手工往beanDefinitionMap中注册 beanDefinition
    • @Import一个类 该类实现了ImportSelector 重写selectImports
    @Import ImportBeanDefinitionRegistrar code> 인터페이스, 다시 작성된 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px; background-color: rgba (27, 31) , 35, 0.05);글꼴 계열: " operator mono consolas monaco menlo monospace break-all rgb>registerBeanDefinitionsIn 이 방법을 사용하면 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px; background-color: rgba(27 , 31, 35, 0.05);font-family: " operator mono consolas monaco menlo monospace break-all rgb>BeanDefinitionRegistryregister, beanDefinitionMap 등록beanDefinition

    @Import ImportSelector 다시 쓰기selectImports 메소드 이 메소드는 String[] 배열의 객체를 반환하고 배열의 클래스는 스프링 컨테이너에 주입됩니다. .

    알겠습니다. 오늘은 여기까지입니다. 이 오픈페인에는 흥미로운 것들이 많이 있습니다. 다음에 그것들을 공유하겠습니다! 🎜🎜🎜🎜🎜🎜🎜

    위 내용은 Spring Cloud 소스 코드 분석: 1부의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 Java后端技术全栈에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제