范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

SpringBoot核心原理自动配置事件驱动Condition

  来源:blog.csdn.net/l6108003/article/details/106966386前言
  SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本非常低,但是学习其实现原理的成本大大增加,需要先了解熟悉Spring原理。
  如果还不清楚Spring原理的,可以先查看博主之前的文章,本篇主要分析SpringBoot的启动、自动配置、Condition、事件驱动原理。  正文启动原理
  SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:  @SpringBootApplication(scanBasePackages = {"cn.dark"}) public class SpringbootDemo {      public static void main(String[] args) {      // 第一种         SpringApplication.run(SpringbootDemo .class, args);    // 第二种         new SpringApplicationBuilder(SpringbootDemo .class)).run(args);    // 第三种         SpringApplication springApplication = new SpringApplication(SpringbootDemo.class);         springApplication.run();       } }
  可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注 @SpringBootApplication 注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?
  在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个 ApplicationContext 的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。
  另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:   public ConfigurableApplicationContext run(String... args) {   // 统计时间用的工具类   StopWatch stopWatch = new StopWatch();   stopWatch.start();   ConfigurableApplicationContext context = null;   Collection exceptionReporters = new ArrayList<>();   configureHeadlessProperty();   // 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载   // META-INF/spring.factories文件下的类   SpringApplicationRunListeners listeners = getRunListeners(args);    // 首先调用SpringApplicationRunListener的starting方法   listeners.starting();   try {    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);     // 处理配置数据    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);    configureIgnoreBeanInfo(environment);     // 启动时打印banner    Banner printedBanner = printBanner(environment);     // 创建上下文对象    context = createApplicationContext();     // 获取SpringBootExceptionReporter接口的类,异常报告    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,      new Class[] { ConfigurableApplicationContext.class }, context);     prepareContext(context, environment, listeners, applicationArguments, printedBanner);     // 核心方法,启动spring容器    refreshContext(context);    afterRefresh(context, applicationArguments);     // 统计结束    stopWatch.stop();    if (this.logStartupInfo) {     new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);    }    // 调用started    listeners.started(context);     // ApplicationRunner    // CommandLineRunner    // 获取这两个接口的实现类,并调用其run方法    callRunners(context, applicationArguments);   }   catch (Throwable ex) {    handleRunFailure(context, ex, exceptionReporters, listeners);    throw new IllegalStateException(ex);   }    try {    // 最后调用running方法    listeners.running(context);   }   catch (Throwable ex) {    handleRunFailure(context, ex, exceptionReporters, null);    throw new IllegalStateException(ex);   }   return context;  }
  SpringBoot的启动流程就是这个方法,先看 getRunListeners 方法,这个方法就是去拿到所有的SpringApplicationRunListener 实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:  private SpringApplicationRunListeners getRunListeners(String[] args) {   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };   return new SpringApplicationRunListeners(logger,     getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));  }   private  Collection getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object... args) {   ClassLoader classLoader = getClassLoader();   // Use names and ensure unique to protect against duplicates   Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));   // 加载上来后反射实例化   List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);   AnnotationAwareOrderComparator.sort(instances);   return instances;  }   public static List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {   String factoryTypeName = factoryType.getName();   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());  }   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";   private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {   MultiValueMap result = cache.get(classLoader);   if (result != null) {    return result;   }    try {    Enumeration urls = (classLoader != null ?      classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));    result = new LinkedMultiValueMap<>();    while (urls.hasMoreElements()) {     URL url = urls.nextElement();     UrlResource resource = new UrlResource(url);     Properties properties = PropertiesLoaderUtils.loadProperties(resource);     for (Map.Entry<?, ?> entry : properties.entrySet()) {      String factoryTypeName = ((String) entry.getKey()).trim();      for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {       result.add(factoryTypeName, factoryImplementationName.trim());      }     }    }    cache.put(classLoader, result);    return result;   }  }
  一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从 META-INF/spring.factories 文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。
  为什么要这样做呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过 @Import 注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。
  回到run方法中,可以看到调用了 createApplicationContext 方法,见名知意,这个就是去创建应用上下文对象:  public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."    + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";   protected ConfigurableApplicationContext createApplicationContext() {   Class<?> contextClass = this.applicationContextClass;   if (contextClass == null) {    try {     switch (this.webApplicationType) {     case SERVLET:      contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);      break;     case REACTIVE:      contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);      break;     default:      contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);     }    }    catch (ClassNotFoundException ex) {     throw new IllegalStateException(       "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);    }   }   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);  }
  注意这里通过反射实例化了一个新的没见过的上下文对象 AnnotationConfigServletWebServerApplicationContext ,这个是SpringBoot扩展的,看看其构造方法:  public AnnotationConfigServletWebServerApplicationContext() {   this.reader = new AnnotatedBeanDefinitionReader(this);   this.scanner = new ClassPathBeanDefinitionScanner(this);  }
  如果你有看过Spring注解驱动的实现原理,这两个对象肯定不会陌生,一个实支持注解解析的,另外一个是扫描包用的。
  上下文创建好了,下一步自然就是调用refresh方法启动容器:   private void refreshContext(ConfigurableApplicationContext context) {   refresh(context);   if (this.registerShutdownHook) {    try {     context.registerShutdownHook();    }    catch (AccessControlException ex) {     // Not allowed in some environments.    }   }  }   protected void refresh(ApplicationContext applicationContext) {   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);   ((AbstractApplicationContext) applicationContext).refresh();  }
  这里首先会调用到其父类中 ServletWebServerApplicationContext :  public final void refresh() throws BeansException, IllegalStateException {   try {    super.refresh();   }   catch (RuntimeException ex) {    stopAndReleaseWebServer();    throw ex;   }  }
  可以看到是直接委托给了父类:   public void refresh() throws BeansException, IllegalStateException {   synchronized (this.startupShutdownMonitor) {    // Prepare this context for refreshing.    prepareRefresh();     // Tell the subclass to refresh the internal bean factory.    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();     // Prepare the bean factory for use in this context.    prepareBeanFactory(beanFactory);     try {     // Allows post-processing of the bean factory in context subclasses.     postProcessBeanFactory(beanFactory);      // Invoke factory processors registered as beans in the context.     invokeBeanFactoryPostProcessors(beanFactory);      // Register bean processors that intercept bean creation.     registerBeanPostProcessors(beanFactory);      // Initialize message source for this context.     initMessageSource();      // Initialize event multicaster for this context.     initApplicationEventMulticaster();      // Initialize other special beans in specific context subclasses.     onRefresh();      // Check for listener beans and register them.     registerListeners();      // Instantiate all remaining (non-lazy-init) singletons.     finishBeanFactoryInitialization(beanFactory);      // Last step: publish corresponding event.     finishRefresh();    }     catch (BeansException ex) {     if (logger.isWarnEnabled()) {      logger.warn("Exception encountered during context initialization - " +        "cancelling refresh attempt: " + ex);     }      // Destroy already created singletons to avoid dangling resources.     destroyBeans();      // Reset "active" flag.     cancelRefresh(ex);      // Propagate exception to caller.     throw ex;    }     finally {     // Reset common introspection caches in Spring"s core, since we     // might not ever need metadata for singleton beans anymore...     resetCommonCaches();    }   }  }
  这个方法不会陌生吧,之前已经分析过了,这里不再赘述,至此SpringBoot的容器就启动了,但是Tomcat启动是在哪里呢?run方法中也没有看到。
  实际上Tomcat的启动也是在refresh流程中,这个方法其中一步是调用了onRefresh方法,在Spring中这是一个没有实现的模板方法,而SpringBoot就通过这个方法完成了Tomcat的启动:   protected void onRefresh() {   super.onRefresh();   try {    createWebServer();   }   catch (Throwable ex) {    throw new ApplicationContextException("Unable to start web server", ex);   }  }   private void createWebServer() {   WebServer webServer = this.webServer;   ServletContext servletContext = getServletContext();   if (webServer == null && servletContext == null) {    ServletWebServerFactory factory = getWebServerFactory();    // 主要看这个方法    this.webServer = factory.getWebServer(getSelfInitializer());   }   else if (servletContext != null) {    try {     getSelfInitializer().onStartup(servletContext);    }    catch (ServletException ex) {     throw new ApplicationContextException("Cannot initialize servlet context", ex);    }   }   initPropertySources();  }
  这里首先拿到 TomcatServletWebServerFactory 对象,通过该对象再去创建和启动Tomcat:  public WebServer getWebServer(ServletContextInitializer... initializers) {   if (this.disableMBeanRegistry) {    Registry.disableRegistry();   }   Tomcat tomcat = new Tomcat();   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");   tomcat.setBaseDir(baseDir.getAbsolutePath());   Connector connector = new Connector(this.protocol);   connector.setThrowOnFailure(true);   tomcat.getService().addConnector(connector);   customizeConnector(connector);   tomcat.setConnector(connector);   tomcat.getHost().setAutoDeploy(false);   configureEngine(tomcat.getEngine());   for (Connector additionalConnector : this.additionalTomcatConnectors) {    tomcat.getService().addConnector(additionalConnector);   }   prepareContext(tomcat.getHost(), initializers);   return getTomcatWebServer(tomcat);  }
  上面的每一步都可以对比Tomcat的配置文件,需要注意默认只支持了http协议:   Connector connector = new Connector(this.protocol);   private String protocol = DEFAULT_PROTOCOL;  public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
  如果想要扩展的话则可以对 additionalTomcatConnectors 属性设置值,需要注意这个属性没有对应的setter方法,只有addAdditionalTomcatConnectors 方法,也就是说我们只能通过实现BeanFactoryPostProcessor 接口的postProcessBeanFactory 方法,而不能通过BeanDefinitionRegistryPostProcessor 的postProcessBeanDefinitionRegistry 方法,因为前者可以通过传入的BeanFactory对象提前获取到TomcatServletWebServerFactory 对象调用addAdditionalTomcatConnectors 即可;而后者只能拿到BeanDefinition对象,该对象只能通过setter方法设置值。 事件驱动
  Spring原本就提供了事件机制,而在SpringBoot中又对其进行扩展,通过发布订阅事件在容器的整个生命周期的不同阶段进行不同的操作。我们先来看看SpringBoot启动关闭的过程中默认会发布哪些事件,使用下面的代码即可:  @SpringBootApplication public class SpringEventDemo {      public static void main(String[] args) {         new SpringApplicationBuilder(SpringEventDemo.class)                 .listeners(event -> {                     System.err.println("接收到事件:" + event.getClass().getSimpleName());                 })                 .run()                 .close();     }  }
  这段代码会在控制台打印所有的事件名称,按照顺序如下:  ApplicationStartingEvent :容器启动 ApplicationEnvironmentPreparedEvent :环境准备好 ApplicationContextInitializedEvent :上下文初始化完成 ApplicationPreparedEvent :上下文准备好 ContextRefreshedEvent :上下文刷新完 ServletWebServerInitializedEvent :webServer初始化完成 ApplicationStartedEvent :容器启动完成 ApplicationReadyEvent :容器就绪 ContextClosedEvent :容器关闭
  以上是正常启动关闭,如果发生异常还有发布 ApplicationFailedEvent 事件。事件的发布遍布在整个容器的启动关闭周期中,事件发布对象刚刚我们也看到了是通过SPI加载的SpringApplicationRunListener 实现类EventPublishingRunListener ,同样事件监听器也是在spring.factories 文件中配置的,默认实现了以下监听器: org.springframework.context.ApplicationListener= org.springframework.boot.ClearCachesApplicationListener, org.springframework.boot.builder.ParentContextCloserApplicationListener, org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor, org.springframework.boot.context.FileEncodingApplicationListener, org.springframework.boot.context.config.AnsiOutputApplicationListener, org.springframework.boot.context.config.ConfigFileApplicationListener, org.springframework.boot.context.config.DelegatingApplicationListener, org.springframework.boot.context.logging.ClasspathLoggingApplicationListener, org.springframework.boot.context.logging.LoggingApplicationListener, org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
  可以看到有用于文件编码的( FileEncodingApplicationListener ),有加载日志框架的(LoggingApplicationListener ),还有加载配置的(ConfigFileApplicationListener )等等一系列监听器,SpringBoot也就是通过这系列监听器将必要的配置和组件加载到容器中来,这里不再详细分析,感兴趣的读者可以通过其实现的onApplicationEvent 方法看到每个监听器究竟是监听的哪一个事件,当然事件发布和监听我们自己也是可以扩展的。 自动配置原理
  SpringBoot最核心的还是自动配置,为什么它能做到开箱即用,不再需要我们手动使用 @EnableXXX 等注解来开启?这一切的答案就在@SpringBootApplication 注解中: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),   @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}
  这里重要的注解有三个: @SpringBootConfiguration 、@EnableAutoConfiguration 、@ComponentScan 。@ComponentScan 就不用再说了,@SpringBootConfiguration 等同于@Configuration ,而@EnableAutoConfiguration 就是开启自动配置: @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {  }  @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {  }
  @AutoConfigurationPackage 注解的作用就是将该注解所标记类所在的包作为自动配置的包,简单看看就行,主要看AutoConfigurationImportSelector ,这个就是实现自动配置的核心类,注意这个类是实现的DeferredImportSelector 接口。
  在这个类中有一个 selectImports 方法。这个方法在我之前的文章这一次搞懂Spring事务注解的解析也有分析过,只是实现类不同,它同样会被ConfigurationClassPostProcessor 类调用,先来看这个方法做了些什么:  public String[] selectImports(AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {    return NO_IMPORTS;   }   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader     .loadMetadata(this.beanClassLoader);   // 获取所有的自动配置类   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,     annotationMetadata);   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());  }   protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,    AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {    return EMPTY_ENTRY;   }   AnnotationAttributes attributes = getAttributes(annotationMetadata);   // SPI获取EnableAutoConfiguration为key的所有实现类   List configurations = getCandidateConfigurations(annotationMetadata, attributes);   configurations = removeDuplicates(configurations);   Set exclusions = getExclusions(annotationMetadata, attributes);   checkExcludedClasses(configurations, exclusions);   configurations.removeAll(exclusions);   // 把某些自动配置类过滤掉   configurations = filter(configurations, autoConfigurationMetadata);   fireAutoConfigurationImportEvents(configurations, exclusions);   // 包装成自动配置实体类   return new AutoConfigurationEntry(configurations, exclusions);  }   protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {   // SPI获取EnableAutoConfiguration为key的所有实现类   List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),     getBeanClassLoader());   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "     + "are using a custom packaging, make sure that file is correct.");   return configurations;  }
  追踪源码最终可以看到也是从 META-INF/spring.factories 文件中拿到所有EnableAutoConfiguration 对应的值(在spring-boot-autoconfigure 中)并通过反射实例化,过滤后包装成AutoConfigurationEntry 对象返回。
  看到这里你应该会觉得自动配置的实现就是通过这个 selectImports 方法,但实际上这个方法通常并不会被调用到,而是会调用该类的内部类AutoConfigurationGroup 的process和selectImports方法,前者同样是通过getAutoConfigurationEntry 拿到所有的自动配置类,而后者这是过滤排序并包装后返回。
  下面就来分析 ConfigurationClassPostProcessor 是怎么调用到这里的,直接进入processConfigBeanDefinitions 方法:  public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {   List configCandidates = new ArrayList<>();   String[] candidateNames = registry.getBeanDefinitionNames();    for (String beanName : candidateNames) {    BeanDefinition beanDef = registry.getBeanDefinition(beanName);    if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {     if (logger.isDebugEnabled()) {      logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);     }    }    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {     configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));    }   }    // Return immediately if no @Configuration classes were found   if (configCandidates.isEmpty()) {    return;   }    // Sort by previously determined @Order value, if applicable   configCandidates.sort((bd1, bd2) -> {    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());    return Integer.compare(i1, i2);   });    // Detect any custom bean name generation strategy supplied through the enclosing application context   SingletonBeanRegistry sbr = null;   if (registry instanceof SingletonBeanRegistry) {    sbr = (SingletonBeanRegistry) registry;    if (!this.localBeanNameGeneratorSet) {     BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(       AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);     if (generator != null) {      this.componentScanBeanNameGenerator = generator;      this.importBeanNameGenerator = generator;     }    }   }    if (this.environment == null) {    this.environment = new StandardEnvironment();   }    // Parse each @Configuration class   ConfigurationClassParser parser = new ConfigurationClassParser(     this.metadataReaderFactory, this.problemReporter, this.environment,     this.resourceLoader, this.componentScanBeanNameGenerator, registry);    Set candidates = new LinkedHashSet<>(configCandidates);   Set alreadyParsed = new HashSet<>(configCandidates.size());   do {    parser.parse(candidates);    parser.validate();     Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());    configClasses.removeAll(alreadyParsed);     // Read the model and create bean definitions based on its content    if (this.reader == null) {     this.reader = new ConfigurationClassBeanDefinitionReader(       registry, this.sourceExtractor, this.resourceLoader, this.environment,       this.importBeanNameGenerator, parser.getImportRegistry());    }    this.reader.loadBeanDefinitions(configClasses);    alreadyParsed.addAll(configClasses);     // 省略…  }
  前面一大段主要是拿到合格的 Configuration 配置类,主要逻辑是在ConfigurationClassParser.parse 方法中,该方法完成了对@Component 、@Bean 、@Import 、@ComponentScans 等注解的解析,这里主要看对@Import 的解析,其它的读者可自行分析。一步步追踪,最终会进入到processConfigurationClass 方法:  protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {   if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {    return;   }    ConfigurationClass existingClass = this.configurationClasses.get(configClass);   if (existingClass != null) {    if (configClass.isImported()) {     if (existingClass.isImported()) {      existingClass.mergeImportedBy(configClass);     }     // Otherwise ignore new imported config class; existing non-imported class overrides it.     return;    }    else {     // Explicit bean definition found, probably replacing an import.     // Let"s remove the old one and go with the new one.     this.configurationClasses.remove(configClass);     this.knownSuperclasses.values().removeIf(configClass::equals);    }   }    // Recursively process the configuration class and its superclass hierarchy.   SourceClass sourceClass = asSourceClass(configClass);   do {    sourceClass = doProcessConfigurationClass(configClass, sourceClass);   }   while (sourceClass != null);    this.configurationClasses.put(configClass, configClass);  }
  这里需要注意 this.conditionEvaluator.shouldSkip 方法的调用,这个方法就是进行Bean加载过滤的,即根据@Condition 注解的匹配值判断是否加载该Bean,具体实现稍后分析,继续跟踪主流程doProcessConfigurationClass : protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)   throws IOException {  省略....   // Process any @Import annotations  processImports(configClass, sourceClass, getImports(sourceClass), true);   省略....  return null; }
  这里就是完成对一系列注解的支撑,我省略掉了,主要看 processImports 方法,这个方法就是处理@Import 注解的:  private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,    Collection importCandidates, boolean checkForCircularImports) {    if (importCandidates.isEmpty()) {    return;   }    if (checkForCircularImports && isChainedImportOnStack(configClass)) {    this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));   }   else {    this.importStack.push(configClass);    try {     for (SourceClass candidate : importCandidates) {      if (candidate.isAssignable(ImportSelector.class)) {       // Candidate class is an ImportSelector -> delegate to it to determine imports       Class<?> candidateClass = candidate.loadClass();       ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,         this.environment, this.resourceLoader, this.registry);       if (selector instanceof DeferredImportSelector) {        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);       }       else {        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());        Collection importSourceClasses = asSourceClasses(importClassNames);        processImports(configClass, currentSourceClass, importSourceClasses, false);       }      }      else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {       Class<?> candidateClass = candidate.loadClass();       ImportBeanDefinitionRegistrar registrar =         ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,           this.environment, this.resourceLoader, this.registry);       configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());      }      else {       this.importStack.registerImport(         currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());       processConfigurationClass(candidate.asConfigClass(configClass));      }     }    }   }  }
  刚刚我提醒过 AutoConfigurationImportSelector 是实现DeferredImportSelector 接口的,如果不是该接口的实现类则是直接调用selectImports 方法,反之则是调用DeferredImportSelectorHandler.handle 方法: private List deferredImportSelectors = new ArrayList<>();  public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {  DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(    configClass, importSelector);  if (this.deferredImportSelectors == null) {   DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();   handler.register(holder);   handler.processGroupImports();  }  else {   this.deferredImportSelectors.add(holder);  } }
  首先创建了一个 DeferredImportSelectorHolder 对象,如果是第一次执行则是添加到deferredImportSelectors 属性中,等到ConfigurationClassParser.parse 的最后调用process方法:  public void parse(Set configCandidates) {   省略.....    this.deferredImportSelectorHandler.process();  }   public void process() {   List deferredImports = this.deferredImportSelectors;   this.deferredImportSelectors = null;   try {    if (deferredImports != null) {     DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();     deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);     deferredImports.forEach(handler::register);     handler.processGroupImports();    }   }   finally {    this.deferredImportSelectors = new ArrayList<>();   }  }
  反之则是直接执行,首先通过register拿到 AutoConfigurationGroup 对象:  public void register(DeferredImportSelectorHolder deferredImport) {   Class<? extends Group> group = deferredImport.getImportSelector()     .getImportGroup();   DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(     (group != null ? group : deferredImport),     key -> new DeferredImportSelectorGrouping(createGroup(group)));   grouping.add(deferredImport);   this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),     deferredImport.getConfigurationClass());  }   public Class<? extends Group> getImportGroup() {   return AutoConfigurationGroup.class;  }
  然后在 processGroupImports 方法中进行真正的处理:   public void processGroupImports() {    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {     grouping.getImports().forEach(entry -> {      ConfigurationClass configurationClass = this.configurationClasses.get(        entry.getMetadata());      try {       processImports(configurationClass, asSourceClass(configurationClass),         asSourceClasses(entry.getImportClassName()), false);      }      catch (BeanDefinitionStoreException ex) {       throw ex;      }      catch (Throwable ex) {       throw new BeanDefinitionStoreException(         "Failed to process import candidates for configuration class [" +           configurationClass.getMetadata().getClassName() + "]", ex);      }     });    }   }    public Iterable getImports() {    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {     this.group.process(deferredImport.getConfigurationClass().getMetadata(),       deferredImport.getImportSelector());    }    return this.group.selectImports();   }
  在 getImports 方法中就完成了对process和selectImports 方法的调用,拿到自动配置类后再递归调用调用processImports 方法完成对自动配置类的加载。至此,自动配置的加载过程就分析完了,下面是时序图:
  Condition注解原理
  在自动配置类中有很多Condition相关的注解,以AOP为例:  Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration {   @Configuration(proxyBeanMethods = false)  @ConditionalOnClass(Advice.class)  static class AspectJAutoProxyingConfiguration {    @Configuration(proxyBeanMethods = false)   @EnableAspectJAutoProxy(proxyTargetClass = false)   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",     matchIfMissing = false)   static class JdkDynamicAutoProxyConfiguration {    }    @Configuration(proxyBeanMethods = false)   @EnableAspectJAutoProxy(proxyTargetClass = true)   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",     matchIfMissing = true)   static class CglibAutoProxyConfiguration {    }   }   @Configuration(proxyBeanMethods = false)  @ConditionalOnMissingClass("org.aspectj.weaver.Advice")  @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",    matchIfMissing = true)  static class ClassProxyingConfiguration {    ClassProxyingConfiguration(BeanFactory beanFactory) {    if (beanFactory instanceof BeanDefinitionRegistry) {     BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;     AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);     AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);    }   }   }  }
  这里就能看到 @ConditionalOnProperty 、@ConditionalOnClass 、@ConditionalOnMissingClass ,另外还有@ConditionalOnBean 、@ConditionalOnMissingBean 等等很多条件匹配注解。
  这些注解表示条件匹配才会加载该Bean,以 @ConditionalOnProperty 为例,表明配置文件中符合条件才会加载对应的Bean,prefix表示在配置文件中的前缀,name表示配置的名称,havingValue 表示配置为该值时才匹配,matchIfMissing 则是表示没有该配置是否默认加载对应的Bean。其它注解可类比理解记忆,下面主要来分析该注解的实现原理。
  这里注解点进去看会发现每个注解上都标注了 @Conditional 注解,并且value值都对应一个类,比如OnBeanCondition ,而这些类都实现了Condition 接口,看看其继承体系:
  上面只展示了几个实现类,但实际上Condition的实现类是非常多的,我们还可以自己实现该接口来扩展 @Condition 注解。Condition接口中有一个matches方法,这个方法返回true则表示匹配。该方法在ConfigurationClassParser 中多处都有调用,也就是刚刚我提醒过的shouldSkip方法,具体实现是在ConditionEvaluator 类中:  public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {    return false;   }    if (phase == null) {    if (metadata instanceof AnnotationMetadata &&      ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {     return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);    }    return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);   }    List conditions = new ArrayList<>();   for (String[] conditionClasses : getConditionClasses(metadata)) {    for (String conditionClass : conditionClasses) {     Condition condition = getCondition(conditionClass, this.context.getClassLoader());     conditions.add(condition);    }   }    AnnotationAwareOrderComparator.sort(conditions);    for (Condition condition : conditions) {    ConfigurationPhase requiredPhase = null;    if (condition instanceof ConfigurationCondition) {     requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();    }    if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {     return true;    }   }    return false;  }
  再来看看matches的实现,但 OnBeanCondition 类中没有实现该方法,而是在其父类SpringBootCondition 中: public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  String classOrMethodName = getClassOrMethodName(metadata);  try {   ConditionOutcome outcome = getMatchOutcome(context, metadata);   logOutcome(classOrMethodName, outcome);   recordEvaluation(context, classOrMethodName, outcome);   return outcome.isMatch();  }
  getMatchOutcome 方法也是一个模板方法,具体的匹配逻辑就在这个方法中实现,该方法返回的ConditionOutcome 对象就包含了是否匹配和日志消息两个字段。进入到OnBeanCondition 类中: public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {  ConditionMessage matchMessage = ConditionMessage.empty();  MergedAnnotations annotations = metadata.getAnnotations();  if (annotations.isPresent(ConditionalOnBean.class)) {   Spec spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);   MatchResult matchResult = getMatchingBeans(context, spec);   if (!matchResult.isAllMatched()) {    String reason = createOnBeanNoMatchReason(matchResult);    return ConditionOutcome.noMatch(spec.message().because(reason));   }   matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,     matchResult.getNamesOfAllMatches());  }  if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {   Spec spec = new SingleCandidateSpec(context, metadata, annotations);   MatchResult matchResult = getMatchingBeans(context, spec);   if (!matchResult.isAllMatched()) {    return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());   }   else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),     spec.getStrategy() == SearchStrategy.ALL)) {    return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")      .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));   }   matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,     matchResult.getNamesOfAllMatches());  }  if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {   Spec spec = new Spec<>(context, metadata, annotations,     ConditionalOnMissingBean.class);   MatchResult matchResult = getMatchingBeans(context, spec);   if (matchResult.isAnyMatched()) {    String reason = createOnMissingBeanNoMatchReason(matchResult);    return ConditionOutcome.noMatch(spec.message().because(reason));   }   matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();  }  return ConditionOutcome.match(matchMessage); }
  可以看到该类支持了 @ConditionalOnBean 、@ConditionalOnSingleCandidate 、@ConditionalOnMissingBean 注解,主要的匹配逻辑在getMatchingBeans 方法中: protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {  ClassLoader classLoader = context.getClassLoader();  ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();  boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;  Set> parameterizedContainers = spec.getParameterizedContainers();  if (spec.getStrategy() == SearchStrategy.ANCESTORS) {   BeanFactory parent = beanFactory.getParentBeanFactory();   Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,     "Unable to use SearchStrategy.ANCESTORS");   beanFactory = (ConfigurableListableBeanFactory) parent;  }  MatchResult result = new MatchResult();  Set beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,    spec.getIgnoredTypes(), parameterizedContainers);  for (String type : spec.getTypes()) {   Collection typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,     parameterizedContainers);   typeMatches.removeAll(beansIgnoredByType);   if (typeMatches.isEmpty()) {    result.recordUnmatchedType(type);   }   else {    result.recordMatchedType(type, typeMatches);   }  }  for (String annotation : spec.getAnnotations()) {   Set annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,     considerHierarchy);   annotationMatches.removeAll(beansIgnoredByType);   if (annotationMatches.isEmpty()) {    result.recordUnmatchedAnnotation(annotation);   }   else {    result.recordMatchedAnnotation(annotation, annotationMatches);   }  }  for (String beanName : spec.getNames()) {   if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {    result.recordMatchedName(beanName);   }   else {    result.recordUnmatchedName(beanName);   }  }  return result; }
  这里逻辑看起来比较复杂,但实际上就做了两件事,首先通过 getNamesOfBeansIgnoredByType 方法调用beanFactory.getBeanNamesForType 拿到容器中对应的Bean实例,然后根据返回的结果判断哪些Bean存在,哪些Bean不存在(Condition注解中是可以配置多个值的)并返回MatchResult对象,而MatchResult中只要有一个Bean没有匹配上就返回false,也就决定了当前Bean是否需要实例化。 总结
  本篇分析了SpringBoot核心原理的实现,通过本篇相信读者也将能更加熟练地使用和扩展SpringBoot。
  另外还有一些常用的组件我没有展开分析,如事务、MVC、监听器的自动配置,这些我们有了Spring源码基础的话下来看一下就明白了,这里就不赘述了。
  最后读者可以思考一下我们应该如何自定义starter启动器,相信看完本篇应该难不倒你。

