spring boot 源码解析14-默认错误页面处理流程, 自定义,及EnableAutoConfigurationImportSelector处理

前言

之前在解析spring boot 加载bean 时 , 会调用ConfigurationClassParser#processImports,此时由于我们在启动类上注有@SpringBootApplication,而由于@SpringBootApplication 注有@EnableAutoConfiguration,该注解如下:

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

通过@Import,引入了 EnableAutoConfigurationImportSelector,同时又因为注解了@AutoConfigurationPackage,因此也就引入了AutoConfigurationPackages.Registrar.

因此在ConfigurationClassParser#processImports 会对 AutoConfigurationPackages$Registrar, EnableAutoConfigurationImportSelector 分别做处理.

同时由于EnableAutoConfigurationImportSelector 是DeferredImportSelector 的实现,因此会加入到ConfigurationClassParser的deferredImportSelectors中,由于AutoConfigurationPackages$Registrar是ImportBeanDefinitionRegistrar的子类,因此会加入到configClass 的ImportBeanDefinitionRegistrar中.

由之前的文章可知,在ConfigurationClassParser#processDeferredImportSelectors 会对deferredImportSelectors进行处理,会依次调用其selectImports方法,获得要导入的configurations,之后依次调用ConfigurationClassParser#processImports,进行导入.

因此,我们就从EnableAutoConfigurationImportSelector#selectImports 讲起.

