5. 路由配置+接口发布
在上一节中我们也提到了使用递归遍历的方式将所有的 AbstractVerticle 类的继承实现给部署到服务里面,如下图:
...
List<Class<Verticle>> clazzSet = ReflectUtil.getVerticleClasses(CommonConstants.ROOT_LEVEL, true);
List<CompletableFuture<Void>> futures =
clazzSet.stream().filter(clazz -> !clazz.getSimpleName().contains(VTX_VERTICLE))
.map(clazz -> CompletableFuture.runAsync(() -> vtx.deployVerticle(clazz, deploymentOptions)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
...
而我们的路由配置类也继承了 AbstractVerticle 的因此也会被自动部署,如下图:
public class RouterConfig extends AbstractVerticle {
private static final Logger LOGGER = LogManager.getLogger(RouterConfig.class);
private Set<HttpMethod> hmSet = new HashSet<>();
// 服务端口
@PropLoader(key = "server.port")
private int port;
// 接口 header 设定
@PropLoader(key = "server.http.header")
private List<String> httpHeader;
/**
*
* @MethodName: setupHttpMethod
* @Description: 设定服务支持那些 http 方法
* @author yuanzhenhui void
* @date 2023-07-17 09:26:46
*/
@PostConstruct
public void setupHttpMethod() {
hmSet.add(HttpMethod.GET);
hmSet.add(HttpMethod.POST);
hmSet.add(HttpMethod.DELETE);
hmSet.add(HttpMethod.PATCH);
hmSet.add(HttpMethod.OPTIONS);
hmSet.add(HttpMethod.PUT);
}
/**
*
* @MethodName: setupAllowedHeaders
* @Description: 允许访问的头属性
* @author yuanzhenhui
* @return Set<String>
* @date 2023-04-13 04:28:45
*/
private Set<String> setupAllowedHeaders() {
Set<String> allowedHeaders = new HashSet<>();
if (httpHeader != null) {
allowedHeaders.addAll(httpHeader);
}
return allowedHeaders;
}
/**
*
* @MethodName: start
* @Description: 配置类启动
* @author yuanzhenhui
* @throws Exception
* @see io.vertx.core.AbstractVerticle#start()
* @date 2023-04-13 04:26:30
*/
@Override
public void start() throws Exception {
// -----------------------------------------------------------------------------------
// 要使用 @PropLoader 注解必须使用 YamlUtil.propLoadSetter 方法预设,以便通过反射获取类中信息 -
// -----------------------------------------------------------------------------------
YamlUtil.propLoadSetter(this);
// --------------------------------
// 传入 vertx 对象并创建 Router 实例 -
// --------------------------------
Router router = Router.router(vertx);
// -------------------------------------------------
// 通过传入 router 实例实现接口绑定和创建当前 http 服务端 -
// -------------------------------------------------
createInterface(router);
createServer(router);
}
/**
*
* @MethodName: createInterface
* @Description: 创建接口
* @author yuanzhenhui
* @param router
* void
* @date 2023-04-13 04:26:44
*/
private void createInterface(Router router) {
router.route().handler(BodyHandler.create());
// ------------
// 创建跨域设置 -
// ------------
CorsHandler cors = CorsHandler.create("*");
cors.allowedHeaders(setupAllowedHeaders());
cors.allowedMethods(hmSet);
router.route().handler(cors);
router.route().consumes(CommonConstants.HTTP_APPLICATION_JSON);
router.route().produces(CommonConstants.HTTP_APPLICATION_JSON);
// ---------------------------------------------------------------------------------------------------------
// 这里采用了多线程并行处理的方式对接口进行发布绑定处理:
// 1. 之所以采用 CopyOnWriteArraySet 处理是因为本项目是以功能表来划分实现类的(毕竟 spring 中毒有点深)。
// 因此表越多实现类也会越多。而每个实现类之间并没有加载的先后顺序关系,因此采用多线程“并行流(parallelStream)”来处理;
// 2. CommonConstants.ROOT_LEVEL 是项目根目录路径,也就是说 ReflectUtil.getClasses 将从根目录开始递归扫描获取所有类;
// 3. 通过 interfaces.contains(RouterSet.class) 来判断当前类是否实现 RouterSet 接口;
// 4. 若是,则通过策略模式对所有实现 router4Restful 方法内容进行发布绑定;
// ---------------------------------------------------------------------------------------------------------
CopyOnWriteArraySet<Class<?>> clazzSet = ReflectUtil.getClasses(CommonConstants.ROOT_LEVEL, true);
clazzSet.parallelStream().forEach(clazz -> {
Set<Class<?>> interfaces = new HashSet<>(Arrays.asList(clazz.getInterfaces()));
if (interfaces.contains(RouterSet.class)) {
try {
RouterSet rs = (RouterSet)clazz.newInstance();
rs.router4Restful(router);
} catch (Exception e) {
LOGGER.error("func[RouterConfig.createInterface] Exception [{} - {}] stackTrace[{}] ",
new Object[] {e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace())});
}
}
});
}
/**
*
* @MethodName: createServer
* @Description: 创建服务器接口
* @author yuanzhenhui
* @param router
* void
* @date 2023-04-13 04:28:29
*/
private void createServer(Router router) {
// ---------------------------------------------------
// 这里是创建 http server 服务,可自主定义了端口、ssl 等设定
// ---------------------------------------------------
vertx.createHttpServer(new HttpServerOptions().setSsl(false)).requestHandler(router::accept).listen(port,
asyncResult -> {
if (asyncResult.failed()) {
LOGGER.error("func[RouterConfig.createServer] Exception [{} - {}]",
new Object[] {asyncResult.cause(), asyncResult.result()});
} else {
LOGGER.info(" ----- All RESTful interfaces are successfully registered! ----- ");
}
});
}
}
上面说到的一个重点就是通过策略模式进行接口的发布绑定,具体就是这样做的。先定义一个接口,如下图:
public interface RouterSet {
/**
*
* @MethodName: router4Restful
* @Description: restful路由
* @author yuanzhenhui
* @param router
* void
* @date 2023-04-13 04:30:55
*/
public void router4Restful(Router router);
}
然后在接口实现类中进行实现即可,如下图:
public class SysUserBuriedRouter extends AbstractVerticle implements RouterSet {
private static final Logger LOGGER = LogManager.getLogger(SysUserBuriedRouter.class);
// 系统上下文名称
@PropLoader(key = "server.context")
private static String context;
...
@Override
public void start() {
YamlUtil.propLoadSetter(this);
...
}
/**
*
* @MethodName: sendBuriedPointInfo
* @Description: restful处理类
* @author yuanzhenhui
* @param ctx
* void
* @date 2023-04-13 05:02:52
*/
public void sendBuriedPointInfo(RoutingContext ctx) {
...
}
/**
*
* @MethodName: router4Restful
* @Description: 实现路由转发
* @author yuanzhenhui
* @param router
* @see io.kida.components.routers.RouterSet#router4Restful(io.vertx.ext.web.Router)
* @date 2023-04-13 05:03:12
*/
@Override
public void router4Restful(Router router) {
router.post(CommonConstants.HTTP_SLASH + context + CommonConstants.HTTP_SLASH + "sendBuriedPointInfo")
.handler(this::sendBuriedPointInfo);
}
}
可以看到 SysUserBuriedRouter 类重写了 RouterSet 接口的 router4Restful 方法,通过策略模式传入的 Router 对象可以直接用于设定接口。router.post 制定了接口的请求方法,而后面的 handler 就是指定了接口的处理类。
通过这种方式就可以实现自动接口扫描和发布,RouterConfig.java 类就不再需要进行编码处理,往后增加或者删除接口都只需要关注对应接口类中的 router4Restful 方法即可。