Spring调用Vertx异步service Interface
- 情景
- 原理
- 实现
- 工程结构说明
- 目录结构
- 样例代码
- example-vertx-interface
- pom依赖
- 代码
- example-vertx-cluster
- pom依赖
- 代码
- lm-spring-vertx-ebclient
- pom依赖
- 代码
- @VertxEBClientScan
- VertxEBClientScannerRegister
- VertxEBProxyBeanDefinitionRegistryPostProcessor
- VertxServiceScanner
- VertxEBProxyFactoryBean
- AwaitUtil
- example-spring-vertx-ebclient
- pom依赖
- 代码
- 样例代码效果展示
- 制作spring-boot-starter
- lm-spring-vertx-ebclient-starter
- lm-spring-vertx-autoconfigure
情景
现有基于Vertx开发的中台应用,新立项一个基于Spring开发的活动应用,需要调用中台应用提供的各种服务能力。
现存问题:
- 中台应用对外提供服务能力,对于非Vertx项目,仅有HTTP RESTful SDK
- SDK有HTTP网协议络消耗
- 新加接口需要SDK的开发工作
期待:
- 比HTTP协议更高效的网络通讯
- 减少新加接口的开发工作
- 简化上游应用对下游服务接入的开发工作
原理
基于Vertx部署的Vertical实例之间的通讯由vertx-eventbus
完成,vertx service之间的调用,靠的就是eventbus
,同Vertical实例service之间使用的是本地eventbus
,没有网络开销,而不同Vertical实例service之间的使用的集群eventbus
,底层会使用TCP/IP的方式连接。
Vertx提供services-proxy机制来简化eventbus
的冗余样板代码,底层是通过代码生成技术,为service interface生成代理类,完成服务注册,服务远程调用的eventbus
代码实现。
Spring提供了FactoryBean
机制,可以为service interface生成代理bean实例。因此我们可以s类似于spring+dubbo
的spring+vertx
的高效RPC。
实现
工程结构说明
一共会启动3个应用:
- HelloInstance(提供HelloService服务)
- NameInstance(提供NameService服务)
- Application(Spring构建的应用,需要调用到HelloService、NameService服务)
目录结构
共7个maven module:
- example-vertx-interface(接口样例)
- example-vertx-cluster(vertx集群样例)
- example-spring-vertx-ebclient(spring+vertx整合样例)
- example-spring-vertx-ebclient-starter(spring-boot+vertx整合样例)
- lm-spring-vertx-ebclient(spring+)
- lm-spring-vertx-autoconfigure
- lm-spring-vertx-ebclient-starter
3个example,1个spring整合
├─example
│ ├─example-lm-spring-vertx-ebclient
│ │ ├─example-spring-vertx-ebclient
│ │ │ └─src
│ │ │ └─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─luomin
│ │ │ │ └─example
│ │ │ │ │ Application.java
│ │ │ │ ├─config
│ │ │ │ │ VertxEBClientConfig.java
│ │ │ │ └─controller
│ │ │ │ HelloController.java
│ │ │ └─resources
│ │ │ cluster.xml
│ │ │
│ │ ├─example-spring-vertx-ebclient-starter
│ │ │ ├─src
│ │ │ │ └─main
│ │ │ │ ├─java
│ │ │ │ │ └─com
│ │ │ │ │ └─luomin
│ │ │ │ │ └─example
│ │ │ │ │ │ Application.java
│ │ │ │ │ └─controller
│ │ │ │ │ HelloController.java
│ │ │ │ └─resources
│ │ │ │ cluster.xml
│ │ │
│ │ ├─example-vertx-cluster
│ │ │ │ example-vertx-cluster.iml
│ │ │ │ pom.xml
│ │ │ │
│ │ │ └─src
│ │ │ └─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─luomin
│ │ │ │ └─example
│ │ │ │ │ HelloInstance.java
│ │ │ │ │ NameInstance.java
│ │ │ │ ├─service
│ │ │ │ │ └─impl
│ │ │ │ │ HelloServiceImpl.java
│ │ │ │ │ NameServiceImpl.java
│ │ │ │ └─verticle
│ │ │ │ HelloApi.java
│ │ │ │
│ │ │ └─resources
│ │ │ cluster.xml
│ │ │
│ │ └─example-vertx-interface
│ │ │ example-vertx-interface.iml
│ │ │ pom.xml
│ │ │
│ │ ├─src
│ │ │ └─main
│ │ │ └─java
│ │ │ └─com
│ │ │ └─luomin
│ │ │ └─example
│ │ │ └─service
│ │ │ HelloService.java
│ │ │ NameService.java
│ │ │ package-info.java
│ │
├─lm-spring-vertx-autoconfigure
│ ├─src
│ │ └─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─luomin
│ │ │ └─autoconfig
│ │ │ └─ebclient
│ │ │ AutoConfiguredVertxEBClientScannerRegister.java
│ │ │ EbClientAutoConfiguration.java
│ │ │
│ │ └─resources
│ │ └─META-INF
│ │ spring.factories
│ │
├─lm-spring-vertx-ebclient
│ ├─src
│ │ └─main
│ │ └─java
│ │ └─com
│ │ └─luomin
│ │ └─ebclient
│ │ ├─annotation
│ │ │ VertxEBClientScan.java
│ │ │ VertxEBClientScannerRegister.java
│ │ │
│ │ ├─factorybean
│ │ │ VertxEBProxyFactoryBean.java
│ │ ├─postprocessor
│ │ │ VertxEBProxyBeanDefinitionRegistryPostProcessor.java
│ │ ├─scanner
│ │ │ VertxServiceScanner.java
│ │ └─util
│ │ AwaitUtil.java
│ │
├─lm-spring-vertx-ebclient-starter
│ pom.xml
样例代码
example-vertx-interface
example-vertx-interface是vertx下游服务的接口,并且也会生成代理实现类,待会也会被spring所依赖。
pom依赖
<dependencies><dependency><groupId>io.vertx</groupId><artifactId>vertx-service-proxy</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-codegen</artifactId><version>3.9.14</version><classifier>processor</classifier></dependency></dependencies><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessors><annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor></annotationProcessors></configuration></plugin></plugins></build>
代码
@ModuleGen(name = "example", groupPackage = "com.luomin.example.service")
package com.luomin.example.service;
import io.vertx.codegen.annotations.ModuleGen;
@ProxyGen
public interface HelloService {void sayHello(String name, Handler<AsyncResult<String>> resultHandler);
}
@ProxyGen
public interface NameService {void getName(Handler<AsyncResult<String>> resultHandler);
}
example-vertx-cluster
example-vertx-cluster会启动两个vertx实例,分别部署vertical,用于提供服务。
pom依赖
<dependencies><dependency><groupId>io.vertx</groupId><artifactId>vertx-web</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-hazelcast</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-service-proxy</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-codegen</artifactId><version>3.9.14</version><classifier>processor</classifier></dependency><dependency><groupId>com.luomin</groupId><artifactId>example-vertx-interface</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessors><annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor></annotationProcessors></configuration></plugin></plugins></build>
代码
public class NameServiceImpl implements NameService {private final List<String> nameList = new ArrayList<>();private int index = 0;public NameServiceImpl() {nameList.add("luomin");nameList.add("xym");nameList.add("hehe");}@Overridepublic void getName(Handler<AsyncResult<String>> resultHandler) {resultHandler.handle(Future.succeededFuture(getName()));}private String getName() {if (index >= nameList.size()) {index = 0;}System.out.println("index = " + index);String name = nameList.get(index);index++;return name;}
}
public class HelloServiceImpl implements HelloService {@Overridepublic void sayHello(String name, Handler<AsyncResult<String>> resultHandler) {resultHandler.handle(Future.succeededFuture("hello " + name));}
}
public class HelloApi extends AbstractVerticle {private HelloService helloService;private NameService nameService;@Overridepublic void start() throws Exception {this.initService();HttpServer httpServer = vertx.createHttpServer();Router router = Router.router(vertx);router.route("/index").handler(routingContext -> {HttpServerResponse response = routingContext.response();response.putHeader("content-type", "text/plain");Future.<String>future(promise -> this.nameService.getName(promise)).compose(name -> Future.<String>future(promise -> this.helloService.sayHello(name, promise))).onSuccess(response::end).onFailure(t -> response.end(t.getMessage()));});httpServer.requestHandler(router);httpServer.listen(10000);}private void initService() {this.helloService = new ServiceProxyBuilder(vertx).setAddress(HelloService.class.getName()).build(HelloService.class);this.nameService = new ServiceProxyBuilder(vertx).setAddress(NameService.class.getName()).build(NameService.class);}
}
public class HelloInstance {public static void main(String[] args) {ClusterManager mgr = new HazelcastClusterManager();VertxOptions options = new VertxOptions().setClusterManager(mgr);Vertx.clusteredVertx(options, res -> {if (res.succeeded()) {System.out.println("cluster启动成功");Vertx vertx = res.result();vertx.deployVerticle(HelloApi.class.getName(), res1 -> {if (res1.succeeded()) {System.out.println("部署HelloApi成功");new ServiceBinder(vertx).setAddress(HelloService.class.getName()).register(HelloService.class, new HelloServiceImpl());System.out.println("注册HelloService成功");}});}});}
}
public class NameInstance {public static void main(String[] args) {ClusterManager mgr = new HazelcastClusterManager();VertxOptions options = new VertxOptions().setClusterManager(mgr);Vertx.clusteredVertx(options, res -> {if (res.succeeded()) {System.out.println("cluster启动成功");Vertx vertx = res.result();new ServiceBinder(vertx).setAddress(NameService.class.getName()).register(NameService.class, new NameServiceImpl());System.out.println("注册NameService成功");}});}
}
lm-spring-vertx-ebclient
lm-spring-vertx-ebclient用于spring整合vertx service proxy机制,能够通过java interface注入bean。
pom依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.14</version><scope>provided</scope></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-core</artifactId><version>3.9.14</version><scope>provided</scope></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-codegen</artifactId><version>3.9.14</version><scope>provided</scope></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-service-proxy</artifactId><version>3.9.14</version><scope>provided</scope></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-hazelcast</artifactId><version>3.9.14</version><scope>provided</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version><scope>provided</scope></dependency></dependencies>
代码
@VertxEBClientScan
用于扫描需要生成代理的Service接口的注解。
这里用@Import
导入我们的组件。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(VertxEBClientScannerRegister.class)
public @interface VertxEBClientScan {String[] value() default {};
}
VertxEBClientScannerRegister
VertxEBClientScannerRegister
获取@VertxEBClientScan
指定扫描的包路径。
注册并配置VertxEBProxyBeanDefinitionRegistryPostProcessor
。
@Slf4j
public class VertxEBClientScannerRegister implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {AnnotationAttributes ebClientScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(VertxEBClientScan.class.getName()));if (ebClientScanAttrs != null) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(VertxEBProxyBeanDefinitionRegistryPostProcessor.class);builder.addPropertyReference("vertx", "vertx");List<String> basePackages = Arrays.stream(ebClientScanAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList());builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);registry.registerBeanDefinition(beanName, beanDefinition);} else {log.warn("not found VertxEBClientScan annotation");}}
}
VertxEBProxyBeanDefinitionRegistryPostProcessor
VertxEBProxyBeanDefinitionRegistryPostProcessor
创建VertxServiceScanner
用于实际的扫描。
public class VertxEBProxyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Setterprivate Vertx vertx;@Setterprivate String basePackage;@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {VertxServiceScanner scanner = new VertxServiceScanner(registry, vertx);scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}
VertxServiceScanner
VertxServiceScanner
扫描类路径下指定包路径的被ProxyGen
修饰的Service接口,得到Set<BeanDefinitionHolder>
,对这些BeanDefinitionHolder
进行修改配置,指定class=VertxEBProxyFactoryBean
。
public class VertxServiceScanner extends ClassPathBeanDefinitionScanner {private final Vertx vertx;public VertxServiceScanner(BeanDefinitionRegistry registry, Vertx vertx) {super(registry, false);this.vertx = vertx;this.addIncludeFilter(new AnnotationTypeFilter(ProxyGen.class));}@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {logger.info("scan basePackages=" + Arrays.toString(basePackages));Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);if (beanDefinitionHolders.isEmpty()) {logger.warn("not found any BeanDefinition");} else {processBeanDefinition(beanDefinitionHolders);}return beanDefinitionHolders;}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return metadata.isInterface();}private void processBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders) {beanDefinitionHolders.forEach(bd -> {BeanDefinition beanDefinition = bd.getBeanDefinition();String beanClassName = beanDefinition.getBeanClassName();Class<?> beanClass;try {beanClass = Class.forName(beanClassName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}beanDefinition.setBeanClassName(VertxEBProxyFactoryBean.class.getName());MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();propertyValues.add("serviceProxyBuilder", new ServiceProxyBuilder(this.vertx).setAddress(beanClassName));propertyValues.add("serviceClass", beanClass);});}}
VertxEBProxyFactoryBean
VertxEBProxyFactoryBean
用于返回bean实例,这里使用ServiceProxyBuilder
生成代理对象。
@Slf4j
public class VertxEBProxyFactoryBean implements FactoryBean<Object> {@Getter@Setterprivate ServiceProxyBuilder serviceProxyBuilder;@Getter@Setterprivate Class<?> serviceClass;@Overridepublic Object getObject() throws Exception {log.info("build service proxy, serviceClass={}", serviceClass.getName());return serviceProxyBuilder.build(serviceClass);}@Overridepublic Class<?> getObjectType() {return serviceClass;}}
AwaitUtil
用于Spring环境中同步调用Service异步方法的工具类。
public class AwaitUtil {public static <T> T awaitResult(Consumer<Handler<AsyncResult<T>>> runnable) throws ExecutionException, InterruptedException {// TODO: 2022/12/5 如果是在vertx的io线程调用future.get()会导致io线程陷入死阻塞checkVertxEventLoop();CompletableFuture<T> future = toCompletableFuture(runnable);return future.get();}public static <T> CompletableFuture<T> toCompletableFuture(Consumer<Handler<AsyncResult<T>>> runnable) {checkVertxEventLoop();CompletableFuture<T> future = new CompletableFuture<>();Handler<AsyncResult<T>> resultHandler = newHandler(future);runnable.accept(resultHandler);return future;}private static <T> Handler<AsyncResult<T>> newHandler(CompletableFuture<T> future) {return event -> {if (event.succeeded()) {future.complete(event.result());} else future.completeExceptionally(event.cause());};}private static void checkVertxEventLoop() {if (isInVertxEventLoop()) throw new RuntimeException("not support blocking call in vertx eventLoop");}private static boolean isInVertxEventLoop() {return Thread.currentThread() instanceof VertxThread && ((VertxThread) Thread.currentThread()).isWorker();}
}
example-spring-vertx-ebclient
pom依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.6.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.6.2</version></dependency><dependency><groupId>com.luomin</groupId><artifactId>example-vertx-interface</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.luomin</groupId><artifactId>lm-spring-vertx-ebclient</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-core</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-codegen</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-service-proxy</artifactId><version>3.9.14</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-hazelcast</artifactId><version>3.9.14</version></dependency></dependencies>
代码
代码很简单,一个SpringBoot ,一个测试用的Controller,一个配置类。
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
@Configuration
@VertxEBClientScan("com.luomin.example")
public class VertxEBClientConfig {@Beanpublic Vertx vertx() throws ExecutionException, InterruptedException {ClusterManager mgr = new HazelcastClusterManager();VertxOptions options = new VertxOptions().setClusterManager(mgr);return AwaitUtil.awaitResult(resultHandler -> Vertx.clusteredVertx(options, resultHandler));}
}
@RestController
public class HelloController {@Autowiredprivate HelloService helloService;@Autowiredprivate NameService nameService;@GetMapping("/index")public String index() throws ExecutionException, InterruptedException {String name = AwaitUtil.awaitResult(resultHandler -> this.nameService.getName(resultHandler));String msg = AwaitUtil.awaitResult(resultHandler -> this.helloService.sayHello(name, resultHandler));return msg;}
}
样例代码效果展示
启动HelloInstance、NameInstance、Application后,可以看到当前Vertx集群共有3个节点。
观察日志,看到扫描的包路径跟路径下的Service接口。
访问 http://localhost:8080/index,可以看到已经注入了bean实例为Vertx的xxxxVertxEBProxy
制作spring-boot-starter
制作spring-boot-starter,使用的时候只需要引入依赖即可,目前功能简单,也就不需要什么配置文件了。
lm-spring-vertx-ebclient-starter
这里只做依赖管理,现在还没对版本进来管理。
<dependencies><dependency><groupId>com.luomin</groupId><artifactId>lm-spring-vertx-autoconfigure</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.luomin</groupId><artifactId>lm-spring-vertx-ebclient</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency></dependencies>
lm-spring-vertx-autoconfigure
META-INF\spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.luomin.autoconfig.ebclient.EbClientAutoConfiguration
自动配置类
@Import(AutoConfiguredVertxEBClientScannerRegister.class)
@ConditionalOnClass(Vertx.class)
public class EbClientAutoConfiguration {@Bean@ConditionalOnMissingBean(Vertx.class)public Vertx vertx() throws Exception {ClusterManager mgr = new HazelcastClusterManager();VertxOptions options = new VertxOptions().setClusterManager(mgr);return AwaitUtil.awaitResult(resultHandler -> Vertx.clusteredVertx(options, resultHandler));}}
自动路径扫描配置类
public class AutoConfiguredVertxEBClientScannerRegister implements BeanFactoryAware, ImportBeanDefinitionRegistrar {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(VertxEBProxyBeanDefinitionRegistryPostProcessor.class);builder.addPropertyReference("vertx", "vertx");List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);registry.registerBeanDefinition(beanName, beanDefinition);}}