跳到主要内容

SpringBoot注解驱动设计

在Dubbo中,我们想要使用Dubbo框架的RPC远程调用功能,主要有三步:

  • 在启动类上加@EnableDubbo
  • 在提供方类上加:@DubboService
  • 在消费者注入的类上加:@DubboReference

我们参考Dubbo,也开发这样三个注解就可以使用整个RPC框架

项目初始化

新建一个SpringBoot项目,加入相关依赖,插件,以及build都不需要

<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.yunfei</groupId>
<artifactId>yunfei-rpc-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

这个插件的主要作用是用户在写yml的时候可以有注释

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

注解设计

@EnableYunRpc

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({RpcInitBootStrap.class, RpcProviderBootstrap.class, RpcConsumerBootstrap.class})
public @interface EnableYunRpc {

/**
* 需要启动server
*
* @return
*/
boolean needServer() default true;
}

@EnableYunRpc 注解的设计:

  1. @Target({ElementType.TYPE}):

    • 这个注解用于指定 @EnableYunRpc 注解可以被应用于哪些程序元素。在这里,它被限定为只能应用于类型(Type)级别,也就是类、接口或枚举。
  2. @Retention(RetentionPolicy.RUNTIME):

    • 这个注解用于指定 @EnableYunRpc 注解的保留策略。RUNTIME 表示该注解会在运行时被 JVM 读取和使用,可以被反射机制访问。
  3. @Import({RpcInitBootStrap.class, RpcProviderBootstrap.class, RpcConsumerBootstrap.class}):

    • 这个注解用于导入其他配置类。在这里,它导入了三个引导类:
      • RpcInitBootStrap: RPC 应用程序的初始化引导类。
      • RpcProviderBootstrap: RPC 服务提供者的引导类。
      • RpcConsumerBootstrap: RPC 服务消费者的引导类。
    • @EnableYunRpc 注解被应用到一个类上时,Spring 容器会自动注册这三个引导类。
  4. public @interface EnableYunRpc {}:

    • 这是一个自定义注解的声明,名为 @EnableYunRpc。通过这个注解,开发者可以在自己的应用程序中开启 RPC 相关的功能。
  5. boolean needServer() default true;:

    • 这是 @EnableYunRpc 注解中定义的一个属性。它用于指定是否需要启动 RPC 服务端。默认值为 true
    • 开发者可以通过设置这个属性的值来决定是否需要启动 RPC 服务端,例如在仅作为 RPC 客户端的场景下,可以将其设置为 false

这个 @EnableYunRpc 注解是一个基于 Spring 的注解驱动设计模式,它可以帮助开发者快速地在自己的应用程序中集成 RPC 功能。通过将引导类的注册和初始化过程封装在这个注解中,开发者只需要简单地在入口类上添加该注解,就可以自动完成 RPC 相关的配置和初始化。

@YunRpcService

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface YunRpcService {

/**
* 服务接口类
*
* @return
*/
Class<?> interfaceClass() default void.class;

/**
* 服务版本
*
* @return
*/
String serviceVersion() default RpcConstant.DEFAULT_SERVICE_VERSION;
}

@YunRpcService 注解主要用于标注 RPC 服务提供者。

  1. @Target({ElementType.TYPE}):

    • 这个元注解指定了 @YunRpcService 注解可以被应用于类型(Type)级别,也就是类、接口或枚举。
  2. @Retention(RetentionPolicy.RUNTIME):

    • 这个元注解指定了 @YunRpcService 注解的保留策略是在运行时被 JVM 读取和使用。
  3. @Component:

    • 这个元注解将 @YunRpcService 注解标记为 Spring 组件,意味着被这个注解标注的类会被 Spring 容器自动扫描和注册。
  4. public @interface YunRpcService {}:

    • 这是 @YunRpcService 注解本身的声明。它定义了这个注解的名称和作用域。
  5. Class<?> interfaceClass() default void.class;:

    • 这是 @YunRpcService 注解定义的一个属性,用于指定服务接口类。
    • 如果不设置该属性,默认值为 void.class
  6. String serviceVersion() default RpcConstant.DEFAULT_SERVICE_VERSION;:

    • 这是 @YunRpcService 注解定义的另一个属性,用于指定服务的版本号。
    • 如果不设置该属性,默认值为 RpcConstant.DEFAULT_SERVICE_VERSION

@YunRpcService 注解的设计目的是为了简化 RPC 服务提供者的配置。当一个类被这个注解标注时,Spring 容器会自动扫描并注册该服务,同时也会提取服务接口类和版本号等元信息。这些信息可以在后续的服务发现和调用过程中使用。

