Vert.x 类似Spring Controller模式编写接口

编程教程 > Java > Vert.x (297) 2025-04-17 11:34:43

前言

前面已经讲述了在vertx中,如何实现服务的注册和使用,这里主要讲解接口的暴露层,在vertx中如何实现类似Spring MVC的Controller层。项目源码引用《Vert.x 4 服务代理(Service Proxy)》进行修改

 

项目结构

Vert.x 类似Spring Controller模式编写接口_图示-15afc5386a014a1082b3e8c5d19b543d.png
项目核心结构

控制层实现说明

服务代理回顾

之前把代理服务注册放到MainVerticle里面,这里吧它独立出来,创建了一个ServiceRegistryVerticle

代码示例:

@Slf4j
public class ServiceRegistryVerticle extends AbstractVerticle {

/**
* 扫描的service包
*/
private final String servicePackages;

public ServiceRegistryVerticle(String servicePackages) {
Objects.requireNonNull(servicePackages, "servicePackages is null");
this.servicePackages = servicePackages;
}

@SuppressWarnings("unchecked")
@Override
public void start(Promise<Void> startPromise) throws Exception {
Set<Class<? extends AbstractAsyncService>> services =
ReflectionUtils.getReflections(servicePackages).getSubTypesOf(AbstractAsyncService.class);
ServiceBinder serviceBinder = new ServiceBinder(VertxHolder.getVertx());
if (!services.isEmpty()) {
try {
for (Class<? extends AbstractAsyncService> service : services) {
AbstractAsyncService serviceInstance = service.getDeclaredConstructor().newInstance();
//AbstractAsyncService#registerAddress获取注册地址/ID
//取巧:注册地址=接口全名
String registerAddress = (String) service.getMethod("registerAddress").invoke(serviceInstance);
//AbstractAsyncService#registerInterface获取注册类
Class registerInterfaceClass = (Class) service.getMethod("registerInterface").invoke(serviceInstance);
//注册
serviceBinder
.setAddress(registerAddress)
.register(registerInterfaceClass, serviceInstance);
}
} catch (Exception e) {
log.error("registerServices error : {}", e.getMessage());
}
}
log.info("All async services are registered successfully");
startPromise.complete();
}
}

 

代码内容和之前《Vert.x 4 服务代理(Service Proxy)》里面的基本一样,就是独立了一个Verticle出来,参数为扫描的service包路径。

 

类似Controller实现

 

创建用于路由的注解

@RouterHandler 

用于标记哪些类是用于路由处理的。类似spring的@Controller

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterHandler {

/**
* api 前缀地址
* @return 前缀地址
*/
String value() default "";

/**
* 注册顺序,数字越小越先注册
*/
int order() default 0;
}

 

@RouterMapping 

用于标记哪些方法具体处理什么路由路径,类似spring的@RequestMapping

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RouterMapping {
/**
* 路由地址
* @return 路由地址
*/
String value();

/**
* 请求方法类型
* @return 方法
*/
RouterMethod method() default RouterMethod.GET;
/**
* 是否覆盖
* @return 是否覆盖
*/
boolean overwrite() default true;

/**
* 接口描述
* @return 接口描述信息
*/
String desc() default "";

/**
* 注册顺序,数字越小越靠前
* @return 注册顺序号
*/
int order() default 0;

/**
* mineType 类型(HTTP - Content-Type)字符串,如:application/json;utf-8
* @return mineType
*/
String consumes() default "";

String produces() default "";
}

方法枚举

public enum RouterMethod {

GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, CONNECT, PATCH,ROUTE
}

 

RouterHandlerFactory

用于反射注册所有路由handler

[user]

 