解析

  1. EnableAutoConfigurationImportSelector 类图如下:

    EnableAutoConfigurationImportSelector类图

    因此selectImports最终会调用AutoConfigurationImportSelector中的实现,代码如下:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
             // 1. 得到注解信息
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            // 2. 得到注解中的所有属性信息
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            // 3. 得到候选配置列表
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            // 4. 去重
            configurations = removeDuplicates(configurations);
            // 5. 排序
            configurations = sort(configurations, autoConfigurationMetadata);
            // 6. 根据注解中的exclude信息去除不需要的
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            // 7. 派发事件
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

    8件事:

    1. 调用isEnabled进行判断是否需要进行处理,如果返回false,则直接return,否则,进行后续处理.其isEnabled方法 永远为true,因此会执行后续处理.
    2. 得到注解信息,其最终会加载META-INF/spring-autoconfigure-metadata.properties,由于默认情况下,spring-autoconfigure-metadata.properties 不存在,因此会最终返回PropertiesAutoConfigurationMetadata.
    3. 得到@EnableAutoConfiguration注解中的所有属性信息,
    4. 加载META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration, 在META-INF/spring.factories 配置内容如下:

      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
      org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
      org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
      org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
      org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
      org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
      org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
      org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
      org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
      org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
      org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
      org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
      org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
      org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
      org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
      org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
      org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
      org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
      org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
      org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
      org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
      org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
      org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
      org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
      org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
      org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
      org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
      org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
      org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
      org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
      org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
      org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
      org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
      org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
      org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
      org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
      org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
      org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
      org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
      org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
      org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
      org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
      org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
      org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
      org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
      org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
      org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
      org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
      org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
      org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
      org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
      org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
      org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
      org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
      org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
      org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
      
    5. 去重,代码如下:

      protected final <T> List<T> removeDuplicates(List<T> list) {
          return new ArrayList<T>(new LinkedHashSet<T>(list));
      }
    6. 排序,代码如下:

          private List<String> sort(List<String> configurations,
          AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
      configurations = new AutoConfigurationSorter(getMetadataReaderFactory(),
              autoConfigurationMetadata).getInPriorityOrder(configurations);
      return configurations;
      }

      最终调用getInPriorityOrder进行排序,代码如下:

      public List<String> getInPriorityOrder(Collection<String> classNames) {
      final AutoConfigurationClasses classes = new AutoConfigurationClasses(
              this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
      List<String> orderedClassNames = new ArrayList<String>(classNames);
      // Initially sort alphabetically
      Collections.sort(orderedClassNames);
      // Then sort by order
      Collections.sort(orderedClassNames, new Comparator<String>() {
      
          @Override
          public int compare(String o1, String o2) {
              int i1 = classes.get(o1).getOrder();
              int i2 = classes.get(o2).getOrder();
              return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
          }
      
      });
      // Then respect @AutoConfigureBefore @AutoConfigureAfter
      orderedClassNames = sortByAnnotation(classes, orderedClassNames);
      return orderedClassNames;
      }
    7. 根据注解中的exclude信息去除不需要的,代码如下:

      protected Set<String> getExclusions(AnnotationMetadata metadata,
          AnnotationAttributes attributes) {
      Set<String> excluded = new LinkedHashSet<String>();
      excluded.addAll(asList(attributes, "exclude"));
      excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
      excluded.addAll(getExcludeAutoConfigurationsProperty());
      return excluded;
      }
      1. 从AnnotationMetadata 获得配置的exclude
      2. 从AnnotationMetadata 获得配置的excludeName
      3. 读取配置spring.autoconfigure.exclude

      由于默认情况下,以上3中情况都没有配置,因此这里最终返回的是一个空集合.

      接下来调用 filter 进行过滤,代码如下:

      private List<String> filter(List<String> configurations,
          AutoConfigurationMetadata autoConfigurationMetadata) {
      long startTime = System.nanoTime();
      String[] candidates = configurations.toArray(new String[configurations.size()]);
      boolean[] skip = new boolean[candidates.length];
      boolean skipped = false;
      for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
          invokeAwareMethods(filter);
          boolean[] match = filter.match(candidates, autoConfigurationMetadata);
          for (int i = 0; i < match.length; i++) {
              if (!match[i]) {
                  skip[i] = true;
                  skipped = true;
              }
          }
      }
      if (!skipped) {
          return configurations;
      }
      List<String> result = new ArrayList<String>(candidates.length);
      for (int i = 0; i < candidates.length; i++) {
          if (!skip[i]) {
              result.add(candidates[i]);
          }
      }
      if (logger.isTraceEnabled()) {
          int numberFiltered = configurations.size() - result.size();
          logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                  + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                  + " ms");
      }
      return new ArrayList<String>(result);
      }
      1. 通过调用getAutoConfigurationImportFilters,加载META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,配置内容如下:

        org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
        org.springframework.boot.autoconfigure.condition.OnClassCondition

        只有一个–> OnClassCondition

      2. 在对OnClassCondition 进行初始化后,调用match 进行过滤,将符合要求的加入到result中进行返回.代码如下:

        public boolean[] match(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
        ConditionEvaluationReport report = getConditionEvaluationReport();
        ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
            autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];
        for (int i = 0; i < outcomes.length; i++) {
        match[i] = (outcomes[i] == null || outcomes[i].isMatch());
        if (!match[i] && outcomes[i] != null) {
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            if (report != null) {
                report.recordConditionEvaluation(autoConfigurationClasses[i], this,
                        outcomes[i]);
            }
        }
        }
        return match;
        }
        1. 调用getConditionEvaluationReport 获得ConditionEvaluationReport,代码如下:

              private ConditionEvaluationReport getConditionEvaluationReport() {
          if (this.beanFactory != null
          && this.beanFactory instanceof ConfigurableBeanFactory) {
          return ConditionEvaluationReport
              .get((ConfigurableListableBeanFactory) this.beanFactory);
          }
          return null;
          }

          调用

          public static ConditionEvaluationReport get(
          ConfigurableListableBeanFactory beanFactory) {
          synchronized (beanFactory) {
          ConditionEvaluationReport report;
          // 1. 如果当前beanFactory包含autoConfigurationReport定义的话,就从beanFactory中获取,
          if (beanFactory.containsSingleton(BEAN_NAME)) {
          report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
          }
          else {
          // 否则就实例化一个,然后进行注册
          report = new ConditionEvaluationReport();
          beanFactory.registerSingleton(BEAN_NAME, report);
          }
          // 2. 如果存在父容器的话,就从父容器中获取。
          locateParent(beanFactory.getParentBeanFactory(), report);
          return report;
          }
          }
          

          因此会最终返回id 为autoConfigurationReport,类型为ConditionEvaluationReport的bean.

        2. 调用getOutcomes获得ConditionOutcome[],代码如下:

          private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
          AutoConfigurationMetadata autoConfigurationMetadata) {
          // Split the work and perform half in a background thread. Using a single
          // additional thread seems to offer the best performance. More threads make
          // things worse
          int split = autoConfigurationClasses.length / 2;
          OutcomesResolver firstHalfResolver = createOutcomesResolver(
          autoConfigurationClasses, 0, split, autoConfigurationMetadata);
          OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
          autoConfigurationClasses, split, autoConfigurationClasses.length,
          autoConfigurationMetadata, this.beanClassLoader);
          ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
          ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
          ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
          System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
          System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
          return outcomes;
          }
          1. 为了提高效率,将autoConfigurationClasses 分为2半进行处理,前一半通过ThreadedOutcomesResolver进行处理,后一半通过StandardOutcomesResolver.
          2. 分别调用ThreadedOutcomesResolver, StandardOutcomesResolver 进行处理.

            1. StandardOutcomesResolver#getOutcomes 如下:

                  private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
              int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
              ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
              for (int i = start; i < end; i++) {
              String autoConfigurationClass = autoConfigurationClasses[i];
              Set<String> candidates = autoConfigurationMetadata
              .getSet(autoConfigurationClass, "ConditionalOnClass");
              if (candidates != null) {
              outcomes[i - start] = getOutcome(candidates);
              }
              }
              return outcomes;
              }
              1. 通过遍历autoConfigurationClasses,依次获得ConditionalOnClass配置的属性,调用getOutcome 进行处理.
              2. getOutcome 代码如下:

                private ConditionOutcome getOutcome(Set<String> candidates) {
                try {
                List<String> missing = getMatches(candidates, MatchType.MISSING,
                this.beanClassLoader);
                if (!missing.isEmpty()) {
                return ConditionOutcome.noMatch(
                ConditionMessage.forCondition(ConditionalOnClass.class)
                        .didNotFind("required class", "required classes")
                        .items(Style.QUOTE, missing));
                }
                }
                catch (Exception ex) {
                // We'll get another chance later
                }
                return null;
                }
                }

                调用

                MISSING {
                
                @Override
                public boolean matches(String className, ClassLoader classLoader) {
                    return !isPresent(className, classLoader);
                }
                };

            至此,就明白了,StandardOutcomesResolver解析配置的autoConfigurationClasses后一半,通过MISSING判断其配置的ConditionalOnClass 是否存在,

            1. ThreadedOutcomesResolver在实例化的时候,启动了一个线程,调用StandardOutcomesResolver#resolveOutcomes方法,其resolveOutcomes 代码如下:
            public ConditionOutcome[] resolveOutcomes() {
            try {
            this.thread.join();
            }
            catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            }
            return this.outcomes;}

            因此我们就明白了, ThreadedOutcomesResolver也会调用StandardOutcomesResolver中的resolveOutcomes进行处理,判断其是否有ConditionalOnClass不满足的情况.

            1. 进行数据的合并.
          3. 通过遍历第2步合并的outcomes,如果发现有不匹配的情况,则会打印日志(Trace 级别),并且会调用第一步获得的ConditionEvaluationReport的recordConditionEvaluation,代码如下:

            public void recordConditionEvaluation(String source, Condition condition,
            ConditionOutcome outcome) {
            Assert.notNull(source, "Source must not be null");
            Assert.notNull(condition, "Condition must not be null");
            Assert.notNull(outcome, "Outcome must not be null");
            this.unconditionalClasses.remove(source);
            if (!this.outcomes.containsKey(source)) {
            this.outcomes.put(source, new ConditionAndOutcomes());
            }
            this.outcomes.get(source).add(condition, outcome);
            this.addedAncestorOutcomes = false;
            }
    8. 派发AutoConfigurationImportEvent事件,代码如下:

      private void fireAutoConfigurationImportEvents(List<String> configurations,
          Set<String> exclusions) {
      List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
      if (!listeners.isEmpty()) {
          AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                  configurations, exclusions);
          for (AutoConfigurationImportListener listener : listeners) {
              invokeAwareMethods(listener);
              listener.onAutoConfigurationImportEvent(event);
          }
      }
      }
      1. 首先获得在 META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportListener,配置内容如下:

        org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
        org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
      2. 在对ConditionEvaluationReportAutoConfigurationImportListener注入beanFactory后,调用其onAutoConfigurationImportEvent 代码如下:

        public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
        if (this.beanFactory != null) {
        ConditionEvaluationReport report = ConditionEvaluationReport
                .get(this.beanFactory);
        report.recordEvaluationCandidates(event.getCandidateConfigurations());
        report.recordExclusions(event.getExclusions());
        }
        }
        1. 通过beanFactory 获得ConditionEvaluationReport
        2. 调用ConditionEvaluationReport#recordEvaluationCandidates.代码如下:

          public void recordEvaluationCandidates(List<String> evaluationCandidates) {
          Assert.notNull(evaluationCandidates, "evaluationCandidates must not be null");
          this.unconditionalClasses = new HashSet<String>(evaluationCandidates);
          }

          这样, ConditionEvaluationReport 就持有了在当前环境下生效的EnableAutoConfiguration.

        3. 调用ConditionEvaluationReport#recordExclusions,代码如下:

          public void recordExclusions(Collection<String> exclusions) {
          Assert.notNull(exclusions, "exclusions must not be null");
          this.exclusions = new ArrayList<String>(exclusions);
          }

          这样ConditionEvaluationReport 就持有了在当前环境下不满足要求的EnableAutoConfiguration.

    至此, EnableAutoConfigurationImportSelector 就处理完了,接着,会进行对生效的EnableAutoConfiguration进行处理,其中,有一个EnableAutoConfiguration的实现–>ErrorMvcAutoConfiguration 同样会后续的加载流程加载,其代码如下:

