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

 2019-10-17 22:05  阅读(2057)
文章分类:Spring boot

前言

本文我们来介绍一下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;
          }
      
       initCorsConfiguration–>复写了RequestMappingHandlerMapping#initCorsConfiguration,直接使用本类配置的corsConfiguration,该方法在注册Handler时有用.代码如下:     protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { return this.corsConfiguration; } 
      
      
      
       MvcEndpointSecurityInterceptor
       AbstractEndpointHandlerMapping中持有的securityInterceptor默认是MvcEndpointSecurityInterceptor(自动装配).
       其字段,构造器如下:     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;
          }
       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;
          } 如果是(cors请求并且是options类型的请求并且请求头Access-Control-Request-Method存在)或者(不进行校验),则直接返回true 如果是options请求并且HandlerMethod不是MvcEndpoint的实例,则返回true 如果配置的endpoints.sensitive=false或者对应的endpoints.xxx.sensitive=false,则直接返回true 如果拥有相应的权限则返回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;
          } 如果org.springframework.security.config.annotation.web.WebSecurityConfigurer在当前类路径下存在,也就是加入了spring-boot-starter-security的依赖,则实例化AuthoritiesValidator 遍历需要的权限,依次判断其是否有对应的权限,只要有1个满足的话,则返回true 判断用户是否是某个种类的角色,类型可以在web.xml中配置(tomcat-users.xml) 如果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;
          } 如果都不满足,返回false 返回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中.
       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环境时生效 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());
                  }
              }
          } 判断 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);
          } 获得server.port的配置 如果server.port没有配置并且ServerProperties在beanFactory中存在的话,则获得ServerProperties中所配置的默认端口 获得management.port的配置,如果management.port没有配置并且ManagementServerProperties在beanFactory中存在的话,则获得ServerProperties中所配置的默认端口 如果managementPort端口号小于零则返回不可用 如果满足以下条件的任意1个,则managementPort 使用和serverPort一样的端口,返回same,否则,返回DIFFERENT: managementPort等于null(management.port没有配置) managementPort 等于8080 managementPort 等于serverPort 如果不一样 如果当前applicationContext为EmbeddedWebApplicationContext,并且有嵌入容器,则创建一个子WebApplicationContext 这也意味着使用actuate时,如果managementPort 和serverPort 不一样时,并且是部署在外置tomcat中时,actuate 会不生效的 如果一样,则 如果management.ssl.enabled 配置为true,则抛出IllegalStateException 向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);
          }
       实例化AnnotationConfigEmbeddedWebApplicationContext 注册配置类 注册嵌入容器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;
          } 添加CloseManagementContextListener监听器–> 处理ContextClosedEvent,ApplicationFailedEvent 事件.其最终处理都是关闭当前上下文.如下:     private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
              if (applicationContext == this.parentContext) {
                  this.childContext.close();
              }
          } 进行初始化 为ManagementContextResolver 重新设置ApplicationContext 为创建的子WebApplicationContext 
      
       EndpointWebMvcAutoConfiguration中有2个配置内部类: 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);
          } EndpointWebMvcConfiguration,代码如下:     @Configuration
          @Conditional(OnManagementMvcCondition.class)
          @Import(ManagementContextConfigurationsImportSelector.class)
          protected static class EndpointWebMvcConfiguration {
      
          } 当满足如下条件的任意1个时生效: managementPort等于null(management.port没有配置) managementPort 等于8080 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()]);
          } 加载/META-INF/spring.factories中配置的org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration 排序 默认返回的是:     org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration,\
          org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration EndpointWebMvcHypermediaManagementContextConfiguration,其注解如下:     @ManagementContextConfiguration
          @ConditionalOnClass(Link.class)
          @ConditionalOnWebApplication
          @ConditionalOnBean(HttpMessageConverters.class)
          @Conditional(EndpointHypermediaEnabledCondition.class)
          @EnableConfigurationProperties(ResourceProperties.class) 由于默认情况下,在类路径下不存在org.springframework.hateoas.Link.class,因此该类不会进行处理. 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;
          } 获得注册的MvcEndpoint 实例化CorsConfiguration 实例化EndpointHandlerMapping–>actuate 系列的HandlerMapping 设置EndpointHandlerMapping的前缀为management.contextPath 的配置值 实例化MvcEndpointSecurityInterceptor–> Interceptor,并进行设置 个性化设置–> 默认没有实现类 此刻就将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");
                  }
              }
          } 调用链如下: 
      
      
       来源:[]()
      
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> spring boot 源码解析55-spring boot actuator HandlerMapping全网独家揭秘

相关推荐