Springboot內建ApplicationContextInitializer--ConfigurationWarningsApplicationContextInitializer
阿新 • • 發佈:2018-12-03
原始碼分析
本文程式碼基於 Springboot 2.1.0
此ApplicationContextInitializer
用於檢查掃描包的問題,然後對這些一般性配置錯誤在日誌上輸出警告,通過這些日誌你可以知道哪些包存在問題或者掃描配置存在問題並作出相應的調整。該ApplicationContextInitializer
的執行僅僅是執行檢查和日誌輸出檢查結果,並不會改變啟動邏輯,也不會對應用狀態做出改動,所以也不會影響應用的正常執行。
package org.springframework.boot.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans. factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory. support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* ApplicationContextInitializer to report warnings for common misconfiguration
* mistakes.
* 對於一些一般性配置錯誤在日誌上輸出警告
*
* @author Phillip Webb
* @since 1.2.0
*/
public class ConfigurationWarningsApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Log logger = LogFactory
.getLog(ConfigurationWarningsApplicationContextInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext context) {
// 並非真正在該初始化方法中執行相應的檢查和日誌報警任務,
// 而是構造一個任務(getChecks()),然後註冊一個BeanDefinitionRegistryPostProcessor
// (ConfigurationWarningsPostProcessor)到容器,真正的檢查和日誌報警任務
// 會在容器的BeanFactory post-process過程中進行。
context.addBeanFactoryPostProcessor(
new ConfigurationWarningsPostProcessor(getChecks()));
}
/**
* Returns the checks that should be applied.
* 構建要執行的檢查任務
* @return the checks to apply
*/
protected Check[] getChecks() {
return new Check[] { new ComponentScanPackageCheck() };
}
/**
* BeanDefinitionRegistryPostProcessor to report warnings.
* 定義一個BeanDefinitionRegistryPostProcessor,它會在BeanFactory的post-process階段
* 執行指定的檢查邏輯,並在遇到問題時日誌輸出警告
*/
protected static final class ConfigurationWarningsPostProcessor
implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {
private Check[] checks;
public ConfigurationWarningsPostProcessor(Check[] checks) {
this.checks = checks;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (Check check : this.checks) {
// 執行指定的檢查任務
String message = check.getWarning(registry);
// 如果指定的檢查任務執行遇到錯誤,會有非空錯誤訊息返回,
if (StringUtils.hasLength(message)) {
// 如果有非空錯誤訊息返回,日誌輸出該錯誤訊息,使用警告級別
warn(message);
}
}
}
private void warn(String message) {
if (logger.isWarnEnabled()) {
logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
}
}
}
/**
* A single check that can be applied.
* 定義一個函式式介面,用於建模一個檢查,接收一個引數BeanDefinitionRegistry。
*/
@FunctionalInterface
protected interface Check {
/**
* Returns a warning if the check fails or null if there are no problems.
* 如果檢查失敗返回一個警告訊息,如果沒問題返回null
* @param registry the BeanDefinitionRegistry
* @return a warning message or null
*/
String getWarning(BeanDefinitionRegistry registry);
}
/**
* Check for @ComponentScan on problematic package.
* @ComponentScan 包掃描檢查問題包任務的抽象和建模
*/
protected static class ComponentScanPackageCheck implements Check {
private static final Set<String> PROBLEM_PACKAGES;
static {
Set<String> packages = new HashSet<>();
packages.add("org.springframework");
packages.add("org");
PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);
}
@Override
public String getWarning(BeanDefinitionRegistry registry) {
// 獲取所有要掃描的包的集合
Set<String> scannedPackages = getComponentScanningPackages(registry);
// 檢查所有要掃描的包,並返回其中有問題的包的集合
List<String> problematicPackages = getProblematicPackages(scannedPackages);
if (problematicPackages.isEmpty()) {
// 如果不存在問題包,返回null
return null;
}
// 返回一個訊息,描述哪些被掃描包存在問題
return "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of "
+ StringUtils.collectionToDelimitedString(problematicPackages, ", ")
+ ".";
}
protected Set<String> getComponentScanningPackages(
BeanDefinitionRegistry registry) {
Set<String> packages = new LinkedHashSet<>();
// 獲取bean登錄檔中所有BeanDefinition的名稱,也可以理解成所有登記的bean的名稱
String[] names = registry.getBeanDefinitionNames();
for (String name : names) {
// 獲取相應的BeanDefinitaion
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof AnnotatedBeanDefinition) {
// 如果這是一個型別為AnnotatedBeanDefinition的BeanDefinition,
// 從其註解屬性中搜集所有要掃描的包和類所在的包
AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
addComponentScanningPackages(packages,
annotatedDefinition.getMetadata());
}
}
return packages;
}
private void addComponentScanningPackages(Set<String> packages,
AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
.getAnnotationAttributes(ComponentScan.class.getName(), true));
if (attributes != null) {
addPackages(packages, attributes.getStringArray("value"));
addPackages(packages, attributes.getStringArray("basePackages"));
// 增加要掃描的類所在的包
addClasses(packages, attributes.getStringArray("basePackageClasses"));
if (packages.isEmpty()) {
// 將當前metadata所屬類的所在包也增加到packages中
packages.add(ClassUtils.getPackageName(metadata.getClassName()));
}
}
}
private void addPackages(Set<String> packages, String[] values) {
if (values != null) {
Collections.addAll(packages, values);
}
}
private void addClasses(Set<String> packages, String[] values) {
if (values != null) {
for (String value : values) {
packages.add(ClassUtils.getPackageName(value));
}
}
}
private List<String> getProblematicPackages(Set<String> scannedPackages) {
List<String> problematicPackages = new ArrayList<>();
for (String scannedPackage : scannedPackages) {
if (isProblematicPackage(scannedPackage)) {
problematicPackages.add(getDisplayName(scannedPackage));
}
}
return problematicPackages;
}
private boolean isProblematicPackage(String scannedPackage) {
//如果該包為null或者0長度字串,則認為這是一個問題包
if (scannedPackage == null || scannedPackage.isEmpty()) {
return true;
}
// 如果該包是PROBLEM_PACKAGES中的任何一個,也就是說如果它是
// org 或者 org.springframework 也認為是問題包
return PROBLEM_PACKAGES.contains(scannedPackage);
}
private String getDisplayName(String scannedPackage) {
if (scannedPackage == null || scannedPackage.isEmpty()) {
return "the default package";
}
return "'" + scannedPackage + "'";
}
}
}