spring boot 源码解析53-AbstractNamedMvcEndpoint

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

前言

本文来介绍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 result = new LinkedHashMap();
            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) 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 中的任意一个时生效

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析53-AbstractNamedMvcEndpoint

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