SpringBoot Vert.x 三(注解自动注册Controller)

其实在SpringBoot的web开发过程中,我们只需要给Controller类 添加@Controller @RestController的注解 就可以实现 让SpringBoot扫描到Controller 并且注册路由。

但是Vertx 需要主动注册的话,无疑会增加代码复杂度、代码的可读性 。接下来继续改造过程 (这东西还是挺麻烦的,还是用SpringBoot Web比较快速....)

到目前为止 我们已经知道 在Vertx注册api路由的时候,它所需要的是一个Handler的接口 实现类

那么我们可以这么做,新建一个Controller 该Controller里面再写各个方法 add 、delete ....然后每个方法返回Handler

例:

public class TestUserController {
  
    public ControllerHandler userInfo() {
       //这里不要写代码   不然这里的代码  只会在注册路由的时候  被调用一次
        return vertxRequest -> {
           //接口所执行的逻辑代码一定要写到这里
            Map<String, String> map = new HashMap<>();
            map.put("name", "大白");
            map.put("age", "18");
            vertxRequest.buildVertxRespone().responeSuccess(map);
        };
    }
}

VerticleMain 再次改造

@Slf4j
public class VerticleMain extends AbstractVerticle {


    @Override
    public void start() throws Exception {
        super.start();
        TestUserController testUserController = new TestUserController();
        //路由
        Router router = Router.router(vertx);
        //编写一个get方法
        router.get("/api/user/userInfo").handler(testUserController.userInfo());
        //start listen port
        HttpServer server = vertx.createHttpServer();
        server.requestHandler(router).listen(8888, handler -> {
            log.info("vertx run prot : [{}] run state : [{}]", 8888, handler.succeeded());
        });
    }
}

尝试访问 /api/user/userInfo api路径

QQ20200131-215655@2x

如上 证明想法是可行的,但是我们显然不能这么去开发,第一个,这样的话,用不上SpringBoot的ioc容器,第二个,大量的Controller的话 这个VerticleMain 会很臃肿

接下来 继续改造成ioc容器

第一步改造 Controller 、 VerticleMain 增加 @Component 注解

@Component
public class TestUserController {
....

改造 VerticleMain

@Component
@Slf4j
public class VerticleMain extends AbstractVerticle {
    @Autowired
    TestUserController testUserController;

    @Override
    public void start() throws Exception {
        super.start();
        //路由
        Router router = Router.router(vertx);
        //编写一个get方法
        router.get("/api/user/userInfo").handler(testUserController.userInfo());
        //start listen port
        HttpServer server = vertx.createHttpServer();
        server.requestHandler(router).listen(8888, handler -> {
            log.info("vertx run prot : [{}] run state : [{}]", 8888, handler.succeeded());
        });
    }
}

改造SpringBoot 启动器 这时候已经可以重新启动 查看效果了

@Slf4j
@SpringBootApplication
public class VertxdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(VertxdemoApplication.class, args);
    }

    /**
     * 监听SpringBoot 启动完毕 开始部署Vertx
     *
     * @param event
     */
    @EventListener
    public void deployVertx(ApplicationReadyEvent event) {
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        VerticleMain verticleMain = applicationContext.getBean(VerticleMain.class);
        Vertx vertx = Vertx.vertx();
        //部署vertx
        vertx.deployVerticle(verticleMain, handler -> {
            log.info("vertx deploy state [{}]", handler.succeeded());
        });
    }
}

第二步改造:

显然 在VerticleMain 里面去注入各个Controller也不太合理 所以继续改造

第二步改造思路: 我们扫描Controller所在的包,扫描出所有的Controller类 然后再通过ioc 获取Bean

最后调用Controller里的方法 获得Handler接口 并且注册到vertx

新增SpringBootContext 类 用来存储 SpringBoot 上下文环境

public class SpringBootContext {
    private static ConfigurableApplicationContext applicationContext;

    public static ConfigurableApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static void setApplicationContext(ConfigurableApplicationContext applicationContext) {
        SpringBootContext.applicationContext = applicationContext;
    }
}

新增枚举

public enum RequestMethod {
    OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PATCH, ROUTE;
}

新增注解 RequestMapping 用来替代SpringBoot里的RequestMapping 注解

/**
 * @author pencilso
 * @date 2020/1/23 10:27 下午
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    /**
     * api path
     *
     * @return
     */
    String[] value() default {};

    /**
     * request method
     *
     * @return
     */
    RequestMethod method() default RequestMethod.GET;
}

新增VerticleUtils

@Validated
public class VerticleUtils {

    /**
     * build api path
     *
     * @param superPath  class api path
     * @param methodPath method api path
     * @return
     */
    public static String buildApiPath(@NotNull String superPath, @NotNull String methodPath) {
        if (!superPath.startsWith("/")) {
            superPath = "/" + superPath;
        }
        if (!superPath.endsWith("/")) {
            superPath += "/";
        }
        if (methodPath.startsWith("/")) {
            methodPath = methodPath.substring(1);
        }
        return superPath + methodPath;
    }

    /**
     * build route api path method
     *
     * @param url           api path
     * @param router        router
     * @param requestMethod method enum
     * @return
     */
    public static Route buildRouterUrl(String url, Router router, RequestMethod requestMethod) {
        //路由
        Route route;
        switch (requestMethod) {
            case POST:
                route = router.post(url);
                break;
            case PUT:
                route = router.put(url);
                break;
            case DELETE:
                route = router.delete(url);
                break;
            case ROUTE:
                route = router.route(url);
                break;
            case GET: // fall through
            default:
                route = router.get(url);
                break;
        }
        return route;
    }


    /**
     * scanner controller class array
     *
     * @param packagePath    controller package
     * @param resourceLoader SpringBoot  resourceLoader
     * @return
     */
    public static Resource[] scannerControllerClass(String packagePath, ResourceLoader resourceLoader) {
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        String controllerBasePackagePath = packagePath.replace(".", "/");
        try {
            return resolver.getResources(String.format("classpath*:%s/**/*.class", controllerBasePackagePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new Resource[]{};
    }

}

做完这些后 改造SpringBoot启动器 SpringBoot启动后 把上下文环境存起来

QQ20200131-222629@2x

改造Controller 增加api路径注解 @RequestMapping

注: Controller一定要加注解 @Component 否则ioc将会找不到该Bean

@RequestMapping("api/user")
@Component
public class TestUserController {

    @RequestMapping("userInfo")
    public ControllerHandler userInfo() {
        return vertxRequest -> {
            Map<String, String> map = new HashMap<>();
            map.put("name", "大白");
            map.put("age", "18");
            vertxRequest.buildVertxRespone().responeSuccess(map);
        };
    }
}

再次改造 VerticleMain 让其自动扫描 Controller类

@Component
@Slf4j
public class VerticleMain extends AbstractVerticle {
    @Autowired
    private ResourceLoader resourceLoader;
    /**
     * Controller 所在的包
     */
    private final String controllerBasePackage[] = {
            "io.javac.vertx.vertxdemo.controller"
    };


    @Override
    public void start() throws Exception {
        super.start();
        //路由
        Router router = Router.router(vertx);
        //编写一个get方法
        for (String packagePath : controllerBasePackage) {
            registerController(router, packagePath);
        }
        //start listen port
        HttpServer server = vertx.createHttpServer();
        server.requestHandler(router).listen(8888, handler -> {
            log.info("vertx run prot : [{}] run state : [{}]", 8888, handler.succeeded());
        });
    }


    /**
     * register controller
     */
    private void registerController(@NotNull Router router, String packagePath) {
        if (SpringBootContext.getApplicationContext() == null) {
            log.warn("SpringBoot application context is null register controller is fail");
            return;
        }

        try {
            Resource[] resources = VerticleUtils.scannerControllerClass(packagePath, resourceLoader);
            for (Resource resource : resources) {
                String absolutePath = resource.getFile().getAbsolutePath().replace("/", ".");
                absolutePath = absolutePath.substring(absolutePath.indexOf(packagePath));
                absolutePath = absolutePath.replace(".class", "");
                if (StringUtils.isEmpty(absolutePath)) continue;
                //get class
                Class<?> controllerClass = Class.forName(absolutePath);
                //from class get controller instance bean
                Object controller = SpringBootContext.getApplicationContext().getBean(controllerClass);

                RequestMapping classRequestMapping = controllerClass.getAnnotation(RequestMapping.class);
                //if controller class not have requestMapping annotation -> skip register
                if (classRequestMapping == null) continue;
                //register controller method
                registerControllerMethod(router, classRequestMapping, controllerClass, controller);
            }
        } catch (Exception ex) {
            log.error("registerController fail ", ex);
        }
    }

    /**
     * register controller method
     *
     * @param router              route
     * @param classRequestMapping controller requestMapping annotation
     * @param controllerClass     controller class
     * @param controller          controller instance
     */
    private void registerControllerMethod(@NotNull Router router, @NotNull RequestMapping classRequestMapping, @NotNull Class<?> controllerClass, @NotNull Object controller) {
        //获取控制器里的方法
        Method[] controllerClassMethods = controllerClass.getMethods();
        Arrays.asList(controllerClassMethods).stream()
                .filter(method -> method.getAnnotation(RequestMapping.class) != null)
                .forEach(method -> {
                    try {
                        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
                        String superPath = classRequestMapping.value()[0];
                        String methodPath = methodRequestMapping.value()[0];
                        //if api path empty skip
                        if (StringUtils.isEmpty(superPath) || StringUtils.isEmpty(methodPath)) return;
                        String url = VerticleUtils.buildApiPath(superPath, methodPath);
                        //build route
                        Route route = VerticleUtils.buildRouterUrl(url, router, methodRequestMapping.method());
                        //run controller method get Handler object
                        Handler<RoutingContext> methodHandler = (Handler<RoutingContext>) method.invoke(controller);
                        route.handler(methodHandler);
                        log.info("register controller -> [{}]  method -> [{}]  url -> [{}] ", controllerClass.getName(), method.getName(), url);
                    } catch (Exception e) {
                        log.error("registerControllerMethod fail controller: [{}]  method: [{}]", controllerClass, method.getName());
                    }
                });

    }
}

再次run起来 访问api接口 不出意外的话 应该是这样的

QQ20200131-215655@2x

Github 源码