自学和再看rpc之通过注解注册消费


前言

傻逼guide一笔带过,我自己学得自己总结

这里讲的主要是通过注解注册/消费服务

然后有的springboot的知识呢,学八股的时候再学习

自定义注解

使用注解开发,而不是xml配置文件,好处是便捷性,操作性。
比如@Builder主要作用是用来生成对象,并能够进行链式赋值。

所以我们这里定义了两个注解

在annotation包里

@RpcService放在服务实现类上。
@RpcReference放在服务引用上。

服务端发送的数据类型是:RpcMessage<RpcReponse>。其中Object是为了包含服务的各种返回类型。
客户端发送的数据类型是:RpcMessage<RpcRequest>。

@RpcService

注册服务

/**
 * RPC service annotation, marked on the service implementation class
 *
 * @author shuang.kou
 * @createTime 2020年07月21日 13:11:00
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface RpcService {

    /**
     * Service version, default value is empty string
     */
    String version() default "";

    /**
     * Service group, default value is empty string
     */
    String group() default "";

}

@Documented:该注解表示带有该类型的注解应该由javadoc和类似工具记录。换句话说,这个注解应该包含在注释元素的文档中。

@Retention(RetentionPolicy.RUNTIME):这表示该注解应在运行时保留。如果要使用反射在运行时访问注解,则这是必需的。

@Target({ElementType.TYPE}):此注解仅允许在类声明上使用。

@Inherited:这表示该注解类型可以从超类继承。

@RpcReference

消费服务

  1. @RpcReference(version = “version1”, group = “test1”) private HelloService helloService; 该注解标记到成员变量上,然后对整个类使用@Component来注入到Spring中
  2. 实现BeanPostProcessor接口,实现里面的postProcessAfterInitialization方法,在Bean被初始化之后处理服务引用,生成代理对象
  3. 遍历Bean的成员变量,如果有@RpcReference则通过注解值获取RpcServiceConfig,然后传入RpcClient和RpcServiceConfig初始化RpcClientProxy。
  4. 通过代理类获取接口的代理对象,使用反射将Controller里的对象替换为我们的代理对象,再调用方法时,会调用到我们的invoke函数里,来实现发送request收到response的目的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Inherited
public @interface RpcReference {

    /**
     * Service version, default value is empty string
     */
    String version() default "";

    /**
     * Service group, default value is empty string
     */
    String group() default "";

}

@RpcScan

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomScannerRegistrar.class)
@Documented
public @interface RpcScan {

    String[] basePackage();//指定要扫描的包路径,对应CustomScannerRegistrar里的BASE_PACKAGE_ATTRIBUTE_NAME

}

通过@Import注解导入了CustomScannerRegistrar.class,这可能是一个配置类,用于处理自定义的注解扫描逻辑。

一些基本概念

我他妈发现我一些基本概念都不懂啊

注册Bean

“注册bean”通常是指将一个Java对象(被称为bean)注册到Spring容器中,以便由Spring进行管理。Spring容器是一个负责创建、配置和管理bean的容器。通过注册bean,我们告诉Spring框架如何实例化、配置和管理这些对象。

注册了之后能干什么?

  1. 实例化和管理: Spring负责实例化bean,并在需要时管理其生命周期。它会根据配置或注解创建bean的实例,并在需要时销毁它们。
  2. 依赖注入: Spring负责将bean之间的依赖关系注入到它们中。通过配置或注解,可以将其他bean引用注入到一个bean中,实现了松散耦合。
  3. AOP(面向切面编程): Spring提供了强大的AOP支持,允许在应用程序中方便地使用切面来处理横切关注点,例如事务管理、日志记录等。
  4. 声明性事务管理: 通过配置或注解,可以声明性地管理事务,而无需在代码中编写显式的事务管理代码。
  5. Spring Boot自动配置: 在Spring Boot应用程序中,注册的bean可以利用自动配置机制,使得开发者能够更轻松地构建和部署应用程序。
  6. 组件扫描和自动装配: Spring容器可以扫描指定的包,自动发现标有特定注解的组件,并将它们自动装配到应用程序中。
  7. 事件和监听器: Spring框架提供了事件和监听器机制,通过它们,bean可以发布和监听事件,实现模块之间的松散耦合通信。
  8. 面向切面编程(AOP): Spring允许通过AOP在应用程序中定义横切关注点,例如日志、事务管理等。

Bean的实例化

简单来说就是创建一个对象

复杂来说就是

  1. Bean 的定义: 在 Spring 配置文件(如 XML 文件)、Java 类上的注解或者 Java 代码中定义 Bean。这个定义包含了 Bean 的类型、属性、依赖关系等信息。
  2. Spring 容器加载配置: Spring 容器读取配置文件或者扫描注解,解析配置信息,将 Bean 的定义信息加载到容器中。
  3. Bean 的实例化: 根据 Bean 的定义信息,Spring 容器使用适当的方式(通常是反射)实例化 Bean 对象。
  4. 属性注入: 如果 Bean 的定义中包含属性信息,Spring 容器会将配置的属性值注入到 Bean 的对应属性中。
  5. 初始化方法: 如果 Bean 的定义中有指定初始化方法,Spring 容器在实例化完成后调用该方法进行初始化。
  6. Bean 的使用: 完成初始化后,Bean 就可以被其他对象引用和使用了。
  7. 销毁方法(可选): 如果 Bean 的定义中有指定销毁方法,当容器关闭时,会调用该方法进行资源释放和清理。

Spring启动时注解执行顺序

  1. 加载配置类: 在启动时,Spring 会加载配置类,这包括通过 @Configuration 标记的配置类、通过 @ComponentScan 扫描的组件、通过 @Import 导入的其他配置类等。
  2. 解析注解: 在加载配置类的过程中,Spring 会解析各种注解,包括 @Bean@Component@Autowired 等。在这个过程中,如果遇到了 @Import 注解,就会触发被导入配置类的解析和注册。
  3. 初始化 ApplicationContext: 随后,Spring 会初始化 ApplicationContext(应用上下文)。在这一阶段,它会实例化 bean,完成依赖注入,执行生命周期方法,以及执行其他与 bean 相关的初始化工作。
  4. 执行自定义逻辑: 在初始化 ApplicationContext 过程中,如果遇到实现了 ImportBeanDefinitionRegistrar 接口的类,Spring 会调用这些类的 registerBeanDefinitions 方法,执行自定义的逻辑,比如注册额外的 bean。

元数据

在这里,元数据指的是有关代码元素(如类、方法、字段等)的信息。Java提供了AnnotationMetadata接口,该接口用于获取与注解相关的元数据信息,就是用来获取注解里面的内容的

CustomScanner

一个自定义扫描器,用于扫描指定包下的类,并根据给定的注解类型进行过滤。

public class CustomScanner extends ClassPathBeanDefinitionScanner {

    public CustomScanner(BeanDefinitionRegistry registry, Class<? extends Annotation> annoType) {
        super(registry);
        super.addIncludeFilter(new AnnotationTypeFilter(annoType));
    }

    @Override
    public int scan(String... basePackages) {
        return super.scan(basePackages);
    }
}

构造函数

BeanDefinitionRegistry是Spring框架中用于注册bean定义的接口。Class<? extends Annotation>是指定的注解类型。

在构造函数中,调用了super(registry)来调用父类的构造函数,并使用super.addIncludeFilter(new AnnotationTypeFilter(annoType))添加了一个包含过滤器,该过滤器根据给定的注解类型过滤扫描结果。也就是说保留含有特定注解的类

scan方法

它调用了父类的scan方法,实际上触发了扫描过程。传入的basePackages参数是要扫描的包路径。

所以这个类的主要作用就是,扫描带有特定注解的类,并且将他们注册为bean

CustomScannerRegistrar

真正用来扫描包并进行注册的类

@Slf4j
public class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private static final String SPRING_BEAN_BASE_PACKAGE = "github.javaguide";
    private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage";
    private ResourceLoader resourceLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;

    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //get the attributes and values ​​of RpcScan annotation
        AnnotationAttributes rpcScanAnnotationAttributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName()));
        String[] rpcScanBasePackages = new String[0];
        if (rpcScanAnnotationAttributes != null) {
            // get the value of the basePackage property
            rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
        }
        if (rpcScanBasePackages.length == 0) {
            rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
        }
        // Scan the RpcService annotation
        CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class);
        // Scan the Component annotation
        CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class);
        if (resourceLoader != null) {
            rpcServiceScanner.setResourceLoader(resourceLoader);
            springBeanScanner.setResourceLoader(resourceLoader);
        }
        int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE);
        log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
        int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
        log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);

    }

}

字段和属性

private static final String SPRING_BEAN_BASE_PACKAGE = "github.javaguide";// Spring Bean 默认扫描的基础包路径。
private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage";//定义了 @RpcScan 注解中指定扫描包路径的属性名称。
private ResourceLoader resourceLoader;//加载资源的接口

registerBeanDefinitions

在 Spring 启动过程中,当解析到配置类中的 @Import(CustomScannerRegistrar.class) 注解时,Spring 会触发 CustomScannerRegistrar 类的加载和初始化。在初始化过程中,会调用 registerBeanDefinitions 方法,执行自定义的注册逻辑。这个过程是 Spring 在启动时自动完成的,不需要手动调用。

