nats java rpc 实现类似EnableFeignClients模式

以前我简单介绍过基于nats rpc 实现类似feign 模式的访问,但是使用中并不如spring cloud feign 的方便,所以说明下如果实现类似EnableFeignClients的模式,实现bean 自动注册

实现机制

  • 原理

基本机制类似EnableFeignClients,通过import 导入一个ImportBeanDefinitionRegistrar 实现,基于bean 的自动注册基于了以前client的机制,具体是自己实现一个FactoryBean

  • EnableNatsRpcClients 实现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(NatsRpcClientsRegistrar.class)
@Documented
public @interface EnableNatsRpcClients {
    String[] basePackages() default {};
}
  • NatsRpcClientsRegistrar
public class NatsRpcClientsRegistrar implements ImportBeanDefinitionRegistrar {

    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableNatsRpcClients.class.getName());
        String[] basePackages = (String[]) attributes.get("basePackages");

        if (basePackages.length == 0) {
            basePackages = new String[]{importingClassMetadata.getClassName().substring(0,
                    importingClassMetadata.getClassName().lastIndexOf("."))};
        }

        ClassPathScanningCandidateComponentProvider  scanner = getScanner();
        scanner.addIncludeFilter(new AnnotationTypeFilter(RpcClient.class,true));

        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDef -> {
                try {
                    Class<?> clazz = Class.forName(beanDef.getBeanClassName());
                    BeanDefinitionBuilder builder =
                            BeanDefinitionBuilder.genericBeanDefinition(NatsRpcClientFactoryBean.class);
                    builder.addConstructorArgValue(clazz);
                    builder.addConstructorArgReference("connection");
                    builder.addConstructorArgReference("objectMapper");
                    String beanName = clazz.getSimpleName();
                    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
}
  • NatsRpcClientFactoryBean 实现

直接复用了以前的RpcServiceClient

public class NatsRpcClientFactoryBean<T> implements FactoryBean<T> {

    private final Class<T> rpcInterface;
    private final Connection connection;
    private final ObjectMapper objectMapper;

    public NatsRpcClientFactoryBean(Class<T> rpcInterface, Connection connection, ObjectMapper objectMapper) {
        this.rpcInterface = rpcInterface;
        this.connection = connection;
        this.objectMapper = objectMapper;
    }
    @Nullable
    @Override
    public T getObject() throws Exception {
        T serviceClient = RpcServiceClient.builder().objectMapper(objectMapper)
                .connection(connection)
                .build().target(rpcInterface);
        return serviceClient;
    }

    @Nullable
    @Override
    public Class<?> getObjectType() {
        return rpcInterface;
    }
}

参考使用

就是类似enablefeign 模式

  • 参考示例
@SpringBootApplication
@EnableNatsRpcClients(basePackages = {
       "com.demo.rpc"
})
  • rpc 定义
@RpcClient(
        serviceName = "authservice",
        servicePrefix = "global",
        serviceEndpoint = "tenantauthservicev2"
)
public interface TenantAuthApi {
    List<ResponseMessage<String>> auth(Message message);
    List<String> getRoles(Message message, Headers headers);
    List<String> getRoles(Message message);
    List<String> delteRoles(Message message, Headers headers);
    List<String> updateRoles(Message message, Headers headers);
    Object  createTenant(MyTenantModel message, Headers headers);
    Object  selectTenant(MyTenantModel message, Headers headers);
    List<ResponseMessage<String>> auth(Message message, Headers headers);
    List<ResponseMessage<String>> auth(String prefix,Message message);
    List<ResponseMessage<String>> auth(String prefix,Message message, Headers headers);
}

说明

以上是一个简单的使用,主要是通过类似feign 模式实现业务的快速开发使用,比较有意思的地方是ClassPathScanningCandidateComponentProvider ,我使用了feign 的,isCandidateComponent 方法还是很重要的,否则会有扫描不到的问题

参考资料

https://github.com/spring-cloud/spring-cloud-openfeign/blob/main/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java#L378

posted on 2025-12-31 08:00  荣锋亮  阅读(0)  评论(0)    收藏  举报

导航