ErrorMvcAutoConfiguration 分析

由于ErrorMvcAutoConfiguration类有如下注解,

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(ResourceProperties.class)

因此该类会在web环境下,并且 Servlet.class, DispatcherServlet.class 在当前的类路径下,并且在WebMvcAutoConfiguration之间进行加载. 在spring boot 源码解析11-ConfigurationClassPostProcessor类加载解析 中我们知道会首先加载ErrorMvcAutoConfiguration中的内部类.

ErrorMvcAutoConfiguration有2个内部类: DefaultErrorViewResolverConfiguration, WhitelabelErrorViewConfiguration.我们来分别解释下:

DefaultErrorViewResolverConfiguration

DefaultErrorViewResolverConfiguration 代码如下:

@Configuration
    static class DefaultErrorViewResolverConfiguration {

        private final ApplicationContext applicationContext;

        private final ResourceProperties resourceProperties;

        DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
                ResourceProperties resourceProperties) {
            this.applicationContext = applicationContext;
            this.resourceProperties = resourceProperties;
        }

        @Bean
        @ConditionalOnBean(DispatcherServlet.class)
        @ConditionalOnMissingBean
        public DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext,
                    this.resourceProperties);
        }

    }



其中DefaultErrorViewResolverConfiguration ,当BeanFactory中有DispatcherServlet类型的Bean并且当BeanFactory中不存在DefaultErrorViewResolver时,向BeanFactory 注册了一个id为conventionErrorViewResolver,类型为DefaultErrorViewResolver.