/**
* 路由处理工厂
* <p>
* 用于初始化router
* </p>
*/
@Slf4j
public class RouterHandlerFactory {

/**
* 扫描的router路径
*/
private static volatile Reflections reflections;

/**
* 默认路由前缀
*/
private static final String DEFAULT_ROUTER_PREFIX = "/";
/**
* 设置路由前缀
*/
private volatile String routerPrefix = DEFAULT_ROUTER_PREFIX;

public RouterHandlerFactory(String routerScanPackages) {
Objects.requireNonNull(routerScanPackages, "routerScanPackages must not be null");
reflections = ReflectionUtils.getReflections(routerScanPackages);
}

public RouterHandlerFactory(String routerScanPackages,String routerPrefix) {
Objects.requireNonNull(routerScanPackages, "routerScanPackages must not be null");
reflections = ReflectionUtils.getReflections(routerScanPackages);
if (!StringUtils.isEmpty(routerPrefix)) {
this.routerPrefix = routerPrefix;
}
}

public Router createRouter(){
Vertx vertx = VertxHolder.getVertx();
Router router = Router.router(vertx);
return createRouter(router);
}

public Router createRouter(Router router) {
router.route()
.handler(routingContext -> {
//日志记录
log.info("request path:{},uri:{},method:{}", routingContext.request().path(), routingContext.request().uri(), routingContext.request().method());

routingContext.response()
//设定默认返回头
.putHeader("content-type", "application/json; charset=utf-8")
//设定跨域
.putHeader("access-control-expose-headers", "*")
.putHeader("access-control-allow-origin", "*")
.putHeader("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
.putHeader("access-control-max-age", "3600");
//调用下一个
routingContext.next();
});

//跨域再次设置
router.route().handler(CorsHandler.create().addOrigin("*").allowedMethods(new HashSet<>(HttpMethod.values())));
//body参数解析
router.route().handler(BodyHandler.create());

//通过反射注册所有标记@RouterHandler#@RouterMapping的路由信息
try {
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouterHandler.class);

//排序器
Comparator<Class<?>> comparator = (c1, c2) -> {
RouterHandler routeHandler1 = c1.getAnnotation(RouterHandler.class);
RouterHandler routeHandler2 = c2.getAnnotation(RouterHandler.class);
return Integer.compare(routeHandler1.order(), routeHandler2.order());
};
//排序再循环处理
handlers.stream().sorted(comparator).forEach(handler -> {
try {
registerNewHandler(router, handler);
} catch (Exception e){
log.error("registerNewHandler error : {}", handler,e);
}
});


} catch (Exception e) {
log.error("router factory register error", e);
throw new RuntimeException(e);
}
return router;
}

@SuppressWarnings("unchecked")
private void registerNewHandler(Router router,Class<?> handlerClass) throws Exception {
String path = routerPrefix;
path = path.startsWith("/") ? path : "/"+path;
path = path.endsWith("/") ? path : path+"/";

if (handlerClass.isAnnotationPresent(RouterHandler.class)){
RouterHandler routerHandler = handlerClass.getAnnotation(RouterHandler.class);
path = path + routerHandler.value();
}

Object instance = handlerClass.getDeclaredConstructor().newInstance();
Method[] methods = handlerClass.getMethods();
//方法排序
Comparator<Method> comparator = (m1, m2) -> {
RouterMapping mapping1 = m1.getAnnotation(RouterMapping.class);
RouterMapping mapping2 = m2.getAnnotation(RouterMapping.class);
return Integer.compare(mapping1.order(), mapping2.order());
};
//赛选/排序/循环处理
List<Method> methodList = Stream.of(methods).filter(method -> method.isAnnotationPresent(RouterMapping.class)).sorted(comparator).toList();
for (Method method : methodList) {
// method.setAccessible(true);
RouterMapping routerMapping = method.getAnnotation(RouterMapping.class);
String routerUrl = routerMapping.value();
RouterMethod routerMethod = routerMapping.method();
if (routerUrl.startsWith("/:")){
routerUrl = method.getName() + routerMapping.value();
}else{
routerUrl = routerUrl.endsWith(method.getName())?routerUrl:(routerMapping.overwrite()?routerUrl:routerUrl+method.getName());
routerUrl = routerUrl.endsWith("/")?routerUrl.substring(1):routerUrl;
}

String fullPath = path + routerUrl;
fullPath = fullPath.replaceAll("//","/");
Handler<RoutingContext> handlerMethod = (Handler<RoutingContext>)method.invoke(instance);
String consumes = routerMapping.consumes();
log.info("Register New Handler -> method:{},url:{},type:{}",routerMapping.method(),fullPath,routerMapping.consumes());

Route route = switch (routerMethod) {
case POST -> router.post(fullPath);
case PUT -> router.put(fullPath);
case DELETE -> router.delete(fullPath);
case OPTIONS -> router.options(fullPath);
case HEAD -> router.head(fullPath);
case PATCH -> router.patch(fullPath);
case TRACE -> router.trace(fullPath);
case CONNECT -> router.connect(fullPath);
case ROUTE -> router.route(fullPath);
default -> router.get(fullPath);
};

if (!StringUtils.isEmpty(consumes)) {
route.consumes(consumes);
}
route.handler(handlerMethod);
}

}

}

 

[/user]

 

 

UserApi

用户接口,对外暴露接口的类,类似Spring的Controller类

@RouterHandler(value = "/api/user")
public class UserApi {

//获取动态代理的service
private UserService userService=AsyncServiceUtils.getAsyncServiceInstance(UserService.class);

@RouterMapping(value = "/getUserById/:id")
public Handler<RoutingContext> findById() {
return ctx ->{
String id = ctx.pathParam("id");
userService.getUserById(Integer.parseInt(id),handler->{
if(handler.succeeded()){
JsonObject result = handler.result();
ctx.json(result);
}else {
ctx.fail(handler.cause());
}
});
};
}
}
 
RouterRegistryVerticle

用于注册路由到vertx和创建http server

