spring boot 源码解析55-spring boot actuator HandlerMapping全网独家揭秘

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

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

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

本文我们来介绍一下EndpointHandlerMapping. 我们在项目中加入spring-boot-starter-actuator 之后,就可以访问如下端点:

path 描述 是否默认敏感
/shutdown 优雅关闭 true
/dump 打印出线程的堆栈信息. true
/configprops 显示出所有的@ConfigurationProperties true
/info 显示出任意的应用信息 false
/env 暴露出ConfigurableEnvironment的所有的properties true
/health 显示应用的健康信息 false
/mappings 显示@RequestMapping的所有路径 true
/autoconfig 显示出所有的自动装配的结果 true
/metrics 显示出当前应用的所有metrics的信息 true
/trace 显示出trace的信息(默认只显示100) true
/auditevents/td> 显示出所有的auditevents true
/heapdump/td> dump堆内存 true
/beans/td> 展示所有的bean true
/loggers/td> 展示或者修改loggers的配置 true
/logfile/td> 展示logfile的内容 true

我们之前解析了一系列的xxxEndpoint,xxxEndpoint是不能直接通过http请求来处理的,因此需要在其上包装一层,因此出现了2个分支–>xxxMvcEndpoint,EndpointMvcAdapter的一系列的子类.但是光这样还不够,我们需要将其注册上去才能在spring mvc 的体系中处理,那么EndpointHandlerMapping就是干这件事的

解析

AbstractEndpointHandlerMapping