WhitelabelErrorViewConfiguration

WhitelabelErrorViewConfiguration 代码如下:

@Configuration
    @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
    @Conditional(ErrorTemplateMissingCondition.class)
    protected static class WhitelabelErrorViewConfiguration {

        private final SpelView defaultErrorView = new SpelView(
                "<html><body><h1>Whitelabel Error Page</h1>"
                        + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                        + "<div id='created'>${timestamp}</div>"
                        + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                        + "<div>${message}</div></body></html>");

        @Bean(name = "error")
        @ConditionalOnMissingBean(name = "error")
        public View defaultErrorView() {
            return this.defaultErrorView;
        }

        // If the user adds @EnableWebMvc then the bean name view resolver from
        // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
        @Bean
        @ConditionalOnMissingBean(BeanNameViewResolver.class)
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
            return resolver;
        }

    }

WhitelabelErrorViewConfiguration类上有@ConditionalOnProperty(prefix = “server.error.whitelabel”, name = “enabled”, matchIfMissing = true),因此,当配置有server.error.whitelabel.enabled=true时,默认为true. 又因为有@Conditional(ErrorTemplateMissingCondition.class) 注解,其继承结构如下:

ErrorTemplateMissingCondition-type-tree
因此会调用其getMatchOutcome,代码如下:

