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) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate = false;if (beanDefinition.getMetadata().isIndependent()) {if (!beanDefinition.getMetadata().isAnnotation()) {isCandidate = true;}}return isCandidate;}};}@Overridepublic 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@Overridepublic T getObject() throws Exception {T serviceClient = RpcServiceClient.builder().objectMapper(objectMapper).connection(connection).build().target(rpcInterface);return serviceClient;}@Nullable@Overridepublic 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