zoukankan      html  css  js  c++  java
  • 深入SpringBoot:自定义Endpoint

    前言

    上一篇文章介绍了SpringBoot的PropertySourceLoader,自定义了Json格式的配置文件加载。这里再介绍下EndPoint,并通过自定EndPoint来介绍实现原理。

    Endpoint

    SpringBoot的Endpoint主要是用来监控应用服务的运行状况,并集成在Mvc中提供查看接口。内置的Endpoint比如HealthEndpoint会监控dist和db的状况,MetricsEndpoint则会监控内存和gc的状况。
    Endpoint的接口如下,其中invoke()是主要的方法,用于返回监控的内容,isSensitive()用于权限控制。

        public interface Endpoint<T> {
            String getId();
            boolean isEnabled();
            boolean isSensitive();
            T invoke();
        }

    Endpoint的加载还是依靠spring.factories实现的。spring-boot-actuator包下的META-INF/spring.factories配置了EndpointAutoConfiguration

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    ...
    org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,
    ...

    EndpointAutoConfiguration就会注入必要的Endpoint。有些Endpoint需要外部的收集类,比如TraceEndpoint

        @Bean
        @ConditionalOnMissingBean
        public TraceEndpoint traceEndpoint() {
            return new TraceEndpoint(this.traceRepository);
        }

    TraceEndpoint会记录每次请求的Request和Response的状态,需要嵌入到Request的流程中,这里就主要用到了3个类。

    1. TraceRepository用于保存和获取Request和Response的状态。
       public interface TraceRepository {
           List<Trace> findAll();
           void add(Map<String, Object> traceInfo);
       }
    2. WebRequestTraceFilter用于嵌入web request,收集请求的状态并保存在TraceRepository中。
    3. TraceEndpointinvoke()方法直接调用TraceRepository保存的数据。
       public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
           private final TraceRepository repository;
           public TraceEndpoint(TraceRepository repository) {
               super("trace");
               Assert.notNull(repository, "Repository must not be null");
               this.repository = repository;
           }
           public List<Trace> invoke() {
               return this.repository.findAll();
           }
       }

    Endpoint的Mvc接口主要是通过EndpointWebMvcManagementContextConfiguration实现的,这个类的配置也放在spring.factories中。

    ...
    org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=
    org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration,
    org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration

    EndpointWebMvcManagementContextConfiguration注入EndpointHandlerMapping来实现Endpoint的Mvc接口。

        @Bean
        @ConditionalOnMissingBean
        public EndpointHandlerMapping endpointHandlerMapping() {
            Set<? extends MvcEndpoint> endpoints = mvcEndpoints().getEndpoints();
            CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
            EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,corsConfiguration);
            boolean disabled = this.managementServerProperties.getPort() != null && this.managementServerProperties.getPort() == -1;
            mapping.setDisabled(disabled);
            if (!disabled) {
                mapping.setPrefix(this.managementServerProperties.getContextPath());
            }
            if (this.mappingCustomizers != null) {
                for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
                    customizer.customize(mapping);
                }
            }
            return mapping;
        }

    自定义Endpoint

    自定义Endpoint也是类似的原理。这里自定义Endpoint实现应用内存的定时收集。完整的代码放在Github上了。

    1. 收集内存,MemStatus是内存的存储结构,MemCollector是内存的收集类,使用Spring内置的定时功能,每5秒收集当前内存。
       public static class MemStatus {
           public MemStatus(Date date, Map<String, Object> status) {
               this.date = date;
               this.status = status;
           }
           private Date date;
           private Map<String, Object> status;
           public Date getDate() {
               return date;
           }
           public Map<String, Object> getStatus() {
               return status;
           }
       }
       public static class MemCollector {
           private int maxSize = 5;
           private List<MemStatus> status;
           public MemCollector(List<MemStatus> status) {
               this.status = status;
           }
           @Scheduled(cron = "0/5 * *  * * ? ")
           public void collect() {
               Runtime runtime = Runtime.getRuntime();
               Long maxMemory = runtime.maxMemory();
               Long totalMemory = runtime.totalMemory();
               Map<String, Object> memoryMap = new HashMap<String, Object>(2, 1);
               Date date = Calendar.getInstance().getTime();
               memoryMap.put("maxMemory", maxMemory);
               memoryMap.put("totalMemory", totalMemory);
               if (status.size() > maxSize) {
                   status.remove(0);
                   status.add(new MemStatus(date, memoryMap));
               } else {
                   status.add(new MemStatus(date, memoryMap));
               }
           }
       }
    2. 自定义Endpoint,getIdEndPoint的唯一标识,也是Mvc接口对外暴露的路径。invoke方法,取出maxMemorytotalMemory和对应的时间。
       public static class MyEndPoint implements Endpoint {
           private List<MemStatus> status;
           public MyEndPoint(List<MemStatus> status) {
               this.status = status;
           }
           public String getId() {
               return "my";
           }
           public boolean isEnabled() {
               return true;
           }
           public boolean isSensitive() {
               return false;
           }
           public Object invoke() {
               if (status == null || status.isEmpty()) {
                   return "hello world";
               }
               Map<String, List<Map<String, Object>>> result = new HashMap<String, List<Map<String, Object>>>();
               for (MemStatus memStatus : status) {
                   for (Map.Entry<String, Object> entry : memStatus.status.entrySet()) {
                       List<Map<String, Object>> collectList = result.get(entry.getKey());
                       if (collectList == null) {
                           collectList = new LinkedList<Map<String, Object>>();
                           result.put(entry.getKey(), collectList);
                       }
                       Map<String, Object> soloCollect = new HashMap<String, Object>();
                       soloCollect.put("date", memStatus.getDate());
                       soloCollect.put(entry.getKey(), entry.getValue());
                       collectList.add(soloCollect);
                   }
               }
               return result;
           }
       }
    3. AutoConfig,注入了MyEndPoint,和MemCollector
       public static class EndPointAutoConfig {
           private List<MemStatus> status = new ArrayList<MemStatus>();
           @Bean
           public MyEndPoint myEndPoint() {
               return new MyEndPoint(status);
           }
           @Bean
           public MemCollector memCollector() {
               return new MemCollector(status);
           }
       }
    4. 程序入口,运行后访问http://localhost:8080/my 就可以看到了。

       @Configuration
       @EnableAutoConfiguration
       public class CustomizeEndPoint {
      
           public static void main(String[] args) {
               SpringApplication application = new SpringApplication(CustomizeEndPoint.class);
               application.run(args);
           }
       }

    结语

    Endpoint也是通过spring.factories实现扩展功能,注入了对应的Bean来实现应用监控的功能。



    文/wcong(简书作者)
    原文链接:http://www.jianshu.com/p/9fab4e81d7bb
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    sp_executesql 带输出参数的 EXEC 动态执行 (z)
    查端口是否被占用 DOS 命令netstat(Z)
    MS SQL处理树型结构数据 将子节点记录的各项字段值累加赋值给其父节点
    lazarus unidac使用注意事项
    UNIDAC在arm linux运行出错的解决方法
    [转]为Linux下的Lazarus添加中文输入支持
    银河麒麟(aarch64)安装Lazarus步骤
    raspberry 添加拼音五笔输入法
    lazarus跨平台编译步骤
    拷贝构造函数与赋值操作符的具体实现 【转】参考度4.5星
  • 原文地址:https://www.cnblogs.com/softidea/p/6249635.html
Copyright © 2011-2022 走看看