private static class ErrorTemplateMissingCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage
                    .forCondition("ErrorTemplate Missing");
            // 在构造器中从spring.factories文件中找出key为TemplateAvailabilityProvider为类,TemplateAvailabilityProvider用来查询视图是否可用
            TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(
                    context.getClassLoader());
            TemplateAvailabilityProvider provider = providers.getProvider("error",
                    context.getEnvironment(), context.getClassLoader(),
                    context.getResourceLoader());
            if (provider != null) {
                // 如果error视图可用,则WhitelabelErrorViewConfiguration不会被构造
                return ConditionOutcome
                        .noMatch(message.foundExactly("template from " + provider));
            }
            // WhitelabelErrorViewConfiguration被构造
            return ConditionOutcome
                    .match(message.didNotFind("error template view").atAll());
        }

    }
  1. 实例化TemplateAvailabilityProviders,其构造器会从spring.factories文件中找出key为org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider的类,TemplateAvailabilityProvider用来查询视图是否可用.配置如下:

    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider
  2. 查询是否存在名为error的视图,如果存在,则返回false,否则返回匹配.

因此 WhitelabelErrorViewConfiguration 会注册id 为 error,类型为View的Bean,同时当BeanFactory不存在BeanNameViewResolver类型的bean时,会注册一个id为beanNameViewResolver,类型为BeanNameViewResolver的bean。

ErrorMvcAutoConfiguration注册的bean

