深入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