@YunRpcReference

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface YunRpcReference {

/**
* 服务接口类
* @return
*/
Class<?> interfaceClass() default void.class;

/**
* 服务版本
* @return
*/
String serviceVersion() default RpcConstant.DEFAULT_SERVICE_VERSION;

/**
* 负载均衡策略
* @return
*/
String loadBalancer() default LoadBalancerKeys.ROUND_ROBIN;

/**
* 重试策略
* @return
*/
String retryStrategy() default RetryStrategyKeys.NO;

/**
* 容错策略
* @return
*/
String tolerantStrategy() default TolerantStrategyKeys.FAIL_FAST;

/**
* 是否mock
* @return
*/
boolean mock() default false;
}

这个 @YunRpcReference 注解是用于标注 RPC 服务消费者端的注解。

  1. @Target({ElementType.FIELD}):

    • 这个元注解指定了 @YunRpcReference 注解只能被应用在字段(Field)级别。
  2. @Retention(RetentionPolicy.RUNTIME):

    • 这个元注解指定了 @YunRpcReference 注解的保留策略是在运行时被 JVM 读取和使用。
  3. public @interface YunRpcReference {}:

    • 这是 @YunRpcReference 注解本身的声明。它定义了这个注解的名称和作用域。
  4. Class<?> interfaceClass() default void.class;:

    • 这是 @YunRpcReference 注解定义的一个属性,用于指定服务接口类。
    • 如果不设置该属性,默认值为 void.class
  5. String serviceVersion() default RpcConstant.DEFAULT_SERVICE_VERSION;:

    • 这是 @YunRpcReference 注解定义的另一个属性,用于指定服务的版本号。
    • 如果不设置该属性,默认值为 RpcConstant.DEFAULT_SERVICE_VERSION
  6. String loadBalancer() default LoadBalancerKeys.ROUND_ROBIN;:

    • 这是 @YunRpcReference 注解定义的一个属性,用于指定负载均衡策略。
    • 如果不设置该属性,默认值为 LoadBalancerKeys.ROUND_ROBIN
  7. String retryStrategy() default RetryStrategyKeys.NO;:

    • 这是 @YunRpcReference 注解定义的一个属性,用于指定重试策略。
    • 如果不设置该属性,默认值为 RetryStrategyKeys.NO
  8. String tolerantStrategy() default TolerantStrategyKeys.FAIL_FAST;:

    • 这是 @YunRpcReference 注解定义的一个属性,用于指定容错策略。
    • 如果不设置该属性,默认值为 TolerantStrategyKeys.FAIL_FAST
  9. boolean mock() default false;:

    • 这是 @YunRpcReference 注解定义的一个属性,用于指定是否使用 Mock 模式。
    • 如果不设置该属性,默认值为 false

总的来说,@YunRpcReference 注解的设计目的是为了简化 RPC 服务消费者的配置。当一个字段被这个注解标注时,Spring 容器会自动注入一个代理对象,该代理对象会负责执行 RPC 调用。开发者可以通过设置注解属性来配置负载均衡、重试、容错等策略,以满足不同的业务需求。

注解驱动

全局启动类

我们希望在Spring框架初始化的时候,能够获取@EnableYunRpc注解,并且初始化RPC框架。

可以使用Spring的ImportBeanDefinitionRegistrar接口来实现,此接口用于在 Spring 容器初始化时执行自定义的注册逻辑。

具体的自定义的注册逻辑写在registerBeanDefinitions方法里面

