上一篇提到了Spring註解Compoent原理,這次簡單介紹一下,什麼是Spring事件驅動,Springboot如何利用事物驅動進行加載.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//10分鐘
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
Properties defaultProperties = new Properties();
defaultProperties.put("server.port",0);
springApplication.setDefaultProperties(defaultProperties);
springApplication.addListeners(System.err::println);
springApplication.run(args);
// new SpringApplicationBuilder(DemoApplication.class)
// .properties("server.port=0").run(args);
}
}
代碼來自start.spring.io.官網。我們今天重點關注的是run方法。
public class SpringApplication {
.....
public ConfigurableApplicationContext run(String... args) {
//時間啓動時間錨點
StopWatch stopWatch = new StopWatch();
//開始啓動
stopWatch.start();
//定義上下文邊界
ConfigurableApplicationContext context = null;
//設置異常
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//設置系統環境變量
configureHeadlessProperty();
//獲取事件發佈器 其實就是SimpleApplicationEventMulticaster的Spring發佈器,用來發布消息
SpringApplicationRunListeners listeners = getRunListeners(args);
//發佈一個ApplicationStartingEvent事件
listeners.starting();
try {
//啓動參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//獲取系統參數 發佈ApplicationEnvironmentPreparedEvent
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//輸出benner
Banner printedBanner = printBanner(environment);
//根據參數設置創建不同的Springboot上下文有server,webflux,mvc等,並初始化ioc容器(DefaultListableBeanFactory) (AnnotationConfigApplicationContext)這個是默認的Context,並設置過濾器
context = createApplicationContext();
//創建異常
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//創建多個監聽器
//發佈 ApplicationContextInitializedEvent()初始化事件
//打印log日誌 此時ioc容器還未有Bean
//將spring applicationArguments printedBanner 設置成單例級別
//增加 Component 過濾器(includeFilters)
//準備 BeanDefinitionLoader Bean的載入器
//發佈 ApplicationPreparedEvent 事件應用預處理完畢
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//那麼到這裏準備開始載入Bean了
//ComponentScan在此處被調用,獲取包掃描的基礎路徑進行(ComponentScanAnnotationParser類 parse 方法 return
scanner.doScan(StringUtils.toStringArray(basePackages));
// 解析加載bean 並且處理自動裝配 import機制 SpringBoot spi機制(andidate.isAssignable(ImportSelector.class)),
//所有的邏輯 所有的邏輯在這裏完成parser.parse(candidates);
//ContextRefreshedEvent 發佈事件
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
...
}
class ComponentScanAnnotationParser {
...
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
...
}
5.0新特性 Components 可從配置文件META-INF/spring.components 寫入 , 不需要帶註解
public final class CandidateComponentsIndexLoader {
/**
* The location to look for components.
* <p>Can be present in multiple JAR files.
*/
public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
/**
* System property that instructs Spring to ignore the index, i.e.
* to always return {@code null} from {@link #loadIndex(ClassLoader)}.
* <p>The default is "false", allowing for regular use of the index. Switching this
* flag to {@code true} fulfills a corner case scenario when an index is partially
* available for some libraries (or use cases) but couldn't be built for the whole
* application. In this case, the application context fallbacks to a regular
* classpath arrangement (i.e. as no index was present at all).
*/
public static final String IGNORE_INDEX = "spring.index.ignore";
private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX);
private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class);
private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache =
new ConcurrentReferenceHashMap<>();
private CandidateComponentsIndexLoader() {
}
/**
* Load and instantiate the {@link CandidateComponentsIndex} from
* {@value #COMPONENTS_RESOURCE_LOCATION}, using the given class loader. If no
* index is available, return {@code null}.
* @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
* @return the index to use or {@code null} if no index was found
* @throws IllegalArgumentException if any module index cannot
* be loaded or if an error occurs while creating {@link CandidateComponentsIndex}
*/
@Nullable
public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader();
}
return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex);
}
@Nullable
private static CandidateComponentsIndex doLoadIndex(ClassLoader classLoader) {
if (shouldIgnoreIndex) {
return null;
}
try {
Enumeration<URL> urls = classLoader.getResources(COMPONENTS_RESOURCE_LOCATION);
if (!urls.hasMoreElements()) {
return null;
}
List<Properties> result = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
result.add(properties);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + result.size() + "] index(es)");
}
int totalCount = result.stream().mapToInt(Properties::size).sum();
return (totalCount > 0 ? new CandidateComponentsIndex(result) : null);
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load indexes from location [" +
COMPONENTS_RESOURCE_LOCATION + "]", ex);
}
}
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//此方法過濾
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
還有一個類, SpringApplicationRunListeners 此類貫穿上下文生命週期發佈不同的事件,
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
public void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
public void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
public void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
public void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
public void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
listener.failed(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
下圖是所有的Spring以及SpringBoot的監聽器 根據事件來處理不同的邏輯,所以說spring boot是一個基於spring事件處理的集成框架