SpringBoot加载配置源码

本文深入探究Spring Boot读取配置文件的源码,从启动类打断点开始,逐步深入到各个方法调用,如SpringApplication.run等。详细展示了配置文件加载的流程,包括不同类和方法的调用顺序,还分析了读取配置时key和value对空白字符的处理情况。

从启动类打断点进去:

public class Application{

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

进入 SpringApplication.run(Application.class, args):

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

 进入:run(new Class[]{primarySource}, args):

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

进入run(args):


    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 从这里跟进去
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

进入this.prepareEnvironment(listeners, applicationArguments):


    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        // 从这里跟进去
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

进入listeners.environmentPrepared((ConfigurableEnvironment)environment):


    public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            // 从这里跟进去
            listener.environmentPrepared(environment);
        }

    }

进入listener.environmentPrepared(environment):

    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }

进入this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)):

    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }

进入this.multicastEvent(event, this.resolveDefaultEventType(event));:


    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

进入this.invokeListener(listener, event):

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = this.getErrorHandler();
        if (errorHandler != null) {
            try {
                this.doInvokeListener(listener, event);
            } catch (Throwable var5) {
                errorHandler.handleError(var5);
            }
        } else {
            this.doInvokeListener(listener, event);
        }

    }

进入this.doInvokeListener(listener, event):


    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException var6) {
            String msg = var6.getMessage();
            if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {
                throw var6;
            }

            Log logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Non-matching event type for listener: " + listener, var6);
            }
        }

    }

进入listener.onApplicationEvent(event):

(在org.springframework.boot.context.config.ConfigFileApplicationListener中)

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
        }

        if (event instanceof ApplicationPreparedEvent) {
            this.onApplicationPreparedEvent(event);
        }

    }

进入this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event):


    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        Iterator var3 = postProcessors.iterator();

        while(var3.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }

    }

进入:postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()):

(这里的postProcessors有多个ConfigurableEnvironment的实现类,我们跟入ConfigFileApplicationListener实现类的处理流程)

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        this.addPropertySources(environment, application.getResourceLoader());
    }

进入this.addPropertySources(environment, application.getResourceLoader()):

    protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        (new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
    }

 进入load():

        public void load() {
            this.profiles = new LinkedList();
            this.processedProfiles = new LinkedList();
            this.activatedProfiles = false;
            this.loaded = new LinkedHashMap();
            this.initializeProfiles();

            while(!this.profiles.isEmpty()) {
                ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
                if (profile != null && !profile.isDefaultProfile()) {
                    this.addProfileToEnvironment(profile.getName());
                }

                this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
                this.processedProfiles.add(profile);
            }

            this.resetEnvironmentProfiles(this.processedProfiles);
            this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
            this.addLoadedPropertySources();
        }

进入this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false)):

        private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            this.getSearchLocations().forEach((location) -> {
                boolean isFolder = location.endsWith("/");
                Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
                names.forEach((name) -> {
                    this.load(location, name, profile, filterFactory, consumer);
                });
            });
        }

进入this.load(location, name, profile, filterFactory, consumer):

这里的location目录有四种,我的配置文件放在classpath下:

       private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            if (!StringUtils.hasText(name)) {
                Iterator var6 = this.propertySourceLoaders.iterator();

                while(var6.hasNext()) {
                    PropertySourceLoader loader = (PropertySourceLoader)var6.next();
                    if (this.canLoadFileExtension(loader, location)) {
                        this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
                        return;
                    }
                }
            }

            Set<String> processed = new HashSet();
            Iterator var14 = this.propertySourceLoaders.iterator();

            while(var14.hasNext()) {
                PropertySourceLoader loaderx = (PropertySourceLoader)var14.next();
                String[] var9 = loaderx.getFileExtensions();
                int var10 = var9.length;

                for(int var11 = 0; var11 < var10; ++var11) {
                    String fileExtension = var9[var11];
                    if (processed.add(fileExtension)) {
                        this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
                    }
                }
            }

        }

进入this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer):


        private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            ConfigFileApplicationListener.DocumentFilter defaultFilter = filterFactory.getDocumentFilter((ConfigFileApplicationListener.Profile)null);
            ConfigFileApplicationListener.DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
            if (profile != null) {
                String profileSpecificFile = prefix + "-" + profile + fileExtension;
                this.load(loader, profileSpecificFile, profile, defaultFilter, consumer);
                this.load(loader, profileSpecificFile, profile, profileFilter, consumer);
                Iterator var10 = this.processedProfiles.iterator();

                while(var10.hasNext()) {
                    ConfigFileApplicationListener.Profile processedProfile = (ConfigFileApplicationListener.Profile)var10.next();
                    if (processedProfile != null) {
                        String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                        this.load(loader, previouslyLoaded, profile, profileFilter, consumer);
                    }
                }
            }

            this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
        }