参数

public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) 

AnnotationMetadata annotationMetadata: 通过这个参数获取自定义注解 @RpcScan 的元数据,以便后续获取注解属性值。

获取 @RpcScan 注解的属性和值。

AnnotationAttributes rpcScanAnnotationAttributes =AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName()));
  1. RpcScan.class.getName() 获取了 @RpcScan 注解的全限定类名。(就是包名加上类名)

  2. annotationMetadata.getAnnotationAttributes(...) 返回一个 Map<String, Object>,其中包含了注解属性和对应的值。在这个情况下,map里就是String[] basePackage();和对应的值

  3. 通过 AnnotationAttributes.fromMap(...)Map<String, Object> 转换为 AnnotationAttributes 对象,类似一个map,使得可以更方便地访问和操作注解的属性。

获取扫描包路径数组

 String[] rpcScanBasePackages = new String[0];
        if (rpcScanAnnotationAttributes != null) {
            // get the value of the basePackage property
            rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
        }
//如果没有指定路径
        if (rpcScanBasePackages.length == 0) {
            rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
        }

解释一下第二个if:

如果没有指定路径,那么就通过反射,讲扫描包路径设置为当前被扫描的类所在的包

getIntrospectedClass()StandardAnnotationMetadata 类提供的方法之一,用于获取被 introspected(内省、审查)的类的 Class 对象。

设置扫描器

// Scan the RpcService annotation
CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class);
// Scan the Component annotation
CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class);

为什么不扫描RpcReference?

RpcReference里面是@Target({ElementType.FIELD})

RpcReference是标记在字段上的,本身的类被@Componet标记,所以也含在里面了

因为你的

资源加载器设置到 CustomScanner 实例中

if (resourceLoader != null) {
    rpcServiceScanner.setResourceLoader(resourceLoader);
    springBeanScanner.setResourceLoader(resourceLoader);
}

扫描并注册

int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE);
log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);
  1. 使用 springBeanScanner 扫描 Spring 组件(带有 @Component 注解的类)的数量,并记录日志。
  2. 使用 rpcServiceScanner 扫描 RPC 服务(带有 @RpcService 注解的类)的数量,并记录日志。

小结

CustomScannerCustomScannerRegistrar 的组合起到了扫描指定注解的作用,并将扫描到的类注册为 Spring Bean。

CustomScanner :主要功能就是调用父类进行扫描的实际操作

CustomScannerRegistrar:主要功能就是 实现了 ImportBeanDefinitionRegistrar 接口,重写了registerBeanDefinitions函数,来定义要扫描哪些包,扫描有哪些注解的类

所以逻辑是这样的

  • 当开始时,因为我们@Import了@Import(CustomScannerRegistrar.class),所以此时就会触发CustomScannerRegistrar的加载和初始化
  • 这个类初始化的过程中registerBeanDefinitions又是被自动调用
  • registerBeanDefinitions方法里面,扫描@Componet注解和@RpcService注解的类,并且注册为bean
  • 那么注册前后就会有postProcessBeforeInitialization()方法和postProcessAfterInitialization()的执行了

SpringBeanPostProcessor

傻逼guide说的原理:

我们实现需要 BeanPostProcessor 接口并重写 postProcessBeforeInitialization()方法和 postProcessAfterInitialization() 方法。

Spring bean 在实例化之前会调用 postProcessBeforeInitialization()方法,在 Spring bean 实例化之后会调用 postProcessAfterInitialization() 方法。

被我们使用 RpcService和RpcReference 注解的类都算是 Spring Bean。

●我们可以在postProcessBeforeInitialization()方法中去判断类上是否有RpcService 注解。如果有的话,就取出 group 和 version 的值。然后,再调用 ServiceProvider 的 publishService() 方法发布服务即可!

●我们可以在 postProcessAfterInitialization() 方法中遍历类的属性上是否有 RpcReference 注解。如果有的话,我们就通过反射将这个属性赋值即可!

静态变量和构造函数

private final ServiceProvider serviceProvider;
private final RpcRequestTransport rpcClient;

public SpringBeanPostProcessor() {
    this.serviceProvider = SingletonFactory.getInstance(ZkServiceProviderImpl.class);
    this.rpcClient = ExtensionLoader.getExtensionLoader(RpcRequestTransport.class).getExtension(RpcRequestTransportEnum.NETTY.getName());
}

serviceProvider在provider包内,见对应文章

