Spring Boot Admin (一) 请求处理原理

Spring Boot Admin (一) 请求处理原理_第1张图片

题图:  北京海洋馆里的水母

在以前的文章里,曾经介绍过一种在嵌入式的 Tomcat 里部署其他应用的方式(如何给Spring Boot 的嵌入式 Tomcat 部署多个应用?)。通过这种方式,我们可以把 Psi-Probe 这一类管理、监控应用部署到 Spring Boot 应用的嵌入容器中,这样可以更清晰的了解容器的内部资源使用、应用的请求处理等情况。当然也可以将 Spring Boot 应用打包成 WAR 文件的形式,和传统应用一样,在一个容器内部署多类应用来进行监控。

也写过一篇通过 Actuator 来了解 应用内Bean 的引用关系、各类Mapping信息的文章(一览Spring Bean 定义和映射全貌的神器)。

但是,第一种形式在 Spring Boot 的 「Starter」大行其道的今天,使用方式上并不一致。

这次我们介绍一款工具,结合了上面两部分的内容,也不需要像自己添加嵌入容器的部署那样,单独进行开发,而是在 maven 里添加 对应的依赖,在 Spring Boot 应用的 application 配置文件中增加部分配置就可以实现。使用习惯上和 Spring Boot的一贯形式一样,开箱之后,稍加配置就可以使用。

这就是我们今天要介绍的主角:Spring Boot Admin, 官方文档里又简称为 SBA。

使用

上手也比较简单,在我们的 Spring Boot 应用中,增加 SBA 的依赖。

说明:这里有一点需要注意的是, SBA的版本一定要和 Spring Boot 的版本匹配。比如 Spring Boot 2.0.x 对应的 SBA 也是 2.0.x 如果是2.1.x,那大家也都用2.1.x。

由于 Spring Boot Admin 又分为 Server 和 Client 两个部分,所以配置这一部分我们分开来说。

Server 会做为响应 Client 信息注册以及最终各类应用信息服务信息查看的应用入口,和一个独立Web服务一样。配置的时候 增加依赖如下:


    de.codecentric
    spring-boot-admin-starter-server
    2.1.3


之后在 Spring Boot 的应用主类中增加注解@EnableAdminServer,然后正常启动。之后在浏览器里请求就能看到Admin的管理界面。

这个时候没有对应的节点,所以信息都是空的。

接着配置一个 Client ,注册过来就能看到信息。

增加依赖


    de.codecentric
    spring-boot-admin-starter-client
    2.1.3


配置文件里,需要指定要注册到的 Server是谁,同时为了不同应用之间的区分,起个名字。最后再把actuator的信息开关打开。

spring.application.name=Spring Boot Client
spring.boot.admin.client.url=http://localhost:8000
management.endpoints.web.exposure.include=*

如果要同时查看Actuator提供的信息,把 actuator 的依赖也一并加上。 启动之。

然后我们就能看到 SBA Server 里注册的实例信息。

点进去可以查看对应实例的信息。

Spring Boot Admin (一) 请求处理原理_第2张图片

Spring Boot Admin (一) 请求处理原理_第3张图片

Server 启动原理

一般看到一个有意思的开源项目,我会看看其中部分特性的实现。 这次关于 SBA 我们先来说下 Server 的启动原理。

首先做为一个Starter,和其他的都类似,也是从一个AutoConfiguration做为入口的 然后,会加载其他的配置文件。

这是 Server 的 AutoConfiguration,内容如下:

@Configuration
@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties(AdminServerProperties.class)
@Import({AdminServerWebConfiguration.class})


这里有一个AdminServerWebConfiguration。 我们看看里面定义了什么。

 @Bean
    @ConditionalOnMissingBean
    public InstancesController instancesController(InstanceRegistry instanceRegistry, InstanceEventStore eventStore) {
        return new InstancesController(instanceRegistry, eventStore);
    }


    @Bean
    @ConditionalOnMissingBean
    public ApplicationsController applicationsController(InstanceRegistry instanceRegistry,
                                                         InstanceEventPublisher eventPublisher) {
        return new ApplicationsController(instanceRegistry, eventPublisher);
    }

