前言
之前在解析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 讲起.
解析
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件事:
- 调用isEnabled进行判断是否需要进行处理,如果返回false,则直接return,否则,进行后续处理.其isEnabled方法 永远为true,因此会执行后续处理.
- 得到注解信息,其最终会加载META-INF/spring-autoconfigure-metadata.properties,由于默认情况下,spring-autoconfigure-metadata.properties 不存在,因此会最终返回PropertiesAutoConfigurationMetadata.
- 得到@EnableAutoConfiguration注解中的所有属性信息,
加载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
去重,代码如下:
protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<T>(new LinkedHashSet<T>(list)); }
排序,代码如下:
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; }
根据注解中的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; }
- 从AnnotationMetadata 获得配置的exclude
- 从AnnotationMetadata 获得配置的excludeName
- 读取配置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); }
通过调用getAutoConfigurationImportFilters,加载META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,配置内容如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition
只有一个–> OnClassCondition
在对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; }
调用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.
调用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; }
- 为了提高效率,将autoConfigurationClasses 分为2半进行处理,前一半通过ThreadedOutcomesResolver进行处理,后一半通过StandardOutcomesResolver.
分别调用ThreadedOutcomesResolver, StandardOutcomesResolver 进行处理.
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; }
- 通过遍历autoConfigurationClasses,依次获得ConditionalOnClass配置的属性,调用getOutcome 进行处理.
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 是否存在,
- ThreadedOutcomesResolver在实例化的时候,启动了一个线程,调用StandardOutcomesResolver#resolveOutcomes方法,其resolveOutcomes 代码如下:
public ConditionOutcome[] resolveOutcomes() { try { this.thread.join(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } return this.outcomes;}
因此我们就明白了, ThreadedOutcomesResolver也会调用StandardOutcomesResolver中的resolveOutcomes进行处理,判断其是否有ConditionalOnClass不满足的情况.
- 进行数据的合并.
通过遍历第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; }
派发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); } } }
首先获得在 META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportListener,配置内容如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
在对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()); } }
- 通过beanFactory 获得ConditionEvaluationReport
调用ConditionEvaluationReport#recordEvaluationCandidates.代码如下:
public void recordEvaluationCandidates(List<String> evaluationCandidates) { Assert.notNull(evaluationCandidates, "evaluationCandidates must not be null"); this.unconditionalClasses = new HashSet<String>(evaluationCandidates); }
这样, ConditionEvaluationReport 就持有了在当前环境下生效的EnableAutoConfiguration.
调用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) 注解,其继承结构如下:
因此会调用其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());
}
}
实例化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
查询是否存在名为error的视图,如果存在,则返回false,否则返回匹配.
因此 WhitelabelErrorViewConfiguration 会注册id 为 error,类型为View的Bean,同时当BeanFactory不存在BeanNameViewResolver类型的bean时,会注册一个id为beanNameViewResolver,类型为BeanNameViewResolver的bean。
ErrorMvcAutoConfiguration注册的bean
ErrorMvcAutoConfiguration依次注册了如下几个bean:
当BeanFactory中不存在ErrorAttributes类型的bean时,会注册一个id为errorAttributes,类型为DefaultErrorAttributes的bean。代码如下:
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
当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
注册一个id为errorPageCustomizer,类型为ErrorPageCustomizer的bean.代码如下:
@Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties); }
其实现了ErrorPageRegistrar,Ordered 接口,其registerErrorPages调用链如下:
代码如下: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.
注册一个id为preserveErrorControllerTargetClassPostProcessor,类型为PreserveErrorControllerTargetClassPostProcessor的bean,代码如下:
@Bean public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() { return new PreserveErrorControllerTargetClassPostProcessor(); }
PreserveErrorControllerTargetClassPostProcessor实现了BeanFactoryPostProcessor接口,其实现了postProcessBeanFactory方法,其调用链如下:
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 处理流程
当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); }
- 设置响应码
- 生成一些数据,存入到Map中,如timestamp、statusCode、错误message等
调用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; }
调用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); }
通过字符串拼接出errorViewName.
一般情况下,当程序出现错误时,其状态码为500,因此,在此处errorViewName为error/500
通过TemplateAvailabilityProviders#getProvider 尝试获得配置的模板。如果获取到,则直接返回配置的TemplateAvailabilityProvider,否则,进行第三步.
调用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步进行处理.
如果第一步,没有获取到并且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 .
还记得吗,在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件事:
- 设置ContentType 为text/html
实例化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; }
- 如果expressions等于null,则进行初始化,否则直接返回.
- 实例化ExpressionCollector.
调用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.对于expressions进行赋值
接下来,实例化ExpressionResolver,其构造器如下:
ExpressionResolver(Map<String, Expression> expressions, Map<String, ?> map) { this.expressions = expressions; this.context = getContext(map); }
- 对expressions 进行赋值,这样的话, ExpressionResolver 就间接的持有了模板中的占位符
- 实例化EvaluationContext,该部分spel部分的知识了,就不展开了
进行占位符处理,同样的流程,会对默认的模板中的占位符–>( 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部分的知识了,就不展开了
- 输出
错误页面自定义
通过之前的内容可知,当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;
}