serviceProvider: 这是一个接口,用于提供服务注册的功能。通过 SingletonFactory.getInstance(ZkServiceProviderImpl.class) 来获取 ZkServiceProviderImpl 的单例实例,这是服务注册的一种具体实现。

rpcClient: 这是一个接口,用于处理远程过程调用(RPC)。通过 ExtensionLoader.getExtensionLoader(RpcRequestTransport.class).getExtension(RpcRequestTransportEnum.NETTY.getName()) 来获取 RpcRequestTransport 接口的具体实现,其中 RpcRequestTransportEnum.NETTY.getName() 表示获取 NETTY 实现,这是 RPC 请求的传输方式。

postProcessBeforeInitialization

在 Bean 初始化之前检查是否有 @RpcService 注解,如果有,则进行服务注册操作。

@SneakyThrows
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (bean.getClass().isAnnotationPresent(RpcService.class)) {
        log.info("[{}] is annotated with  [{}]", bean.getClass().getName(), RpcService.class.getCanonicalName());
        // get RpcService annotation
        RpcService rpcService = bean.getClass().getAnnotation(RpcService.class);
        // build RpcServiceProperties
        RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
                .group(rpcService.group())
                .version(rpcService.version())
                .service(bean).build();
        serviceProvider.publishService(rpcServiceConfig);
    }
    return bean;
}

@SneakyThrows: Lombok 注解,用于在方法中抛出受检异常时不需要显式捕获。

@Override: 表示该方法是对父类或接口中同名方法的重写。

postProcessBeforeInitialization: 这是 BeanPostProcessor 接口定义的方法之一,在 Bean 初始化之前被调用。Object bean: 表示正在被初始化的 Bean 实例。

String beanName: 表示正在被初始化的 Bean 的名称。

bean.getClass().isAnnotationPresent(RpcService.class): 检查当前 Bean 类是否被 @RpcService 注解标记。

如果被注解标记:

  • 通过 bean.getClass().getAnnotation(RpcService.class) 获取 @RpcService 注解的实例。
  • 使用获取到的注解信息构建 RpcServiceConfig 对象。
  • 调用 serviceProvider.publishService(rpcServiceConfig) 注册服务。这个里面最终是用Curator实现

postProcessAfterInitialization

在 Bean 初始化之后检查是否有 @RpcReference 注解,如果有,则创建相应的代理对象,并将其注入到对应的字段中。这是实现 RPC 客户端代理的关键逻辑。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    Class<?> targetClass = bean.getClass();
    Field[] declaredFields = targetClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class);
        if (rpcReference != null) {
            RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
                    .group(rpcReference.group())
                    .version(rpcReference.version()).build();
            RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceConfig);
            Object clientProxy = rpcClientProxy.getProxy(declaredField.getType());
            declaredField.setAccessible(true);
            try {
                declaredField.set(bean, clientProxy);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }
    return bean;
}

Class<?> targetClass = bean.getClass(): 获取当前 Bean 的类对象。

Field[] declaredFields = targetClass.getDeclaredFields(): 获取当前

for (Field declaredField : declaredFields): 遍历所有声明字段。

RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class): 获取字段上的 @RpcReference 注解。

if (rpcReference != null): 如果字段上有 @RpcReference 注解,执行以下操作:

​ 构建 RpcServiceConfig 对象,用于配置 RPC 服务的相关信息。

​ 创建 RpcClientProxy 实例,用于生成动态代理对象。

​ 通过 rpcClientProxy.getProxy(declaredField.getType()) 获取代理对象。代理的部分见动态代理博客

declaredField.setAccessible(true);将字段设置为可访问

为什么?

绕过Java的访问修饰符,以便在运行时通过反射访问和修改字段的值。

declaredField.set(bean, clientProxy);

将创建的代理对象 clientProxy 设置为字段 declaredField 所在的对象 bean 的值,也就是把bean里面的代理类设置成我们新建的这个proxy类

如果类型不匹配就抛出异常

小结

SpringBeanPostProcessor 类上使用了 @Component 注解,这意味着它会被 Spring 扫描并注册为一个 Spring Bean。由于它实现了 BeanPostProcessor 接口,Spring 在初始化每个 Bean 的过程中都会调用 postProcessBeforeInitializationpostProcessAfterInitialization 方法。

当一个类被标记为 @Component 时,Spring 会将其纳入到 IoC 容器的管理中,自动进行实例化并进行依赖注入。在这个过程中,如果该类同时实现了 BeanPostProcessor 接口,Spring 就会注意到它,并在适当的时机调用其 postProcessBeforeInitializationpostProcessAfterInitialization 方法。

因此,@Component 注解是触发 Spring 自动执行 BeanPostProcessor 方法的一种方式。


  目录