AbstractEndpointHandlerMapping 继承自RequestMappingHandlerMapping

  1. 字段,构造器如下:

        // MVC端点集合
        private final Set endpoints;
    
        // 安全处理程序拦截器
        private HandlerInterceptor securityInterceptor;
    
        // CORS配置
        private final CorsConfiguration corsConfiguration;
    
        // 端点的映射路径前缀
        private String prefix = "";
    
        private boolean disabled = false;
    
        public AbstractEndpointHandlerMapping(Collection endpoints) {
            this(endpoints, null);
        }
    
        public AbstractEndpointHandlerMapping(Collection endpoints,
                CorsConfiguration corsConfiguration) {
            this.endpoints = new HashSet(endpoints);
            // 2. 扩展点
            postProcessEndpoints(this.endpoints);
            this.corsConfiguration = corsConfiguration;
            // By default the static resource handler mapping is LOWEST_PRECEDENCE - 1
            // and the RequestMappingHandlerMapping is 0 (we ideally want to be before both)
            // 默认情况下,静态资源处理程序映射的顺序是 LOWEST_PRECEDENCE - 1 
            setOrder(-100);
            // 不进行后缀匹配
            setUseSuffixPatternMatch(false);
        }

    在构造器中调用了postProcessEndpoints,默认空实现,代码如下:

        protected void postProcessEndpoints(Set endpoints) {
        }
  2. 覆写了如下方法:

    1. afterPropertiesSet,代码如下:

          public void afterPropertiesSet() {
              super.afterPropertiesSet();
              if (!this.disabled) { // 如果端点是启用的 
                  for (MvcEndpoint endpoint : this.endpoints) {
                      // 调用AbstractHandlerMethodMapping中的detectHandlerMethods方法进行注册handler,寻找在MvcEndpoint有@RequestMapping注解的方法
                      detectHandlerMethods(endpoint);
                  }
              }
          }
      1. 调用父类的afterPropertiesSet
      2. 如果disabled等于true,则遍历endpoints,依次调用detectHandlerMethods,进行注册handler,寻找在MvcEndpoint有@RequestMapping注解的方法.此时就将xxxMvcEndpoint,EndpointMvcAdapter的一系列的子类被@ActuatorGetMapping注解的方法进行了注册
    2. isHandler–>因为所有的handler都已在afterPropertiesSet中进行了处理,因此这里就不需要判断了,直接返回false.如下:

          protected boolean isHandler(Class beanType) {
              return false;
          }
    3. registerHandlerMethod–> 复写了AbstractHandlerMethodMapping#detectHandlerMethods.修改了handler的pattern.代码如下:

          protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { if (mapping == null) { return; } String[] patterns = getPatterns(handler, mapping);
              if (!ObjectUtils.isEmpty(patterns)) { // 重新注册了映射规则 super.registerHandlerMethod(handler, method, withNewPatterns(mapping, patterns)); }
          }
      1. 如果RequestMappingInfo等于null,则直接return
      2. 获得对应的pattern.代码如下:

            private String[] getPatterns(Object handler, RequestMappingInfo mapping) {
                if (handler instanceof String) {
                    handler = getApplicationContext().getBean((String) handler);
                }
                Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported");
                String path = getPath((MvcEndpoint) handler); // 获得MvcEndpoint配置的path
                return (path == null ? null : getEndpointPatterns(path, mapping));
            }
        1. 如果handler是String的实例,则将其看做bean id 获得对应的handler
        2. 获得MvcEndpoint配置的path.代码如下:

              protected String getPath(MvcEndpoint endpoint) {
                  return endpoint.getPath();
              }
        3. 前缀处理.代码如下:

              private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) {
                  // 1. 路径模式前缀
                  String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path
                          : path;
                  // 2. 根据RequestMappingInfo 获得匹配的路径
                  Set defaultPatterns = mapping.getPatternsCondition().getPatterns();
                  // 3. 如果defaultPatterns为空
                  if (defaultPatterns.isEmpty()) {
                      return new String[] { patternPrefix, patternPrefix + ".json" };
                  }
                  // 4. 如果不为空,则加defaultPatterns的路径前加上前缀
                  List patterns = new ArrayList(defaultPatterns);
                  for (int i = 0; i < patterns.size(); i++) {
                      patterns.set(i, patternPrefix + patterns.get(i));
                  }
                  return patterns.toArray(new String[patterns.size()]);
              }
          1. 路径模式前缀
          2. 根据RequestMappingInfo 获得匹配的路径
          3. 如果defaultPatterns为空,则直接返回patternPrefix,patternPrefix.json
          4. 如果不为空,则加defaultPatterns的路径前加上前缀

          拿MetricsMvcEndpoint来说,其继承自EndpointMvcAdapter,内部持有的是MetricsEndpoint.由于默认情况下,我们没有management.context-path,因此在第1步返回的是/metrics

          由于在MetricsMvcEndpoint中有两个被@ActuatorGetMapping注解的方法:

          1. 声明在EndpointMvcAdapter中的invoke方法,如下:

                @Override
                @ActuatorGetMapping
                @ResponseBody
                public Object invoke() {
                    return super.invoke();
                }

            由于没有配置@ActuatorGetMapping中的value属性,因此,也就是在第2步中没有获取到defaultPatterns,因此,最终在第3步返回/metrics,/metrics.json 然后进行注册.

          2. value方法,声明在MetricsMvcEndpoint中,代码如下:

                @ActuatorGetMapping("/{name:.*}")
                @ResponseBody
                @HypermediaDisabled
                public Object value(@PathVariable String name) {
                        ....
                }

            由于配置@ActuatorGetMapping中的value属性–>/{name:.*},因此,在第2步中获取到defaultPatterns–>/{name:.*},因此,最终在第4步返回/metrics/{name:.*}, 然后进行注册.

            因此我们在启动应用时,会看到如下日志:

                2018-02-01 15:24:52.984  INFO 7106 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
                2018-02-01 15:24:52.984  INFO 7106 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
      3. 重新注册映射规则–>在注册hanler时重新修改了RequestMappingInfo的mapping.代码如下:

            private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping,
                    String[] patternStrings) {
                PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings,
                        null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null);
                return new RequestMappingInfo(patterns, mapping.getMethodsCondition(),
                        mapping.getParamsCondition(), mapping.getHeadersCondition(),
                        mapping.getConsumesCondition(), mapping.getProducesCondition(),
                        mapping.getCustomCondition());
            }
    4. getHandlerExecutionChain–>获取处理程序执行链,在DispatcherServlet#doDispatch中会调用HandlerMapping#getHandler,而在getHandler中会调用该方法来获得HandlerExecutionChain,HandlerExecutionChain是由一系列的拦截器+handler组成的.代码如下:

          protected HandlerExecutionChain getHandlerExecutionChain(Object handler,
                  HttpServletRequest request) {
              // 1. 调用父类的处理
              HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
              // 2. 如果securityInterceptor 等于null或者是CORS请求,则直接返回
              if (this.securityInterceptor == null || CorsUtils.isCorsRequest(request)) {
                  return chain;
              }
              // 3. 在原先的Interceptor基础上加上securityInterceptor
              return addSecurityInterceptor(chain);
          }
      1. 调用父类的处理
      2. 如果securityInterceptor 等于null或者是CORS请求,则直接返回
      3. 在原先的Interceptor基础上加上securityInterceptor.代码如下:

            private HandlerExecutionChain addSecurityInterceptor(HandlerExecutionChain chain) {
                List interceptors = new ArrayList();
                if (chain.getInterceptors() != null) {
                    interceptors.addAll(Arrays.asList(chain.getInterceptors()));
                }
                interceptors.add(this.securityInterceptor);
                return new HandlerExecutionChain(chain.getHandler(),
                        interceptors.toArray(new HandlerInterceptor[interceptors.size()]));
            }

        关于securityInterceptor我们后面有讲解

    5. extendInterceptors –> 该方法是在AbstractHandlerMapping#initApplicationContext中调用的,调用的时间是在该HandlerMapping初始化时调用的.代码如下:

          protected void extendInterceptors(List interceptors) {
              interceptors.add(new SkipPathExtensionContentNegotiation());
          }
      

      SkipPathExtensionContentNegotiation,在前置处理中向request中保存了org.springframework.web.accept.PathExtensionContentNegotiationStrategy.SKIP的属性,值为true.代码如下:

          public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                  Object handler) throws Exception {
              request.setAttribute(SKIP_ATTRIBUTE, Boolean.TRUE);
              return true;
          }
    6. initCorsConfiguration–>复写了RequestMappingHandlerMapping#initCorsConfiguration,直接使用本类配置的corsConfiguration,该方法在注册Handler时有用.代码如下:

          protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { return this.corsConfiguration; }
    7. MvcEndpointSecurityInterceptor

      AbstractEndpointHandlerMapping中持有的securityInterceptor默认是MvcEndpointSecurityInterceptor(自动装配).

      1. 其字段,构造器如下:

            private static final Log logger = LogFactory
                    .getLog(MvcEndpointSecurityInterceptor.class);
        
            // 是否进行校验
            private final boolean secure;
        
            // 默认为ACTUATOR
            private final List roles;
        
            private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean();
        
            public MvcEndpointSecurityInterceptor(boolean secure, List roles) {
                this.secure = secure;
                this.roles = roles;
            }
        
      2. preHandle方法如下:

            public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                    Object handler) throws Exception {
                // 1. 如果是(cors请求并且是options类型的请求并且请求头Access-Control-Request-Method存在)或者(不进行校验),则直接返回true
                if (CorsUtils.isPreFlightRequest(request) || !this.secure) {
                    return true;
                }
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                // 2. 如果是options请求并且HandlerMethod不是MvcEndpoint的实例,则返回true
                if (HttpMethod.OPTIONS.matches(request.getMethod())
                        && !(handlerMethod.getBean() instanceof MvcEndpoint)) {
                    return true;
                }
                MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
                // 3. 如果配置的endpoints.sensitive=false或者对应的endpoints.xxx.sensitive=false,则直接返回true
                if (!mvcEndpoint.isSensitive()) {
                    return true;
                }
                // 4. 如果拥有相应的权限则返回true
                if (isUserAllowedAccess(request)) {
                    return true;
                }
                // 5. 返回401
                sendFailureResponse(request, response);
                return false;
            }
        1. 如果是(cors请求并且是options类型的请求并且请求头Access-Control-Request-Method存在)或者(不进行校验),则直接返回true
        2. 如果是options请求并且HandlerMethod不是MvcEndpoint的实例,则返回true
        3. 如果配置的endpoints.sensitive=false或者对应的endpoints.xxx.sensitive=false,则直接返回true
        4. 如果拥有相应的权限则返回true.代码如下:

              private boolean isUserAllowedAccess(HttpServletRequest request) {
                  AuthoritiesValidator authoritiesValidator = null;
                  // 1. 如果org.springframework.security.config.annotation.web.WebSecurityConfigurer在当前类路径下存在,也就是加入了spring-boot-starter-security的依赖
                  // 则实例化AuthoritiesValidator
                  if (isSpringSecurityAvailable()) {
                      authoritiesValidator = new AuthoritiesValidator();
                  }
                  // 2. 遍历需要的权限,依次判断其是否有对应的权限,只要有1个满足的话,则返回true
                  for (String role : this.roles) {
                      // 2.1 判断用户是否是某个种类的角色,类型可以在web.xml中配置(tomcat-users.xml)
                      if (request.isUserInRole(role)) {
                          return true;
                      }
                      // 2.2 如果authoritiesValidator不等于null并且拥有对应的权限,则返回true
                      if (authoritiesValidator != null && authoritiesValidator.hasAuthority(role)) {
                          return true;
                      }
                  }
                  // 3. 如果都不满足,返回false
                  return false;
              }
          1. 如果org.springframework.security.config.annotation.web.WebSecurityConfigurer在当前类路径下存在,也就是加入了spring-boot-starter-security的依赖,则实例化AuthoritiesValidator
          2. 遍历需要的权限,依次判断其是否有对应的权限,只要有1个满足的话,则返回true

            1. 判断用户是否是某个种类的角色,类型可以在web.xml中配置(tomcat-users.xml)
            2. 如果authoritiesValidator不等于null并且拥有对应的权限,则返回true.代码如下:

                  private boolean hasAuthority(String role) {
                      Authentication authentication = SecurityContextHolder.getContext()
                              .getAuthentication();
                      if (authentication != null) {
                          for (GrantedAuthority authority : authentication.getAuthorities()) {
                              if (authority.getAuthority().equals(role)) {
                                  return true;
                              }
                          }
                      }
                      return false;
                  }
          3. 如果都不满足,返回false
        5. 返回401

      EndpointHandlerMapping

      EndpointHandlerMapping 继承自AbstractEndpointHandlerMapping.代码如下:

          public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping<MvcEndpoint> {
      
              public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
                  super(endpoints);
              }
      
              // 创建一个EndpointHandlerMapping.
              public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints,
                      CorsConfiguration corsConfiguration) {
                  super(endpoints, corsConfiguration);
              }
      
          }

      自动装配

      EndpointHandlerMapping的自动装配是在EndpointWebMvcAutoConfiguration中.

      1. EndpointWebMvcAutoConfiguration声明了如下注解,在满足如下条件时生效:

            @Configuration
            @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
            @ConditionalOnWebApplication
            @AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
                EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
                ManagementServerPropertiesAutoConfiguration.class,
                RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class,
                HttpMessageConvertersAutoConfiguration.class })
        • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) –> 在类路径下存在Servlet.class, DispatcherServlet.class时生效
        • @ConditionalOnWebApplication –> 是web环境时生效
      2. EndpointHandlerMapping实现了ApplicationContextAware, BeanFactoryAware, SmartInitializingSingleton接口,其中,当EndpointHandlerMapping初始化之后会回调afterSingletonsInstantiated.代码如下:

            public void afterSingletonsInstantiated() {
                // 1. 判断 managementPort 是否和serverPort一样
                ManagementServerPort managementPort = ManagementServerPort.DIFFERENT;
                if (this.applicationContext instanceof WebApplicationContext) {
                    managementPort = ManagementServerPort
                            .get(this.applicationContext.getEnvironment(), this.beanFactory);
                }
                // 2. 如果不一样
                if (managementPort == ManagementServerPort.DIFFERENT) {
                    // 2.1 如果当前applicationContext为EmbeddedWebApplicationContext,并且有嵌入容器,则创建一个子WebApplicationContext
                    // note: 这也意味着使用actuate时,如果managementPort 和serverPort 不一样时,并且是部署在外置tomcat中时,actuate 会不生效的 
                    if (this.applicationContext instanceof EmbeddedWebApplicationContext
                            && ((EmbeddedWebApplicationContext) this.applicationContext)
                                    .getEmbeddedServletContainer() != null) {
                        createChildManagementContext();
                    }
                    else {
                        // 否则,不进行创建
                        logger.warn("Could not start embedded management container on "
                                + "different port (management endpoints are still available "
                                + "through JMX)");
                    }
                }
                // 3. 如果一样,则
                if (managementPort == ManagementServerPort.SAME) {
                    // 3.1 如果management.ssl.enabled 配置为true,则抛出IllegalStateException
                    if (new RelaxedPropertyResolver(this.applicationContext.getEnvironment(),
                            "management.ssl.").getProperty("enabled", Boolean.class, false)) {
                        throw new IllegalStateException(
                                "Management-specific SSL cannot be configured as the management "
                                        + "server is not listening on a separate port");
                    }
                    // 3.2 向environment中添加名为Management Server的PropertySource,其中获得local.management.port时,会转而向environment调用local.server.port
                    // 访问其他的属性直接返回null
                    if (this.applicationContext
                            .getEnvironment() instanceof ConfigurableEnvironment) {
                        addLocalManagementPortPropertyAlias(
                                (ConfigurableEnvironment) this.applicationContext
                                        .getEnvironment());
                    }
                }
            }
        1. 判断 managementPort 是否和serverPort一样.代码如下:

              public static ManagementServerPort get(Environment environment,
                      BeanFactory beanFactory) {
                  // 1. 获得server.port的配置
                  Integer serverPort = getPortProperty(environment, "server.");
                  // 2. 如果server.port没有配置并且ServerProperties在beanFactory中存在的话
                  if (serverPort == null && hasCustomBeanDefinition(beanFactory,
                          ServerProperties.class, ServerPropertiesAutoConfiguration.class)) {
                      // 则获得ServerProperties中所配置的默认端口
                      serverPort = getTemporaryBean(beanFactory, ServerProperties.class)
                              .getPort();
                  }
                  // 3. 获得management.port的配置,如果management.port没有配置并且ManagementServerProperties在beanFactory中存在的话
                  // 则获得ServerProperties中所配置的默认端口
                  Integer managementPort = getPortProperty(environment, "management.");
                  if (managementPort == null && hasCustomBeanDefinition(beanFactory,
                          ManagementServerProperties.class,
                          ManagementServerPropertiesAutoConfiguration.class)) {
                      managementPort = getTemporaryBean(beanFactory,
                              ManagementServerProperties.class).getPort();
                  }
                  // 4. 如果managementPort端口号小于零则返回不可用
                  if (managementPort != null && managementPort < 0) {
                      return DISABLE;
                  }
                  /* * 5. 如果 * 
            *
          • managementPort等于null(management.port没有配置)
          • *
          • managementPort 等于8080
          • *
          • managementPort 等于serverPort
          • *
          * 满足其中一个,则 managementPort 使用和serverPort一样的端口,返回same,否则,返回DIFFERENT * */ return ((managementPort == null) || (serverPort == null && managementPort.equals(8080)) || (managementPort != 0 && managementPort.equals(serverPort)) ? SAME : DIFFERENT); }
          1. 获得server.port的配置
          2. 如果server.port没有配置并且ServerProperties在beanFactory中存在的话,则获得ServerProperties中所配置的默认端口
          3. 获得management.port的配置,如果management.port没有配置并且ManagementServerProperties在beanFactory中存在的话,则获得ServerProperties中所配置的默认端口
          4. 如果managementPort端口号小于零则返回不可用
          5. 如果满足以下条件的任意1个,则managementPort 使用和serverPort一样的端口,返回same,否则,返回DIFFERENT:

            1. managementPort等于null(management.port没有配置)
            2. managementPort 等于8080
            3. managementPort 等于serverPort
        2. 如果不一样

          1. 如果当前applicationContext为EmbeddedWebApplicationContext,并且有嵌入容器,则创建一个子WebApplicationContext

            这也意味着使用actuate时,如果managementPort 和serverPort 不一样时,并且是部署在外置tomcat中时,actuate 会不生效的

        3. 如果一样,则

          1. 如果management.ssl.enabled 配置为true,则抛出IllegalStateException
          2. 向environment中添加名为Management Server的PropertySource,其中获得local.management.port时,会转而向environment调用local.server.port.访问其他的属性直接返回null.代码如下:

                private void addLocalManagementPortPropertyAlias(
                        final ConfigurableEnvironment environment) {
                    environment.getPropertySources()
                            .addLast(new PropertySource("Management Server") {
                                @Override
                                public Object getProperty(String name) {
                                    if ("local.management.port".equals(name)) {
                                        return environment.getProperty("local.server.port");
                                    }
                                    return null;
                                }
                            });
                }
            
            
            
            
            

            其中:当 managementPort 和serverPort 不一样,会执行如下代码:

                private void createChildManagementContext() {
                    AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext();
                    childContext.setParent(this.applicationContext);
                    childContext.setNamespace("management");
                    childContext.setId(this.applicationContext.getId() + ":management");
                    childContext.setClassLoader(this.applicationContext.getClassLoader());
                    // 1. 注册配置类
                    childContext.register(EndpointWebMvcChildContextConfiguration.class,
                            PropertyPlaceholderAutoConfiguration.class,
                            EmbeddedServletContainerAutoConfiguration.class,
                            DispatcherServletAutoConfiguration.class);
                    // 2. 注册嵌入容器Factory 
                    registerEmbeddedServletContainerFactory(childContext);
                    // 3. 添加CloseManagementContextListener监听器--> 处理ContextClosedEvent,ApplicationFailedEvent 事件
                    CloseManagementContextListener.addIfPossible(this.applicationContext,
                            childContext);
                    // 4. 进行初始化
                    childContext.refresh();
                    // 5. 为ManagementContextResolver 重新设置ApplicationContext 为创建的子WebApplicationContext
                    managementContextResolver().setApplicationContext(childContext);
                }
            1. 实例化AnnotationConfigEmbeddedWebApplicationContext
            2. 注册配置类
            3. 注册嵌入容器Factory.代码如下:

                  private void registerEmbeddedServletContainerFactory(
                          AnnotationConfigEmbeddedWebApplicationContext childContext) {
                      try {
                          ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
                          if (beanFactory instanceof BeanDefinitionRegistry) {
                              BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                              registry.registerBeanDefinition("embeddedServletContainerFactory",
                                      new RootBeanDefinition(
                                              determineEmbeddedServletContainerFactoryClass()));
                          }
                      }
                      catch (NoSuchBeanDefinitionException ex) {
                          // Ignore and assume auto-configuration
                      }
                  }

              调用:

                  private Class determineEmbeddedServletContainerFactoryClass()
                          throws NoSuchBeanDefinitionException {
                      Class servletContainerFactoryClass = this.applicationContext
                              .getBean(EmbeddedServletContainerFactory.class).getClass();
                      if (cannotBeInstantiated(servletContainerFactoryClass)) {
                          throw new FatalBeanException("EmbeddedServletContainerFactory implementation "
                                  + servletContainerFactoryClass.getName() + " cannot be instantiated. "
                                  + "To allow a separate management port to be used, a top-level class "
                                  + "or static inner class should be used instead");
                      }
                      return servletContainerFactoryClass;
                  }
            4. 添加CloseManagementContextListener监听器–> 处理ContextClosedEvent,ApplicationFailedEvent 事件.其最终处理都是关闭当前上下文.如下:

                  private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
                      if (applicationContext == this.parentContext) {
                          this.childContext.close();
                      }
                  }
            5. 进行初始化
            6. 为ManagementContextResolver 重新设置ApplicationContext 为创建的子WebApplicationContext
          3. EndpointWebMvcAutoConfiguration中有2个配置内部类:

            1. ApplicationContextFilterConfiguration,代码如下:

                  @Configuration
                  @ConditionalOnProperty(prefix = "management", name = "add-application-context-header", matchIfMissing = true, havingValue = "true")
                  protected static class ApplicationContextFilterConfiguration {
              
                      // ApplicationContextHeaderFilter是添加名为X-Application-Context的响应头,值为ApplicationContext的id
                      @Bean
                      public ApplicationContextHeaderFilter applicationContextIdFilter(
                              ApplicationContext context) {
                          return new ApplicationContextHeaderFilter(context);
                      }
                  }

              当配置有management.add-application-context-header = true或者没有配置时默认生效.注册1个ApplicationContextHeaderFilter.其作用是添加名为X-Application-Context的响应头,值为ApplicationContext的id.代码如下:

                  public static final String HEADER_NAME = "X-Application-Context";
                  private final ApplicationContext applicationContext;
                  public ApplicationContextHeaderFilter(ApplicationContext context) {
                      this.applicationContext = context;
                  }
                  @Override
                  protected void doFilterInternal(HttpServletRequest request,
                          HttpServletResponse response, FilterChain filterChain)
                                  throws ServletException, IOException {
                      response.addHeader(HEADER_NAME, this.applicationContext.getId());
                      filterChain.doFilter(request, response);
                  }
            2. EndpointWebMvcConfiguration,代码如下:

                  @Configuration
                  @Conditional(OnManagementMvcCondition.class)
                  @Import(ManagementContextConfigurationsImportSelector.class)
                  protected static class EndpointWebMvcConfiguration {
              
                  }

              当满足如下条件的任意1个时生效:

              1. managementPort等于null(management.port没有配置)
              2. managementPort 等于8080
              3. managementPort 等于serverPort

              通过@Import导入了ManagementContextConfigurationsImportSelector.由于其是DeferredImportSelector的实例,因此会调用其selectImports方法,如下:

                  public String[] selectImports(AnnotationMetadata metadata) {
                      // Find all management context configuration classes, filtering duplicates
                      // 1. 加载/META-INF/spring.factories中配置的org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration
                      List configurations = getConfigurations();
                      // 2. 排序
                      OrderComparator.sort(configurations);
                      List names = new ArrayList();
                      for (ManagementConfiguration configuration : configurations) {
                          names.add(configuration.getClassName());
                      }
                      return names.toArray(new String[names.size()]);
                  }
              1. 加载/META-INF/spring.factories中配置的org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration
              2. 排序

              默认返回的是:

                  org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration,\
                  org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration
              1. EndpointWebMvcHypermediaManagementContextConfiguration,其注解如下:

                    @ManagementContextConfiguration
                    @ConditionalOnClass(Link.class)
                    @ConditionalOnWebApplication
                    @ConditionalOnBean(HttpMessageConverters.class)
                    @Conditional(EndpointHypermediaEnabledCondition.class)
                    @EnableConfigurationProperties(ResourceProperties.class)

                由于默认情况下,在类路径下不存在org.springframework.hateoas.Link.class,因此该类不会进行处理.

              2. EndpointWebMvcManagementContextConfiguration,在这个配置类中,除了配置了一系列的mvcEndpoints之外,还配置了endpointHandlerMapping.如下:

                    @Bean
                    @ConditionalOnMissingBean
                    public EndpointHandlerMapping endpointHandlerMapping() {
                        // 1. 获得注册的MvcEndpoint
                        Set endpoints = mvcEndpoints().getEndpoints();
                        // 2. 实例化CorsConfiguration
                        CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
                        // 3. 实例化EndpointHandlerMapping-->actuate 系列的HandlerMapping
                        EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
                                corsConfiguration);
                        // 4. 设置EndpointHandlerMapping的前缀为management.contextPath 的配置值
                        mapping.setPrefix(this.managementServerProperties.getContextPath());
                        // 5. 实例化MvcEndpointSecurityInterceptor--> Interceptor,并进行设置
                        MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor(
                                this.managementServerProperties.getSecurity().isEnabled(),
                                this.managementServerProperties.getSecurity().getRoles());
                        mapping.setSecurityInterceptor(securityInterceptor);
                        // 6. 个性化设置--> 默认没有实现类
                        for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
                            customizer.customize(mapping);
                        }
                        return mapping;
                    }
                1. 获得注册的MvcEndpoint
                2. 实例化CorsConfiguration
                3. 实例化EndpointHandlerMapping–>actuate 系列的HandlerMapping
                4. 设置EndpointHandlerMapping的前缀为management.contextPath 的配置值
                5. 实例化MvcEndpointSecurityInterceptor–> Interceptor,并进行设置
                6. 个性化设置–> 默认没有实现类

            此刻就将EndpointHandlerMapping注册了,当DispatcherServlet第1次实例化或者当BeanFactory发送ContextRefreshedEvent事件时,就会调用DispatcherServlet的initHandlerMappings方法,在该方法中,将BeanFactory中所有的HandlerMapping的bean都加入到了handlerMappings中.代码如下:

                private void initHandlerMappings(ApplicationContext context) {
                    this.handlerMappings = null;
            
                    if (this.detectAllHandlerMappings) {
                        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
                        Map matchingBeans =
                                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
                        if (!matchingBeans.isEmpty()) {
                            this.handlerMappings = new ArrayList(matchingBeans.values());
                            // We keep HandlerMappings in sorted order.
                            AnnotationAwareOrderComparator.sort(this.handlerMappings);
                        }
                    }
                    else {
                        try {
                            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                            this.handlerMappings = Collections.singletonList(hm);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            // Ignore, we'll add a default HandlerMapping later.
                        }
                    }
            
                    // Ensure we have at least one HandlerMapping, by registering
                    // a default HandlerMapping if no other mappings are found.
                    if (this.handlerMappings == null) {
                        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
                        if (logger.isDebugEnabled()) {
                            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
                        }
                    }
                }

            调用链如下:

            20191017100435\_1.png


          4. 来源:[]()

            赞(0) 打赏
            版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析55-spring boot actuator HandlerMapping全网独家揭秘

            评论 抢沙发

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

            © 2014 - 2020 Java 技术驿站   网站地图  | 

            icp 湘ICP备14000180

            >>> 网站已平稳运行:

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

            支付宝扫一扫打赏

            微信扫一扫打赏