哪些手机能同时登录2个微信号?下面一起来了解下,我觉得肯定有人感兴趣!手机端同时登陆2个微信号的方法需用用到的工具功能安卓手机,应用分身第一步打开手机中的设置应用第二步接着,选择开启应用分身第三步如图,这里选择支付宝微信个人收款码要被追查近4年数据,还要补税?谣言新京报贝壳财经讯(记者程维妙潘亦纯)3月1日个人收款条码使用新规即将实施。日前有网传消息称,自2022年3月1日起,微信支付宝的个人收款码不得用于经营性收款。近4年的数据将被追查,数据库SQL高级用法(一)今天是日更的42365天大家好,我是阿常,今天我和大家分享数据库SQL高级用法的第一个章节。SQLSELECTLIMIT用法SQLLIKE用法SQL通配符用法一SQLSELECTL第七期中国科技如何弯道超车?过去这一两年来,关于中美科技博弈这个话题,大家的情绪似乎就是在这两极之间摆动一会儿被卡脖子很难受一会儿充满自信,我们有美好的未来。但是我们都知道,历史的演化并不会像这样两条腿走路,智能电子测温耳标助力畜牧业发展最近几年随着智能产品的普及,物联网的兴起,农业以及养殖也发生了很多变化。电子耳标,畜牧管理软件,植入式芯片,动物耳标扫码器都是动物管理中的硬件要素。电子耳标适用于猪场,牛场,或散养浙江移动助力医卫系统数字化改革立方数科与三峡电能战略合作,涉及新能源开发和数字化36氪大公司数字创新指南0214作者王耐,真梓编辑石亚琼36氪ToB产业组推出了新板块大公司数字化创新指南,我们会为大家汇总每日各行业大公司数字化创新业务的主要新闻。2月14日大公司数字化创新行业动态日报请查收浙元宇宙新鲜事元宇宙第一股Roblox首份年报未达预期摩根大通成为首家进入元宇宙的银行财联社区块链日报16日讯今日元宇宙新鲜事有元宇宙第一股Roblox首份年报未达预期致盘后股价暴跌15。28YouTube宣布进军区块链和元宇宙摩根大通成为首家进入元宇宙的银行。中国北京冬奥会出现新能源车,日本人开心坏了丰田终于有成功的可能大家好,我是俞天任,朋友们大多都叫我老冰。今天我们圈个有用,来聊聊冬奥会上的新能源车。日本媒体对中国也越来越苛刻了,现在对中国友好,说中国好的日本媒体几乎是没有了。但是在日本还是能8位华人学者当选美国国家工程院院士当地时间2月9日,美国国家工程院宣布了新当选的111位院士与22位外籍院士名单。至此,美国国家工程院院士人数达2388位,外籍院士人数达到310位。其中,8位华人学者当选新院士。他美国神助攻,中国热捧RISCV芯片,ARM的饭突然不美味了纠缠一年多后,迫于监管压力,英伟达无奈放弃收购ARM,对正在使用ARM授权IP的半导体厂商来说,算是松了一口气,但对于现金流窘迫的日本软银集团而言,660亿美元大单飞了,ARM这块刘强东梦想成真!京东物流,在北京冬奥扬眉吐气就在中国的体育事业从无到有,从体育大国迈向体育强国的时候,其他领域同样也在奋起直追。奥运史上第一个中国物流服务商从1996年第26届亚特兰大奥运会开始,奥组委第一次为奥运设立了专门
不给友商活路!索爱签约陈小春助力手机业务,8GB256GB仅售899说到手机,人们的话题总是离不开小米oppovivo这三个品牌。的确它们三家几乎瓜分了国内中低端手机市场份额,而它们这么受欢迎只有一个理由,那就是性价比足够高,没人能逃脱真香定论。然MIUI官方精简版罗永浩不做手机?Win11又翻车马斯克可能被罚小米POCOC40或首发搭载MIUIGo据91mobiles报道,小米POCOC40入门级机型现已通过FCC认证,型号为C3QP。XDA的一份报告显示,POCOC40是一款完全不同会除甲醛的中央空调随着人们生活水平的提高,对于家庭装修,人们除了追求美观,对于舒适度和健康的要求也越来越高!那您听过会除甲醛的中央空调么?带您了解一下日立PE纯净生态模块都有哪些功能特点吧。第一点呢腾讯总裁刘炽平辞任滴滴董事阿里CEO张勇也已卸任雷递网乐天4月17日报道滴滴日前发布公告,披露腾讯总裁刘炽平已辞任滴滴董事,腾讯副法律总顾问梁凤霞(FengxiaLiang)被任命为新任董事。2021年12月,阿里巴巴董事会主席游戏者的福利来啦,游戏电视EvoX55京东首销活动开始啦对游戏电视EvoX55真的眼馋太久了,现在终于在京东开始首销啦,2799元就可以搞到如此高刷加持的游戏电视,真的是游戏爱好者的福音了。现在就付定金,还有超多的惊喜好礼送,不仅可以得ViddaEvoX55游戏电视发布,四重高刷,PS5Xbox绝配现代家庭电视屏幕尺寸越来越大,而且基本上是每家一台电视。有时候家中的大电视不一定拿来看,很多时候都是闲置的。然而,对于游戏发烧友来说,要是能在大屏幕的电视上玩游戏,那真的是一种享受iPhone越来越多的用户关闭了反跟踪功能2021年5月,网络广告业陷入恐慌。在这一年苹果在iOS14。5更新中推出了一个新功能AppTrackingTransparency(我们将其翻译为反跟踪工具)。这是一个默认启用的关于复制MySQL表数据的三个方法什么时候需要复制表数据先列举下几个需要复制MySQL表数据的场景1新加入一个项目组,项目已经开发一段时间了,你想要在本地搞个数据库,方便开发2领导找你要个报表,需要对数据库进行一些iPhone不送充电头又迎来跟随者,全球用户都很熟悉当苹果从iPhone12开始不再附赠充电头之后,业内就普遍预计这一做法会被越来越多手机厂商效仿,果不其然,越来越多手机厂商都不再附赠充电头,不少厂商都是默默跟随,但这其中最尴尬的应苹果送AppleMusic会员!老用户3个月可在PS5上使用要说哪款音乐App最良心?苹果的AppleMusic必须榜上有名!不仅有丰富的曲库与便宜的价格,更能在PS5上使用,一边玩游戏一边听歌,岂不美哉?苹果也是非常大方,又开启了送会员活辐射粉丝将迷你核弹改为电脑!但却玩老滚5TikTok博主FalloutCollector,是一位资深的辐射系列粉丝,他主页中的都和辐射系列有关,近日他将官方在发布FalloutAnthologyBethesda时附赠的塑