/**
* 注意必须先注册服务类再注册本类
* 路由构建发布以及HTTP服务启动
*/
@Slf4j
public class RouterRegistryVerticle extends AbstractVerticle {

/**
* 路由handler类包名
*/
String routerPackages;

HttpServer server;

public RouterRegistryVerticle(String routerPackages) {
this.routerPackages = routerPackages;
}

@Override
public void start(Promise<Void> startPromise) throws Exception {
String contextPath = config().getString("contextPath", "/");
Integer port = config().getInteger("port", 8080);
Router router = new RouterHandlerFactory(routerPackages,contextPath).createRouter();

HttpServerOptions options=new HttpServerOptions();

options.setPort(port);
server = vertx.createHttpServer(options).requestHandler(router).listen(ar -> {
if (ar.succeeded()) {
log.info("All router handler are registered successfully and http server listening on port {}", ar.result().actualPort());
startPromise.complete();
} else {
startPromise.fail(ar.cause());
}
});
}

@Override
public void stop(Promise<Void> stopPromise) throws Exception {
if (Objects.isNull(server)) {
stopPromise.complete();
}
server.close(voidAsyncResult -> {
if (voidAsyncResult.succeeded()) {
stopPromise.complete();
}else{
stopPromise.fail(voidAsyncResult.cause());
}
});
}
}

 

MainVerticle

核心启动类

@Slf4j
public class MainVerticle extends AbstractVerticle {



@Override
public void start(Promise<Void> startPromise) throws Exception {
//初始化
VertxHolder.init(vertx);
//注册服务
vertx.deployVerticle(new ServiceRegistryVerticle(config().getString("servicePackages","com.demo.vertx.vertx_demo.service"))).onSuccess(handler -> {
vertx.deployVerticle(new RouterRegistryVerticle(config().getString("routerPackages","com.demo.vertx.vertx_demo")));
});

}

}

提示:注意verticle之间的部署启动顺序

使用MainVerticle启动项目

io.vertx.core.Launcher run com.demo.vertx.vertx_demo.MainVerticle

观察控制台

Vert.x 类似Spring Controller模式编写接口_图示-2404490d00df48149886263171ea9966.png
控制台输出日志

可以看到输出日志内容与我们预期结果一致,接下来使用postman工具访问接口测试

Vert.x 类似Spring Controller模式编写接口_图示-fbbf364c95244b3aa20f308728263eb4.png

观察测试返回,与预期结果一致。

 

扩展思维

组合前面所学至此可以完整的开发项目了。

 

完整代码

VIP资源,登录后查阅

此处内容需要开通会员后查阅(刷新)

登录/注册

 

 

 


评论
User Image
提示:请评论与当前内容相关的回复,广告、推广或无关内容将被删除。

相关文章
前言项目由之前的第一个vert.x 4 项目改编而来,vert.x项目创建参考: vert.x 4 web应用编写修改MainVerticle文件,内容如下:p
Vert.x java 入门,Vert.x这个框架在常规的web业务开发中估计还不是很成熟。但是了解了他的一些原理我觉得可以有一番作为。今天主要简单讲解下eclipse Vert.x是什么有什么...
前言最新的Ver.x 4 从idea工具运行启动,并访问。项目创建通过vert.x官网生成器完成。Vert.x 4项目创建打开vert.x官网项目生成地址,ht
前言这里主要讲解Vert.x配置文件的读取和使用,引用之前的项目《Vert.x 4 Web应用初识》 引入必要依赖maven pom.xml&lt;dependenc
前言Vert.x 中实现全局数据共享,如环境参数等。Vertx数据共享实现共享数据存入/更新SharedData sharedData = vertx.shar
前言项目创建参考之前的《Vert.x 4 Web应用初识》。本文通过Vert.x实现了REST接口的CRUD操作。通过本教程你可以获得以下内容vert.x项目中
前言Vert.x Router路由中多个处理器之间通过RoutingContext共享数据实现。 Vert.x Router 上下文数据数据设置routingC
前言前面已经讲述了在vertx中,如何实现服务的注册和使用,这里主要讲解接口的暴露层,在vertx中如何实现类似Spring MVC的Controller层。项
前言文件上传在web应用比较常见,本文以vert.x web实现文件上传功能。引用之前的项目《Vert.x 4 Web应用初识》作为基础,添加了日志。 Vert
前言vert.x 默认是没有像spring的依赖注入的,需要自己结合vertx-service-proxy插件实现。本文引用项目为基础《Vert.x 4 Web
前言创建一个常规的web项目肯定需要一个模板引擎来实现,引用之前的项目《Vert.x 4 Web应用初识》基础结构目前vert.x支持多款模板引擎,包括:MVE
前言本文主要讲解在Vert.x环境下与Mysql数据库连接和操作,实现基础的增删改查和事务,以及REST接口返回数据库中查询结果。项目引用之前的《Vert.x
前言Web项目开发一般接口入参都有校验需求,Vert.x 4 目前已有插件实现参数校验,引用之前的项目《Vert.x 4 Web应用初识》源码 引入验证插件ma
前言引用《Vert.x 4 Web REST CRUD接口应用》项目,加入日志依赖并编码实现类似Interceptor功能处理。vert.x日志集成参考《Ver
前言接上一篇《Vert.x 4 Web应用初识》,在web应用中除了访问地址得到动态的信息还有静态的资源库访问,如 jQuery / bootstrap 等前端