@Slf4j
public class RpcInitBootStrap implements ImportBeanDefinitionRegistrar {

/**
* Spring初始化执行时候,初始化Rpc框架
*
* @param importingClassMetadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取EnableRpc 注解的属性值
boolean needServer = (boolean) importingClassMetadata.getAnnotationAttributes(EnableYunRpc.class.getName()).get("needServer");

// Rpc框架初始化(配置和注册中心)
RpcApplication.init();

final RpcConfig rpcConfig = RpcApplication.getRpcConfig();

// 启动服务器
if (needServer) {
VertxTcpServer vertxTcpServer = new VertxTcpServer();
vertxTcpServer.doStart(rpcConfig.getServerPort());
} else {
log.info("Rpc server is not started");
}
}
}

这个 RpcInitBootStrap 类负责在 Spring 容器初始化时执行 RPC 框架的初始化和服务端启动逻辑。

它利用 @EnableYunRpc 注解中的 needServer 属性,来决定是否需要启动 RPC 服务端。这种设计可以让 RPC 框架在 Spring Boot 应用中更加灵活和可配置。

提供者启动

提供者需要获取到所有包含@YunRpcService的注解的类,然后利用反射机制,获取到对应的注册信息,完成服务信息的注册。

我们让启动类实现BeanPostProcessor接口里的postProcessAfterInitialization方法,就可以在服务提供者Bean初始化之后,执行注册服务等操作了。

public class RpcProviderBootstrap implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
YunRpcService rpcService = beanClass.getAnnotation(YunRpcService.class);
if (rpcService != null) {
// 需要注册服务
// 获取服务基本信息
Class<?> interfaceClass = rpcService.interfaceClass();
// 默认值处理
if (interfaceClass == void.class) {
interfaceClass = beanClass.getInterfaces()[0];
}
String serviceName = interfaceClass.getName();
String serviceVersion = rpcService.serviceVersion();

// 注册服务
// 本地注册
LocalRegistry.register(serviceName, beanClass);

// 全局配置
final RpcConfig rpcConfig = RpcApplication.getRpcConfig();
// 注册到注册中心
RegistryConfig registryConfig = rpcConfig.getRegistryConfig();
Registry registry = RegistryFactory.getInstance(registryConfig.getRegistry());
ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
serviceMetaInfo.setServiceName(serviceName);
serviceMetaInfo.setServiceVersion(serviceVersion);
serviceMetaInfo.setServiceHost(rpcConfig.getServerHost());
serviceMetaInfo.setServicePort(rpcConfig.getServerPort());
try {
registry.register(serviceMetaInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
  1. 获取服务基本信息:

    • 首先从 @YunRpcService 注解中获取 interfaceClass 属性的值。这个属性指定了服务的接口类。
    • 如果 interfaceClass 的值为 void.class(默认值),则说明开发者没有手动指定接口类,于是取当前 Bean 实例的第一个接口作为服务接口。
    • 使用服务接口类的名称作为 serviceName
    • @YunRpcService 注解中获取 serviceVersion 属性的值。
  2. 本地注册服务:

    • 使用 LocalRegistry.register(serviceName, beanClass) 方法将服务信息注册到本地注册表中。这样在后续的 RPC 调用中,就可以从本地注册表中获取到服务的实现类。

消费者启动

RpcConsumerBootstrap 类是 RPC 服务消费者的引导类,它同样实现了 Spring 的 BeanPostProcessor 接口。它的主要作用是在 Spring 容器初始化 Bean 实例后,检查这些 Bean 中是否有被 @YunRpcReference 注解标注的字段,如果有,则为这些字段生成代理对象并注入。

public class RpcConsumerBootstrap implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
// 遍历对象的所有属性
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field field : declaredFields) {
YunRpcReference rpcReference = field.getAnnotation(YunRpcReference.class);
if (rpcReference != null) {
// 为属性生成代理对象
Class<?> interfaceClass = rpcReference.interfaceClass();
if (interfaceClass == void.class) {
interfaceClass = field.getType();
}
System.out.println("生成代理对象:" + interfaceClass.getName()+" "+field.getType());
field.setAccessible(true);
log.info("生成代理对象:{}", interfaceClass.getName());
Object proxy = ServiceProxyFactory.getProxy(interfaceClass);
try {
field.set(bean, proxy);
field.setAccessible(false);
} catch (IllegalAccessException e) {
System.out.println("生成代理对象失败");
throw new RuntimeException(e);
}
}

}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}

让我们逐步解析这个类的实现:

  1. Field[] declaredFields = beanClass.getDeclaredFields();:这行代码获取当前 Bean 实例的所有声明字段。
  2. YunRpcReference rpcReference = field.getAnnotation(YunRpcReference.class);:如果字段被 @YunRpcReference 注解标注,则获取该注解实例。
  3. if (rpcReference != null) { ... }:
    • 如果字段被 @YunRpcReference 注解标注,则执行以下逻辑:
      • 获取服务接口类,如果未指定则默认使用字段类型。
      • 使用 ServiceProxyFactory.getProxy(interfaceClass) 方法为服务接口生成代理对象。
      • 将生成的代理对象设置到当前 Bean 实例的字段上。
  4. return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);:
    • 最后,调用父类 BeanPostProcessorpostProcessAfterInitialization() 方法,确保其他 BeanPostProcessor 实现也能正确执行。

注册已编写的启动类

最后还需要在启动类上面使用@Import到注册我们自定义的启动类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({RpcInitBootStrap.class, RpcProviderBootstrap.class, RpcConsumerBootstrap.class})
public @interface EnableYunRpc {

/**
* 需要启动server
*
* @return
*/
boolean needServer() default true;
}

测试

见快速入门:快速入门