进入this.load(loader, prefix + fileExtension, profile, profileFilter, consumer):

        private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
            try {
                Resource resource = this.resourceLoader.getResource(location);
                StringBuilder descriptionxx;
                if (resource != null && resource.exists()) {
                    if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
                        if (this.logger.isTraceEnabled()) {
                            descriptionxx = this.getDescription("Skipped empty config extension ", location, resource, profile);
                            this.logger.trace(descriptionxx);
                        }

                    } else {
                        String name = "applicationConfig: [" + location + "]";
                        List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
                        if (CollectionUtils.isEmpty(documents)) {
                            if (this.logger.isTraceEnabled()) {
                                StringBuilder description = this.getDescription("Skipped unloaded config ", location, resource, profile);
                                this.logger.trace(description);
                            }

                        } else {
                            List<ConfigFileApplicationListener.Document> loaded = new ArrayList();
                            Iterator var10 = documents.iterator();

                            while(var10.hasNext()) {
                                ConfigFileApplicationListener.Document document = (ConfigFileApplicationListener.Document)var10.next();
                                if (filter.match(document)) {
                                    this.addActiveProfiles(document.getActiveProfiles());
                                    this.addIncludedProfiles(document.getIncludeProfiles());
                                    loaded.add(document);
                                }
                            }

                            Collections.reverse(loaded);
                            if (!loaded.isEmpty()) {
                                loaded.forEach((documentx) -> {
                                    consumer.accept(profile, documentx);
                                });
                                if (this.logger.isDebugEnabled()) {
                                    StringBuilder descriptionx = this.getDescription("Loaded config file ", location, resource, profile);
                                    this.logger.debug(descriptionx);
                                }
                            }

                        }
                    }
                } else {
                    if (this.logger.isTraceEnabled()) {
                        descriptionxx = this.getDescription("Skipped missing config ", location, resource, profile);
                        this.logger.trace(descriptionxx);
                    }

                }
            } catch (Exception var12) {
                throw new IllegalStateException("Failed to load property source from location '" + location + "'", var12);
            }
        }

进入this.loadDocuments(loader, name, resource):

        private List<ConfigFileApplicationListener.Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
            ConfigFileApplicationListener.DocumentsCacheKey cacheKey = new ConfigFileApplicationListener.DocumentsCacheKey(loader, resource);
            List<ConfigFileApplicationListener.Document> documents = (List)this.loadDocumentsCache.get(cacheKey);
            if (documents == null) {
                List<PropertySource<?>> loaded = loader.load(name, resource);
                documents = this.asDocuments(loaded);
                this.loadDocumentsCache.put(cacheKey, documents);
            }

            return documents;
        }

进入loader.load(name, resource):

(我们用的是properties文件,所以进入PropertiesPropertySourceLoader)

    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        Map<String, ?> properties = this.loadProperties(resource);
        return properties.isEmpty() ? Collections.emptyList() : Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
    }

进入this.loadProperties(resource):

    private Map<String, ?> loadProperties(Resource resource) throws IOException {
        String filename = resource.getFilename();
        return (Map)(filename != null && filename.endsWith(".xml") ? PropertiesLoaderUtils.loadProperties(resource) : (new OriginTrackedPropertiesLoader(resource)).load());
    }

进入(new OriginTrackedPropertiesLoader(resource)).load()):

    public Map<String, OriginTrackedValue> load() throws IOException {
        return this.load(true);
    }

进入this.load(true):

