之前在解析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件事:
加载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 List removeDuplicates(List list) {
return new ArrayList(new LinkedHashSet(list));
}
排序,代码如下:
private List<String> sort(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
configurations = new AutoConfigurationSorter(getMetadataReaderFactory(),
autoConfigurationMetadata).getInPriorityOrder(configurations);
return configurations;
}
最终调用getInPriorityOrder进行排序,代码如下:
public List getInPriorityOrder(Collection classNames) {
final AutoConfigurationClasses classes = new AutoConfigurationClasses(
this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
List orderedClassNames = new ArrayList(classNames);
// Initially sort alphabetically
Collections.sort(orderedClassNames);
// Then sort by order
Collections.sort(orderedClassNames, new Comparator() {
@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;
}
由于默认情况下,以上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;
}
分别调用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;
}
getOutcome 代码如下:
private ConditionOutcome getOutcome(Set candidates) {
try {
List 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 是否存在,
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 configurations,
Set exclusions) {
List 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());
}
}
调用ConditionEvaluationReport#recordEvaluationCandidates.代码如下:
public void recordEvaluationCandidates(List evaluationCandidates) {
Assert.notNull(evaluationCandidates, "evaluationCandidates must not be null");
this.unconditionalClasses = new HashSet(evaluationCandidates);
}
这样, ConditionEvaluationReport 就持有了在当前环境下生效的EnableAutoConfiguration.
调用ConditionEvaluationReport#recordExclusions,代码如下:
public void recordExclusions(Collection exclusions) {
Assert.notNull(exclusions, "exclusions must not be null");
this.exclusions = new ArrayList(exclusions);
}
这样ConditionEvaluationReport 就持有了在当前环境下不满足要求的EnableAutoConfiguration.
至此, EnableAutoConfigurationImportSelector 就处理完了,接着,会进行对生效的EnableAutoConfiguration进行处理,其中,有一个EnableAutoConfiguration的实现–>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 代码如下:
@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 代码如下:
@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final SpelView defaultErrorView = new SpelView(
"Whitelabel Error Page
"
+ "This application has no explicit mapping for /error, so you are seeing this as a fallback.
"
+ "${timestamp}"
+ "There was an unexpected error (type=${error}, status=${status})."
+ "${message}");
@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) 注解,其继承结构如下:
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:
当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方法,其调用链如下:
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.
当Spring boot 在web环境中出现错误时,最终会调用errorHtml方法进行处理.其映射路径为/error,代码如下:
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
// 设置响应码
HttpStatus status = getStatus(request);
// 设置一些信息,比如timestamp、statusCode、错误message等
Map 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);
}
调用resolveErrorView进行解析.如果解析成功,则返回ModelAndView,否则,返回路径为error的ModelAndView.其resolveErrorView代码如下:
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map 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 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;
}
很简单,依次从
路径下加载 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件事:
实例化ExpressionResolver.在实例化过程中,首先调用getExpressions获得expressions.代码如下:
private Map 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;
}
调用replacePlaceholders,将占位符依次放入ExpressionCollector中.这里的处理,我们之前的文章有涉及到,到解析到文本中有${Placeholder} 时,则会通过字符串截取的方式,获得Placeholder,同时调用PlaceholderResolver#resolvePlaceholder进行处理.这里由于传入的模板是:
private final SpelView defaultErrorView = new SpelView(
"<html><body><h1>Whitelabel Error Pageh1>"
+ "<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);
}
进行占位符处理,同样的流程,会对默认的模板中的占位符–>( 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 中出现错误时,并且在没有模板引擎配置错误模板时,则会在以下路径下查找对应的错误页面进行渲染:
因此,很简单,我们只需在以上路径配置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/errorh1>
html>
/resources/error 中的5xx.html 内容如下:
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
<title>500页面title>
head>
<html>
<h1>不好意思,出错了.路径为/resources/errorh1>
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;
}