ErrorMvcAutoConfiguration依次注册了如下几个bean:

  1. 当BeanFactory中不存在ErrorAttributes类型的bean时,会注册一个id为errorAttributes,类型为DefaultErrorAttributes的bean。代码如下:

        @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }
  2. 当BeanFactory中不存在ErrorController类型的bean时,会注册一个id为basicErrorController,类型为BasicErrorController的bean,代码如下:

    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }

    在其类上有如下注解:

    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")

    因此该映射路径为/error

  3. 注册一个id为errorPageCustomizer,类型为ErrorPageCustomizer的bean.代码如下:

        @Bean
    public ErrorPageCustomizer errorPageCustomizer() {
        return new ErrorPageCustomizer(this.serverProperties);
    }

    其实现了ErrorPageRegistrar,Ordered 接口,其registerErrorPages调用链如下:

    ErrorPageCustomizer-call-tree
    代码如下:

    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
                    + this.properties.getError().getPath());
            errorPageRegistry.addErrorPages(errorPage);
        }

    调用AbstractConfigurableEmbeddedServletContainer#addErrorPages,进行注册,代码如下:

    public void addErrorPages(ErrorPage... errorPages) {
        Assert.notNull(errorPages, "ErrorPages must not be null");
        this.errorPages.addAll(Arrays.asList(errorPages));
    }

    因此在加载TomcatEmbeddedServletContainerFactory时,向其添加了一个ErrorPage,其路径为/error.

  4. 注册一个id为preserveErrorControllerTargetClassPostProcessor,类型为PreserveErrorControllerTargetClassPostProcessor的bean,代码如下:

        @Bean
    public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
        return new PreserveErrorControllerTargetClassPostProcessor();
    }

    PreserveErrorControllerTargetClassPostProcessor实现了BeanFactoryPostProcessor接口,其实现了postProcessBeanFactory方法,其调用链如下:

    PreserveErrorControllerTargetClassPostProcessor-call-tree
    postProcessBeanFactory代码如下:

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                throws BeansException {
            String[] errorControllerBeans = beanFactory
                    .getBeanNamesForType(ErrorController.class, false, false);
            for (String errorControllerBean : errorControllerBeans) {
                try {
                    beanFactory.getBeanDefinition(errorControllerBean).setAttribute(
                            AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
                }
                catch (Throwable ex) {
                    // Ignore
                }
            }
        }

    很简单,首先获得在beanFactory中获得ErrorController类型的bean,依次遍历之,向其设置了preserveTargetClass属性为 true.

BasicErrorController 处理流程

  1. 当Spring boot 在web环境中出现错误时,最终会调用errorHtml方法进行处理.其映射路径为/error,代码如下:

    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        // 设置响应码
        HttpStatus status = getStatus(request);
        // 设置一些信息,比如timestamp、statusCode、错误message等
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        // 返回error视图
        return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }
    1. 设置响应码
    2. 生成一些数据,存入到Map中,如timestamp、statusCode、错误message等
    3. 调用resolveErrorView进行解析.如果解析成功,则返回ModelAndView,否则,返回路径为error的ModelAndView.其resolveErrorView代码如下:

      protected ModelAndView resolveErrorView(HttpServletRequest request,
          HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
      for (ErrorViewResolver resolver : this.errorViewResolvers) {
          ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
          if (modelAndView != null) {
              return modelAndView;
          }
      }
      return null;
      }

      通过遍历其errorViewResolvers ,依次调用其resolveErrorView 进行处理,如果获得一个ModelAndView 则直接返回.

      注意,这里只有一个ErrorViewResolver–>DefaultErrorViewResolver,其resolveErrorView如下:

      public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
          Map<String, Object> model) {
      ModelAndView modelAndView = resolve(String.valueOf(status), model);
      if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
          modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
      }
      return modelAndView;
      }
      1. 调用resolve 进行解析.代码如下:

        private ModelAndView resolve(String viewName, Map<String, Object> model) {
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
            .getProvider(errorViewName, this.applicationContext);
        if (provider != null) {
        return new ModelAndView(errorViewName, model);
        }
        return resolveResource(errorViewName, model);
        }
        1. 通过字符串拼接出errorViewName.

          一般情况下,当程序出现错误时,其状态码为500,因此,在此处errorViewName为error/500

        2. 通过TemplateAvailabilityProviders#getProvider 尝试获得配置的模板。如果获取到,则直接返回配置的TemplateAvailabilityProvider,否则,进行第三步.

        3. 调用resolveResource进行加载.代码如下:

              private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
          for (String location : this.resourceProperties.getStaticLocations()) {
          try {
          Resource resource = this.applicationContext.getResource(location);
          resource = resource.createRelative(viewName + ".html");
          if (resource.exists()) {
              return new ModelAndView(new HtmlResourceView(resource), model);
          }
          }
          catch (Exception ex) {
          }
          }
          return null;
          }

          很简单,依次从

          • classpath:/META-INF/resources/
          • classpath:/resources/
          • classpath:/static/
          • classpath:/public/

          路径下加载 viewName.html.如果加载到,则返回ModelAndView,否则,返回null.
          对于当前来说,是在以上路径加载500.html,很明显是加载不到的,因此会进入到第2步进行处理.

      2. 如果第一步,没有获取到并且SERIES_VIEWS 中存在该类型的配置,则再次调用resolve进行解析. SERIES_VIEWS 如下:

        private static final Map<Series, String> SERIES_VIEWS;
        static {
        Map<Series, String> views = new HashMap<Series, String>();
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
        }

        因此,会再次调用resolve 尝试加载5xx.html.很明显会返回null.

    因此,会返回路径为error的ModelAndView .

  2. 还记得吗,在WhitelabelErrorViewConfiguration中我们注册了一个id为error,类型为View的Bean,同时注册了一个BeanNameViewResolver.因此,/error 最终返回的是在WhitelabelErrorViewConfiguration注册的View.同时,由于其是View的实现,因此会最终调用render进行渲染(springmvc 处理流程).其代码如下:

        public void render(Map<String, ?> model, HttpServletRequest request,
                HttpServletResponse response) throws Exception {
            if (response.getContentType() == null) {
                response.setContentType(getContentType());
            }
            Map<String, Object> map = new HashMap<String, Object>(model);
            map.put("path", request.getContextPath());
            PlaceholderResolver resolver = new ExpressionResolver(getExpressions(), map);
            String result = this.helper.replacePlaceholders(this.template, resolver);
            response.getWriter().append(result);
        }

    3件事:

    1. 设置ContentType 为text/html
    2. 实例化ExpressionResolver.在实例化过程中,首先调用getExpressions获得expressions.代码如下:

      private Map<String, Expression> getExpressions() {
          if (this.expressions == null) {
              synchronized (this) {
                  // 1. 实例化ExpressionCollector.
                  ExpressionCollector expressionCollector = new ExpressionCollector();
                  // 2. 调用replacePlaceholders,将占位符依次放入ExpressionCollector中
                  this.helper.replacePlaceholders(this.template, expressionCollector);
                  // 3. 对于expressions进行赋值
                  this.expressions = expressionCollector.getExpressions();
              }
          }
          return this.expressions;
      }
      1. 如果expressions等于null,则进行初始化,否则直接返回.
      2. 实例化ExpressionCollector.
      3. 调用replacePlaceholders,将占位符依次放入ExpressionCollector中.这里的处理,我们之前的文章有涉及到,到解析到文本中有${Placeholder} 时,则会通过字符串截取的方式,获得Placeholder,同时调用PlaceholderResolver#resolvePlaceholder进行处理.这里由于传入的模板是:

        private final SpelView defaultErrorView = new SpelView(
            "<html><body><h1>Whitelabel Error Page</h1>"
                    + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                    + "<div id='created'>${timestamp}</div>"
                    + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                    + "<div>${message}</div></body></html>");

        因此存在 timestamp, {error}, status, {message} 4个占位符,同时由于当前PlaceholderResolver为ExpressionCollector,其resolvePlaceholder 如下:

        public String resolvePlaceholder(String name) {
            this.expressions.put(name, this.parser.parseExpression(name));
            return null;
        }

        因此ExpressionCollector 最终在expressions 存入了key为name(timestamp, error, status, message),value为SpelExpression.

        注意,ExpressionCollector#resolvePlaceholder 方法最终返回了null,为什么?
        这样处理的话,则不会对文本进行替换,而是在视图渲染时进行真正的替换,这里只是为了提取key.

      4. 对于expressions进行赋值

      接下来,实例化ExpressionResolver,其构造器如下:

      ExpressionResolver(Map<String, Expression> expressions, Map<String, ?> map) {
          this.expressions = expressions;
          this.context = getContext(map);
      }
      1. 对expressions 进行赋值,这样的话, ExpressionResolver 就间接的持有了模板中的占位符
      2. 实例化EvaluationContext,该部分spel部分的知识了,就不展开了
    3. 进行占位符处理,同样的流程,会对默认的模板中的占位符–>( timestamp, {error}, status, {message} )依次调用ExpressionResolver进行处理.代码如下:

      public String resolvePlaceholder(String placeholderName) {
          Expression expression = this.expressions.get(placeholderName);
          return escape(expression == null ? null : expression.getValue(this.context));
      }

      如果expressions 中不存在对应的placeholderName的话,则返回null,否则调用Expression#getValue,进行处理.这里属于spel部分的知识了,就不展开了

    4. 输出

