前言
本文来介绍AbstractNamedMvcEndpoint的一系列的子类:
- AuditEventsMvcEndpoint
- HeapdumpMvcEndpoint
- LogFileMvcEndpoint
其他的实现:
- DocsMvcEndpoint
- HalJsonMvcEndpoint
- JolokiaMvcEndpoint
我们后续的文章进行分析
解析
AuditEventsMvcEndpoint
AuditEventsMvcEndpoint–> 继承自AbstractNamedMvcEndpoint
字段,构造器如下:
private final AuditEventRepository auditEventRepository; public AuditEventsMvcEndpoint(AuditEventRepository auditEventRepository) { super("auditevents", "/auditevents", true); Assert.notNull(auditEventRepository, "AuditEventRepository must not be null"); this.auditEventRepository = auditEventRepository; }
此时注册的路径为/auditevents
其只声明了1个方法:
@ActuatorGetMapping @ResponseBody public ResponseEntity<?> findByPrincipalAndAfterAndType( @RequestParam(required = false) String principal, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ") Date after, @RequestParam(required = false) String type) { if (!isEnabled()) { return DISABLED_RESPONSE; } Map<Object, Object> result = new LinkedHashMap<Object, Object>(); result.put("events", this.auditEventRepository.find(principal, after, type)); return ResponseEntity.ok(result); }
- 如果不可用的话,则返回默认的失败相应
- 否则,查询AuditEventRepository构造响应结果.响应状态码为200
关于AuditEventRepository的内容,可以查看 spring boot 源码解析31-AuthenticationAuditListener,AuthorizationAuditListener
属性配置:
因为该类声明了@ConfigurationProperties(prefix = “endpoints.auditevents”)注解,因此可以通过如下属性配置:
endpoints.auditevents.enabled= # Enable the endpoint. endpoints.auditevents.path= # Endpoint path. endpoints.auditevents.sensitive=false # Enable security on the endpoint.
自动装配:
声明在EndpointWebMvcManagementContextConfiguration中,代码如下:
@Bean @ConditionalOnMissingBean @ConditionalOnBean(AuditEventRepository.class) @ConditionalOnEnabledEndpoint("auditevents") public AuditEventsMvcEndpoint auditEventMvcEndpoint( AuditEventRepository auditEventRepository) { return new AuditEventsMvcEndpoint(auditEventRepository); }
- @Bean –> 注册1个id为auditEventMvcEndpoint,类型为AuditEventsMvcEndpoint的bean
- @ConditionalOnMissingBean –> BeanFactory中不存在AuditEventsMvcEndpoint类型的bean时生效
- @ConditionalOnBean(AuditEventRepository.class) –> BeanFactory中存在AuditEventRepository类型的bean时生效
- @ConditionalOnEnabledEndpoint(“auditevents”) –> 如果配置有endpoints. auditevents.enabled = true 或者endpoints.enabled= true 则该配置生效.如果没有配置的话,默认返回true
HeapdumpMvcEndpoint
HeapdumpMvcEndpoint–>继承自AbstractNamedMvcEndpoint
字段,构造器如下:
private final long timeout; private final Lock lock = new ReentrantLock(); private HeapDumper heapDumper; public HeapdumpMvcEndpoint() { this(TimeUnit.SECONDS.toMillis(10)); } protected HeapdumpMvcEndpoint(long timeout) { super("heapdump", "/heapdump", true); this.timeout = timeout; }
这里有必要说明一下HeapDumper–> dump 堆内存到文件中的策略接口,其声明了1个方法,如下:
void dumpHeap(File file, boolean live) throws IOException, InterruptedException;
其只有1个实现类–>HotSpotDiagnosticMXBeanHeapDumper–>使用HotSpotDiagnosticMXBean来进行dump.
字段,构造器如下:
// HotSpotDiagnosticMXBean对象 private Object diagnosticMXBean; // HotSpotDiagnosticMXBean中的dumpHeap所对应的Method对象 private Method dumpHeapMethod; @SuppressWarnings("unchecked") protected HotSpotDiagnosticMXBeanHeapDumper() { try { // 1. 获得HotSpotDiagnosticMXBean.class Class<?> diagnosticMXBeanClass = ClassUtils.resolveClassName( "com.sun.management.HotSpotDiagnosticMXBean", null); // 2. 获得HotSpotDiagnosticMXBean this.diagnosticMXBean = ManagementFactory.getPlatformMXBean( (Class<PlatformManagedObject>) diagnosticMXBeanClass); // 3. 获得HotSpotDiagnosticMXBean中的dumpHeap所对应的Method对象 this.dumpHeapMethod = ReflectionUtils.findMethod(diagnosticMXBeanClass, "dumpHeap", String.class, Boolean.TYPE); } catch (Throwable ex) { throw new HeapDumperUnavailableException( "Unable to locate HotSpotDiagnosticMXBean", ex); } }
如果在初始化的过程中出现错误,则抛出HeapDumperUnavailableException.代码如下:
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) protected static class HeapDumperUnavailableException extends RuntimeException { public HeapDumperUnavailableException(String message, Throwable cause) { super(message, cause); } }
最终返回的状态码为503
dumpHeap–>通过反射的方式dump.代码如下:
public void dumpHeap(File file, boolean live) { // 1. 通过反射的方式dump ReflectionUtils.invokeMethod(this.dumpHeapMethod, this.diagnosticMXBean, file.getAbsolutePath(), live); }
其声明了1个方法,代码如下:
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public void invoke(@RequestParam(defaultValue = "true") boolean live, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 1. 如果不可用的话,返回404 if (!isEnabled()) { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } try { // 2. 尝试获得锁,默认等待10毫秒 if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) { try { // 3. dumpHeap(live, request, response); return; } finally { // 4. 释放锁 this.lock.unlock(); } } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } // 5. 如果锁获取失败,则返回429 response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); }
- 如果不可用的话,返回404
- 尝试获得锁,默认等待10毫秒
dump文件.代码如下:
private void dumpHeap(boolean live, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, InterruptedException { // 1. 如果heapDumper等于null,则进行实例化.第一次访问该接口时创建 if (this.heapDumper == null) { this.heapDumper = createHeapDumper(); } // 2. 创建临时文件 File file = createTempFile(live); try { // 3. 将当前的堆内存信息dump到临时文件中 this.heapDumper.dumpHeap(file, live); // 4. 输出到响应流中 handle(file, request, response); } finally { file.delete(); } }
如果heapDumper等于null,则进行实例化.第一次访问该接口时创建.代码如下:
protected HeapDumper createHeapDumper() throws HeapDumperUnavailableException { return new HotSpotDiagnosticMXBeanHeapDumper(); }
创建临时文件.代码如下:
private File createTempFile(boolean live) throws IOException { String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date()); File file = File.createTempFile("heapdump" + date + (live ? "-live" : ""), ".hprof"); file.delete(); return file; }
在临时目录下创建heapdumpyyyy-MM-dd-HH-mm-live.hprof 的文件.对于笔者来说,临时目录为/var/folders/n9/7jzwh0z507xdrb0ld109vwkh0000gn/T
将当前的堆内存信息dump到临时文件中
输出到响应流中.代码如下:
protected void handle(File heapDumpFile, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置ContentType,响应头 response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + (heapDumpFile.getName() + ".gz") + "\""); try { // 2. 通过GZIPOutputStream将文件中的内容进行压缩,然后输出的response的输出流中 InputStream in = new FileInputStream(heapDumpFile); try { GZIPOutputStream out = new GZIPOutputStream(response.getOutputStream()); StreamUtils.copy(in, out); out.finish(); } catch (NullPointerException ex) { } finally { try { in.close(); } catch (Throwable ex) { } } } catch (FileNotFoundException ex) { } }
- 设置ContentType,响应头
- 通过GZIPOutputStream将文件中的内容进行压缩,然后输出的response的输出流中
- 释放锁
- 果锁获取失败,则返回429
下载的文件可以通过MAT打开
属性配置:
由于该类声明了@ConfigurationProperties(prefix = “endpoints.heapdump”)注解,因此可以通过如下属性配置:
endpoints.heapdump.enabled= # Enable the endpoint. endpoints.heapdump.path= # Endpoint path. endpoints.heapdump.sensitive= # Mark if the endpoint exposes sensitive information.
自动装配:
在EndpointWebMvcManagementContextConfiguration中进行了声明,代码如下:
@Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint("heapdump") public HeapdumpMvcEndpoint heapdumpMvcEndpoint() { return new HeapdumpMvcEndpoint(); }
- @Bean –> 注册1个id为heapdumpMvcEndpoint,类型为HeapdumpMvcEndpoint的bean
- @ConditionalOnMissingBean–> BeanFactory中不存在HeapdumpMvcEndpoint类型的bean时生效
- @ConditionalOnEnabledEndpoint(“heapdump”)–> 如果配置有endpoints. heapdump.enabled = true 或者endpoints.enabled= true 则该配置生效.如果没有配置的话,默认返回true
LogFileMvcEndpoint
LogFileMvcEndpoint–> 继承自AbstractNamedMvcEndpoint
字段,构造器如下:
private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class); // 一般情况下,为null private File externalFile; public LogFileMvcEndpoint() { super("logfile", "/logfile", true); }
方法如下:
@RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD }) public void invoke(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 如果不可用的话,则返回404 if (!isEnabled()) { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } // 2. 获得LogFile,如果不存在的话,则将Resource置为null Resource resource = getLogFileResource(); if (resource != null && !resource.exists()) { if (logger.isDebugEnabled()) { logger.debug("Log file '" + resource + "' does not exist"); } resource = null; } // 3. 实例化Handler Handler handler = new Handler(resource, request.getServletContext()); handler.handleRequest(request, response); }
- 如果不可用的话,则返回404
获得LogFile,如果不存在的话,则将Resource置为null.代码如下:
private Resource getLogFileResource() { if (this.externalFile != null) { return new FileSystemResource(this.externalFile); } LogFile logFile = LogFile.get(getEnvironment()); if (logFile == null) { logger.debug("Missing 'logging.file' or 'logging.path' properties"); return null; } return new FileSystemResource(logFile.toString()); }
实例化Handler处理请求.Handler–>继承自ResourceHttpRequestHandler.
构造器如下:
Handler(Resource resource, ServletContext servletContext) { this.resource = resource; getLocations().add(resource); try { setServletContext(servletContext); afterPropertiesSet(); } catch (Exception ex) { throw new IllegalStateException(ex); } }
将logfile对应的文件添加到ResourceHttpRequestHandler的Locations
getResource–> 直接返回logfile对应的文件.代码如下:
protected Resource getResource(HttpServletRequest request) throws IOException { return this.resource; }
getMediaType–> 设置了响应类型为text/plain.代码如下:
protected MediaType getMediaType(Resource resource) { return MediaType.TEXT_PLAIN; }
这样一来,我们访问/logfile时就会将logFile对应的日志内容渲染到浏览器中.如下:
2018-02-01 01:07:26.635 INFO 3132 --- [main] com.example.demo.DemoApplication : Starting DemoApplication on localhost with PID 3132 (/Users/hejiarui/Documents/spring-boot-source/demo/target/classes started by hejiarui in /Users/hejiarui/Documents/spring-boot-source/demo) 2018-02-01 01:07:26.638 INFO 3132 --- [main] com.example.demo.DemoApplication : The following profiles are active: test 2018-02-01 01:07:26.894 INFO 3132 --- [main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7d1cfb8b: startup date [Thu Feb 01 01:07:26 CST 2018]; root of context hierarchy 2018-02-01 01:07:27.619 INFO 3132 ---
属性配置:
由于该类声明了@ConfigurationProperties(prefix = “endpoints.logfile”),因此可以通过如下注解进行配置:
endpoints.logfile.enabled=true # Enable the endpoint. endpoints.logfile.external-file= # External Logfile to be accessed. endpoints.logfile.path=/logfile # Endpoint URL path. endpoints.logfile.sensitive=true # Enable security on the endpoint.
自动装配:
声明在EndpointWebMvcManagementContextConfiguration中,代码如下:
@Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint("logfile") @Conditional(LogFileCondition.class) public LogFileMvcEndpoint logfileMvcEndpoint() { return new LogFileMvcEndpoint(); }
- @Bean –> 注册1个id为 logfileMvcEndpoint,类型为LogFileMvcEndpoint的bean
- @ConditionalOnMissingBean–>BeanFactory中不存在LogFileMvcEndpoint类型的bean时生效
- @ConditionalOnEnabledEndpoint(“logfile”) –> 如果配置有endpoints. logfile.enabled = true 或者endpoints.enabled= true 则该配置生效.如果没有配置的话,默认返回true
- @Conditional(LogFileCondition.class) –> 配置有logging.file,logging.path,endpoints.logfile.external-file 中的任意一个时生效