我们看到,这里声明了两个Controller 的Bean。不过别急,这里作用虽和 SpringMVC 的类似,不过声明和创建略有不同,我们在开发项目时,也可以参考学习一下。

接着在该类向下看,你会发现有个HandlerMapping的处理Bean

@Bean
public org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping adminHandlerMapping(
    ContentNegotiationManager contentNegotiationManager) {
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping mapping = new de.codecentric.boot.admin.server.web.servlet.AdminControllerHandlerMapping(
        adminServerProperties.getContextPath());
    mapping.setOrder(0);
    mapping.setContentNegotiationManager(contentNegotiationManager);
    return mapping;
}


具体内容在 AdminControllerHandlerMapping类里。比较关键的一句话在这

   @Override
    protected boolean isHandler(Class beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, AdminController.class);
    }


也就是包含 AdminController这个注解的,会被识别为 Handler。 我们再看上面注册的那个ApplicationsController,果然也是。

@AdminController
@ResponseBody
public class ApplicationsController {
    ...
    @GetMapping(path = "/applications", produces = MediaType.APPLICATION_JSON_VALUE)
    public Flux applications() {
        return registry.getInstances()
                       .filter(Instance::isRegistered)
                       .groupBy(instance -> instance.getRegistration().getName())
                       .flatMap(grouped -> toApplication(grouped.key(), grouped));
    }


}



我们一般是通过注解@Controller 或者 @RestController 来声明一个 Controller 的,这里的「@AdminController」并不是 Spring 内置的注解,但也依然能做为 Handler生效,上面就是一个原因。 另一个是识别出是个 Handler 之后,把对应方法注册到 Handler 列表里。

protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    super.registerHandlerMethod(handler, method, withPrefix(mapping));
}

完整点的识别注册路径如下:

    /**
     * Determine the type of the specified candidate bean and call
     * {@link #detectHandlerMethods} if identified as a handler type.
     * 

This implementation avoids bean creation through checking * {@link org.springframework.beans.factory.BeanFactory#getType} * and calling {@link #detectHandlerMethods} with the bean name. * @param beanName the name of the candidate bean * @since 5.1 * @see #isHandler * @see #detectHandlerMethods */ protected void processCandidateBean(String beanName) { Class beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }

这是AbstractHandlerMethodMapping里的一段,用于判断是否要注册成HandlerMethod,找到之后就注册成HandlerMethod

    /**
     * Look for handler methods in the specified handler bean.
     * @param handler either a bean name or an actual handler instance
     * @see #getMappingForMethod
     */
    protected void detectHandlerMethods(Object handler) {
        Class handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());


        if (handlerType != null) {
            Class userType = ClassUtils.getUserClass(handlerType);
            Map methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }


对应的,在AdminControllerHandlerMapping里,isHandler的处理,也简单直接,只要看看有没有AdminController的注解

    @Override
    protected boolean isHandler(Class beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, AdminController.class);
    }


之后就和其他的 Spring MVC 过程类似,添加到Registry里,在请求时,会从所有的HandlerMapping里找, 这里对应的请求会找到 AdminControllerHandlerMapping,从中再找出对应的执行方法。

所以具体到我们请求 Server 里对应的 实例列表时,就直接调用到 Controller 里的方法了。


比如对于应用的列表

@GetMapping(path = "/applications", produces = MediaType.APPLICATION_JSON_VALUE)
    public Flux applications() {
        return registry.getInstances()
                       .filter(Instance::isRegistered)
                       .groupBy(instance -> instance.getRegistration().getName())
                       .flatMap(grouped -> toApplication(grouped.key(), grouped));
}


应用列表就直接从 registry 这个注册的地方来获取,所以可以想像 Client 在启动之后,应该是直接向这里注册了信息。 我们看到这里使用了 Spring 5 的 WebFlux, 还包含一些 Reactive Stream 的内容。

下一篇文章,我们再分析下这些内容。

更多常见问题,请关注公众号,在菜单「常见问题」中查看。

源码|实战|成长|职场

这里是「Tomcat那些事儿」

请留下你的足迹

我们一起「终身成长」

识别二维码,关注我

                                                                  如有帮助,就给我一个“好看”吧Spring Boot Admin (一) 请求处理原理_第4张图片

你可能感兴趣的:(java,spring,spring,boot,docker,tomcat)