错误页面自定义

通过之前的内容可知,当spring boot 中出现错误时,并且在没有模板引擎配置错误模板时,则会在以下路径下查找对应的错误页面进行渲染:

  • classpath:/META-INF/resources/error
  • classpath:/resources/error
  • classpath:/static/error
  • classpath:/public/error

因此,很简单,我们只需在以上路径配置5xx.html,4xx.html 即可.比如:在/META-INF/resources/error中放入5xx.html,内容如下:

<head>
 <meta http-equiv=Content-Type content="text/html;charset=utf-8">
 <title>500页面</title>
</head>
<html>
    <h1>不好意思,出错了.</h1>
</html>

当程序出错时,则返回如下内容:

错误页面自定义

这里有个问题,假设我们在/META-INF/resources/error,/resources/error,/static/error,/public/error,/error 依次放入5xx.html,并在其页面中加入存放路径的输出,比如在/META-INF/resources/error 中的5xx.html 内容如下:

<head>
 <meta http-equiv=Content-Type content="text/html;charset=utf-8">
 <title>500页面</title>
</head>
<html>
    <h1>不好意思,出错了.路径为/META-INF/resources/error</h1>
</html>

/resources/error 中的5xx.html 内容如下:

<head>
 <meta http-equiv=Content-Type content="text/html;charset=utf-8">
 <title>500页面</title>
