深入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
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM