spring boot 源码解析53-AbstractNamedMvcEndpoint

前言

本文来介绍AbstractNamedMvcEndpoint的一系列的子类:

  • AuditEventsMvcEndpoint
  • HeapdumpMvcEndpoint
  • LogFileMvcEndpoint

其他的实现:

  • DocsMvcEndpoint
  • HalJsonMvcEndpoint
  • JolokiaMvcEndpoint

我们后续的文章进行分析

解析

AuditEventsMvcEndpoint

AuditEventsMvcEndpoint–> 继承自AbstractNamedMvcEndpoint

  1. 字段,构造器如下:

    private final AuditEventRepository auditEventRepository;
    
    public AuditEventsMvcEndpoint(AuditEventRepository auditEventRepository) {
        super("auditevents", "/auditevents", true);
        Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
        this.auditEventRepository = auditEventRepository;
    }

    此时注册的路径为/auditevents

  2. 其只声明了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);
    }
    1. 如果不可用的话,则返回默认的失败相应
    2. 否则,查询AuditEventRepository构造响应结果.响应状态码为200

    关于AuditEventRepository的内容,可以查看 spring boot 源码解析31-AuthenticationAuditListener,AuthorizationAuditListener

  3. 属性配置:

    因为该类声明了@ConfigurationProperties(prefix = “endpoints.auditevents”)注解,因此可以通过如下属性配置:

    endpoints.auditevents.enabled= # Enable the endpoint.
    endpoints.auditevents.path= # Endpoint path.
    endpoints.auditevents.sensitive=false # Enable security on the endpoint.
  4. 自动装配:

    声明在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

  1. 字段,构造器如下:

    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.

    1. 字段,构造器如下:

      // 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

    2. dumpHeap–>通过反射的方式dump.代码如下:

      public void dumpHeap(File file, boolean live) {
          // 1. 通过反射的方式dump
          ReflectionUtils.invokeMethod(this.dumpHeapMethod, this.diagnosticMXBean,
                  file.getAbsolutePath(), live);
      }
  2. 其声明了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());
    }
    1. 如果不可用的话,返回404
    2. 尝试获得锁,默认等待10毫秒
    3. 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();
          }
      }
      1. 如果heapDumper等于null,则进行实例化.第一次访问该接口时创建.代码如下:

        protected HeapDumper createHeapDumper() throws HeapDumperUnavailableException {
            return new HotSpotDiagnosticMXBeanHeapDumper();
        }
      2. 创建临时文件.代码如下:

        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

      3. 将当前的堆内存信息dump到临时文件中

      4. 输出到响应流中.代码如下:

        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) {
            }
        }
        1. 设置ContentType,响应头
        2. 通过GZIPOutputStream将文件中的内容进行压缩,然后输出的response的输出流中
    4. 释放锁
    5. 果锁获取失败,则返回429

    下载的文件可以通过MAT打开

  3. 属性配置:

    由于该类声明了@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.
  4. 自动装配:

    在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

  1. 字段,构造器如下:

    private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class);
    
    // 一般情况下,为null
    private File externalFile;
    
    public LogFileMvcEndpoint() {
        super("logfile", "/logfile", true);
    }
  2. 方法如下:

    @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);
    }
    1. 如果不可用的话,则返回404
    2. 获得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());
      }
    3. 实例化Handler处理请求.Handler–>继承自ResourceHttpRequestHandler.

      1. 构造器如下:

        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

      2. getResource–> 直接返回logfile对应的文件.代码如下:

        protected Resource getResource(HttpServletRequest request) throws IOException {
            return this.resource;
        }
      3. 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 ---
  3. 属性配置:

    由于该类声明了@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.
  4. 自动装配:

    声明在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 中的任意一个时生效
    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/79223630
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