</head>
<html>
    <h1>不好意思,出错了.路径为/resources/error</h1>
</html>

则最终出错时,返回哪个页面呢?

答案是 /META-INF/resources/error, spring boot 会依次遍历/META-INF/resources/error,/resources/error,/resources/error,/static/error,/public/error,如果加载到,则直接返回ModelAndView.不会在继续加载.代码如下:

                    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        for (String location : this.resourceProperties.getStaticLocations()) {
            try {
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return new ModelAndView(new HtmlResourceView(resource), model);
                }
            }
            catch (Exception ex) {
            }
        }
        return null;
    }
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要获取spring-boot-admin-starter-client自定义的属性,可以按照以下步骤进行操作: 1. 首先,确保你的项目中已经添加了spring-boot-admin-starter-client包,并成功进行了打包。 2. 在项目的配置文件(比如application.properties或application.yml)中,可以使用以下格式设置spring-boot-admin-starter-client的自定义属性: ``` spring.boot.admin.client.custom-property=value ``` 其中,custom-property是你自定义的属性名,value是对应的属性值。 3. 可以通过在spring-boot-admin-starter-server项目的代码中使用@ConfigurationProperties注解来获取spring-boot-admin-starter-client的自定义属性。例如: ```java @ConfigurationProperties(prefix = "spring.boot.admin.client") public class MyConfiguration { private String customProperty; // getter and setter // other configurations } ``` 在这个配置类中,使用prefix属性指定了属性的前缀为"spring.boot.admin.client",这样就能获取到spring-boot-admin-starter-client的自定义属性。 4. 运行spring-boot-admin-starter-server项目时,就可以通过MyConfiguration类获取spring-boot-admin-starter-client的自定义属性了。 综上所述,你可以通过在项目的配置文件中设置属性,并使用@ConfigurationProperties注解获取这些自定义属性来获取spring-boot-admin-starter-client的自定义属性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [spring-boot-admin-client-2.6.2-API文档-中文版.zip](https://download.csdn.net/download/qq_36462452/85294581)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [spring-boot-admin-starter-client与spring-boot版本不匹配的坑](https://blog.csdn.net/mahui_1980/article/details/117528352)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值