(ps:终于进入我们要找的地方了)

    public Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
        OriginTrackedPropertiesLoader.CharacterReader reader = new OriginTrackedPropertiesLoader.CharacterReader(this.resource);
        Throwable var3 = null;

        try {
            Map<String, OriginTrackedValue> result = new LinkedHashMap();
            StringBuilder buffer = new StringBuilder();

            while(reader.read()) {
                String key = this.loadKey(buffer, reader).trim();
                if (expandLists && key.endsWith("[]")) {
                    key = key.substring(0, key.length() - 2);
                    int var19 = 0;

                    while(true) {
                        OriginTrackedValue value = this.loadValue(buffer, reader, true);
                        this.put(result, key + "[" + var19++ + "]", value);
                        if (!reader.isEndOfLine()) {
                            reader.read();
                        }

                        if (reader.isEndOfLine()) {
                            break;
                        }
                    }
                } else {
                    OriginTrackedValue value = this.loadValue(buffer, reader, false);
                    this.put(result, key, value);
                }
            }

            LinkedHashMap var18 = result;
            return var18;
        } catch (Throwable var16) {
            var3 = var16;
            throw var16;
        } finally {
            if (reader != null) {
                if (var3 != null) {
                    try {
                        reader.close();
                    } catch (Throwable var15) {
                        var3.addSuppressed(var15);
                    }
                } else {
                    reader.close();
                }
            }

        }
    }

分别看下loadKey和loadValue方法:

loadKey:

    private String loadKey(StringBuilder buffer, OriginTrackedPropertiesLoader.CharacterReader reader) throws IOException {
        buffer.setLength(0);
        boolean previousWhitespace = false;

        while(!reader.isEndOfLine()) {
            if (reader.isPropertyDelimiter()) {
                reader.read();
                return buffer.toString();
            }
            // 从这里的判断逻辑可以发现,配置文件的key不能被空白字符分隔
            if (!reader.isWhiteSpace() && previousWhitespace) {
                return buffer.toString();
            }

            previousWhitespace = reader.isWhiteSpace();
            buffer.append(reader.getCharacter());
            reader.read();
        }

        return buffer.toString();
    }

loadValue:

    private OriginTrackedValue loadValue(StringBuilder buffer, OriginTrackedPropertiesLoader.CharacterReader reader, boolean splitLists) throws IOException {
        buffer.setLength(0);
        // 分隔符(:和=)后面的空白字符全部跳过
        while(reader.isWhiteSpace() && !reader.isEndOfLine()) {
            reader.read();
        }

        Location location = reader.getLocation();

        while(!reader.isEndOfLine() && (!splitLists || !reader.isListDelimiter())) {
            buffer.append(reader.getCharacter());
            reader.read();
        }

        Origin origin = new TextResourceOrigin(this.resource, location);
        return OriginTrackedValue.of(buffer.toString(), origin);
    }

可以看到,对于key,会去掉前后空格(reader.read()方法,对于开头的空白字符会跳过,所以key左边的空白字符是读出来的时候已经去掉了);对于value,value左边的空白字符会被忽略,但是右边的空白字符会被保留:

配置文件:

 读取到的配置:

 

 

参考文章:

SpringBoot读取配置文件源码探究

<think>我们被要求品读Spring Boot自动配置源码,重点在于理解其核心机制。根据之前的讨论,我们知道`AutoConfigurationImportSelector`是自动配置的核心类,但自动配置源码阅读涉及多个方面。以下是一个系统的源码解读框架: ### Spring Boot自动配置源码核心解读 #### 1. **启动入口:`@SpringBootApplication`** ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration // 开启自动配置 @ComponentScan public @interface SpringBootApplication { // ... } ``` - `@EnableAutoConfiguration`是关键,它通过`@Import`导入了`AutoConfigurationImportSelector`。 #### 2. **核心选择器:`AutoConfigurationImportSelector`** - **继承结构**:实现`DeferredImportSelector`接口,因此其导入的配置类会延迟处理(在所有`@Configuration`类处理之后)。 - **核心方法**: ```java @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 1. 检查是否开启自动配置(默认开启) if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 2. 获取所有自动配置类(带过滤条件) AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } ``` - `getAutoConfigurationEntry()`方法负责加载和过滤配置类。 #### 3. **配置加载:`getAutoConfigurationEntry()`** ```java protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { // 获取@EnableAutoConfiguration的属性(如exclude) AnnotationAttributes attributes = getAttributes(annotationMetadata); // 加载候选配置类(从spring.factories) List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重(避免重复配置) configurations = removeDuplicates(configurations); // 获取需要排除的配置类(通过exclude属性) Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 检查排除的类是否合法(必须是自动配置类) checkExcludedClasses(configurations, exclusions); // 移除排除的类 configurations.removeAll(exclusions); // 应用自动配置过滤器(条件注解过滤) configurations = getConfigurationClassFilter().filter(configurations); // 触发自动配置导入事件 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } ``` #### 4. **条件过滤:`AutoConfigurationImportFilter`** - 通过`spring.factories`中配置的`AutoConfigurationImportFilter`实现类进行过滤。 - 默认使用三个过滤器(位于`spring-boot-autoconfigure`包): 1. `OnClassCondition`:检查类路径是否存在指定类 2. `OnBeanCondition`:检查容器中是否存在指定Bean 3. `OnWebApplicationCondition`:检查是否Web应用 #### 5. **配置类解析:`AutoConfigurationGroup`** - 在`process()`方法中处理配置类,包括排序和分组。 #### 6. **自动配置类示例:`ServletWebServerFactoryAutoConfiguration`** ```java @Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { // ... } ``` - 条件注解控制:只有存在`ServletRequest`且是Servlet Web应用时生效。 - 通过`@Import`导入多个具体配置(Tomcat/Jetty/Undertow)。 ### 关键设计思想 1. **约定优于配置**:通过条件注解按需加载。 2. **扩展性**:通过`spring.factories`机制允许第三方starter贡献配置。 3. **性能优化**:延迟加载、条件过滤减少不必要的配置解析。 §§相关问题§§ 1. Spring Boot 如何确保自动配置类在常规配置类之后加载? 2. 在自动配置过程中,条件注解(如@ConditionalOnClass)的解析发生在哪个阶段? 3. 如何自定义一个 AutoConfigurationImportFilter 实现自定义过滤逻辑? 4. 自动配置类中使用@ConditionalOnMissingBean 注解的作用是什么?请举例说明。 5. Spring Boot 自动配置源码中,哪些地方使用了设计模式?</think>### Spring Boot 自动配置源码深度解析 #### 一、核心入口:`@EnableAutoConfiguration` ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) // 核心入口 public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; } ``` - **关键设计**:通过`@Import`动态加载`AutoConfigurationImportSelector` #### 二、核心引擎:`AutoConfigurationImportSelector` ##### 1. 配置加载机制 ```java // 扫描所有spring.factories文件 protected List<String> getCandidateConfigurations( AnnotationMetadata metadata, AnnotationAttributes attributes ) { return SpringFactoriesLoader.loadFactoryNames( EnableAutoConfiguration.class, getBeanClassLoader() ); } ``` - **扫描路径**:`META-INF/spring.factories` - **配置键**:`org.springframework.boot.autoconfigure.EnableAutoConfiguration` ##### 2. 条件过滤流程 ```java // 执行条件过滤 private List<String> filter(List<String> configurations) { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); return new AutoConfigurationImportFilter() .filter(configurations, autoConfigurationMetadata); } ``` - **核心过滤器**: - `OnClassCondition`:检查类路径是否存在 - `OnBeanCondition`:检查Bean是否存在 - `OnWebApplicationCondition`:检查Web环境 ##### 3. 延迟加载机制 ```java // 实现DeferredImportSelector接口 public class AutoConfigurationImportSelector implements DeferredImportSelector, ... { // 延迟到所有常规@Configuration处理完成后执行 @Override public Class<? extends Group> getImportGroup() { return AutoConfigurationGroup.class; } } ``` #### 三、配置类解析:`AutoConfigurationGroup` ```java private static class AutoConfigurationGroup implements DeferredImportSelector.Group, ... { // 实际处理配置类的方法 @Override public void process(AnnotationMetadata metadata, DeferredImportSelector selector) { // 1. 加载候选配置 // 2. 条件过滤 // 3. 触发配置导入事件 } } ``` #### 四、典型配置类示例:`DataSourceAutoConfiguration` ```java @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class }) public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration { } @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class }) protected static class PooledDataSourceConfiguration { } } ``` - **条件注解实战**: - `@ConditionalOnClass`:检测类路径存在性 - `@ConditionalOnMissingBean`:避免Bean重复定义 - `@EnableConfigurationProperties`:绑定配置属性 #### 五、关键设计模式 1. **工厂模式**: - `SpringFactoriesLoader`加载配置 2. **策略模式**: - `Condition`接口的不同实现 3. **观察者模式**: - `AutoConfigurationImportEvent`事件通知 4. **模板方法模式**: - `AutoConfigurationImportSelector`中的处理流程 #### 六、调试技巧 1. **查看加载配置类**: ```properties # application.properties debug=true ``` 启动时将打印: ``` Positive matches: ----------------- DataSourceAutoConfiguration matched - @ConditionalOnClass found required classes 'javax.sql.DataSource'... ``` 2. **排除特定配置**: ```java @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值