Spring Boot 源码分析(一)

Spring Boot 源码分析(一)

sschrodinger

2019/05/28


Spring boot 简介


Spring boot 采用约定大于配置的思想对应用程序进行默认配置,减少了大量的配置时间。

Spring Boot 包含的特性如下:

  • 创建可以独立运行的 Spring 应用
  • 直接嵌入 Tomcat 或 Jetty 服务器,不需要部署 WAR 文件
  • 提供推荐的基础 POM 文件来简化 Apache Maven 配置
  • 尽可能的根据项目依赖来自动配置 Spring 框架
  • 提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查
  • 没有代码生成,也没有 XML 配置文件

通过 Spring Boot,创建新的 Spring 应用变得非常容易,而且创建出的 Spring 应用符合通用的最佳实践。只需要简单的几个步骤就可以创建出一个 Web 应用。对于 spring boot 的创建,参见 spring 官方启动器


Spring boot 加载


因为 Spring boot 内置了 Tomcat 或 Jetty 服务器,不需要直接部署 War 文件,所以 spring boot 的程序起点为一个普通的主函数,主函数类如下:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

}

看似和普通的程序没有什么区别,其实他最主要的步骤都通过注解 @SpringBootApplication 和方法 SpringApplication.run() 完成了。

其实所有的依赖在这一步就可以完成注入,主要的步骤是 ==spring 读取所有依赖中 META-INF/spring.factories 文件,该文件指明了哪些依赖可以被自动加载,然后根据 ImportSelector 类选择加载哪些依赖,使用 ConditionOn 系列注解排除掉不需要的配置文件,最后将剩余的配置文件所代表的 bean 加载到 IoC 容器中==。

META-INF/spring.factories 文件的读取

spring 所有的配置文件都分布在 jar 包的 META-INF/spring.factories 中,如下:

image.png

每一个 META-INF/spring.factories 为一个键值对列表,截取了部分 spring-boot jar 包下的 spring.factories 文件,如下:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

在程序中,这段列表会被解析为 Map<K, List<V>> 的格式,即一个 MultiValueMap(一个键对应多个值的 map),键和每一个值都是一个类的权限定名

主函数中,只有一个方法,即 SpringApplication.run(),如下:

@SpringBootApplication
public class DemoApplication {

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

}

我们来看一个读取 META-INF/spring.factories 的例子。

在程序初始化时,即运行 new SpringApplication() 时,就会读取一次配置文件,我们来分析是如何读取配置文件的。

SpringApplication.run(DemoApplication.class, args) 方法是 SpringApplication 类提供的一个静态方法,调用的是 run(new Class<?>[] { primarySource }, args) 方法。如下:

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

其中,读取配置文件的操作就发生在 SpringApplication 类的实例化过程中。实例化的代码如下:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 把初始化的初始化器存到数组中
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    // 把初始化的监听器加入到数组中
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 获得主类
    this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances(ApplicationContextInitializer.class)) 中 spring 读取了spring.factories 文件。


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 直接利用反射创建实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

这里一定要记住 loadFactoryNames((Class<?> factoryClass, @Nullable ClassLoader classLoader) 这个函数,该函数是 SpringFactoriesLoader 类提供的一个静态方法,用于读取 factoryClass 的类全限定名这个键在 spring-factoies 中的值列表。

loadSpringFactories 读取所有的配置文件中的属性的键值对,并把缓存到一个 cache 中,在需要读取的时候进行读取。

在运行完 loadSpringFactories(classLoader) 执行语句后,在如上的 spring.factories 实例下,cache 可能变成如下形式:

cache : 
    key = classloader.toString()
    ->
    value = {
        "org.springframework.boot.env.PropertySourceLoader" 
            -> ["org.springframework.boot.env.PropertiesPropertySourceLoader", "
org.springframework.boot.env.YamlPropertySourceLoader"],
        "org.springframework.boot.SpringApplicationRunListener" 
            -> ["org.springframework.boot.context.event.EventPublishingRunListener"],
        "org.springframework.boot.SpringBootExceptionReporter" 
            -> ["org.springframework.boot.diagnostics.FailureAnalyzers"],
        "org.springframework.context.ApplicationContextInitializer" 
            -> ["org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer", "org.springframework.boot.context.ContextIdApplicationContextInitializer", "org.springframework.boot.context.config.DelegatingApplicationContextInitializer", "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"],
        
    }
    // 以属性文件的 key 作为键,value 作为值,注意这个值用一个链表表示,因为可能一个键对应多个值

具体加载和读取配置文件如下所示:

解析配置文,如下:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    } try {
        Enumeration<URL> urls = (classLoader != null ?
            // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"    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 factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    
    // output
    // like:
    // org.springframework.boot.autoconfigure.EnableAutoConfiguration -> {LinkedList@1446} size = 118
    //   |
    //   |- org.springframework.boot.autoconfigure.EnableAutoConfiguration
        
}

以指定的 factoryClass 的类全限定名为键,尝试从缓存中取出值,对于 ApplicationContextInitializer.class 这个 factoryClass 来说,会取出所有键为 org.springframework.context.ApplicationContextInitializer 的值,对于以上的 spring.factories,就是取出了 `

"org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
"org.springframework.boot.context.ContextIdApplicationContextInitializer"
"org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
"org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"

总结

  • ==SpringFactoriesLoader 类的静态方法实现了依赖文件的读取,读取所有的配置类的全限定名名称==
  • ==整个 SpringApplication 在初始化时完成了最基本配置的读取和实例化==
  • ==我们需要记住这个时候的 cache,这个 cache 在上下文加载中非常有用==

文件上下文的加载

上一节展示了如何将配置文件所在的类加载到内存中,那么 spring boot 的自动配置如何办到?

如,在 spring mvc 中,我们需要手工配置如视图解析器等基本配置,需要如下的代码配置:

@Bean
public ViewResolver viewResolver() {
    return new ThymeleafViewResolver();
}

既然 spring boot 可以自动配置,那么在 spring boot 中,则一定有类似的语句加载默认的视图解析器等,我们来看看怎么加载的。

注解简介

首先介绍几个注解

@SpringBootApplication

@SpringBootApplication 是一个复合注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 注解一
@EnableAutoConfiguration // 注解二
// 没有配置 basepackages,默认代表当前包及其子包
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM,
                classes = AutoConfigurationExcludeFilter.class) }) // 注解三
public @interface SpringBootApplication {
    // ...
}

注解一代表配置注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

有了这个注解,我们就可以把由 @SpringBootApplication 注解修饰的类作为配置类来使用,即在类 DemoApplication 中由 @Bean 注解修饰的代码都会被注入到 IoC 容器中由 Spring 统一管理。

注解三会自动扫描该类所在包,将各种组件注入到 IoC 容器中让其管理,所以我们也可以类 DemoApplication 上增加诸如 @Controller 的注解,将其作为一个 Controller。

@EnableAutoConfiguration

我们注意到 SpringBootApplication 注解同时由 @EnableAutoConfiguration 注解修饰,我们继续看 @EnableAutoConfiguration

@EnableAutoConfiguration 的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage  // 注解一
@Import(AutoConfigurationImportSelector.class) // 注解二
public @interface EnableAutoConfiguration {

    // ...

}

该注解的核心是注解二,用于导入自动配置类。

note

  • 在 Spring 中,由 Enable 开头的注解的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的 bean 定义

@Import

@Import 注解可以导入三种类:

  1. @Configuration 注解修饰的类
  2. ImportSelectorImportBeanDefinitionRegistrar 的实现类
  3. 普通的组件类,即由 @Component 注解修饰的类。

note

  • 在 spring boot 中,自动配置的加载使用的是 AutoConfigurationImportSelector 这个类。

ImportSelector

ImportSelector 接口定义如下:

public interface ImportSelector {
    // 返回哪些类需要被创建
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

该接口的主要作用是根据给定的选择条件(通常是一个或多个注解属性)导入哪个 @Configuration 类。

note

  • 如果该接口的子类实现了如下四个接口,会先执行如下四个接口的函数:
  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware
  • 如果希望在所有的 @Configuration 类都导入后再导入该类,则使用其子接口 DeferredImportSelector

filter

在 spring 中,有过滤器注解,如下:

@ConditionalOnBean  // 在所有的 Bean 都加载好之后才能够加载
@ConditionalOnMissingBean // 在没有该 bean 的时候才能够加载
@ConditionalOnSingleCandidate // 仅当指定类的Bean已包含在BeanFactory中并且可以确定单个候选项时才匹配的条件

@ConditionalOnClass // 所有的 class 都加载之后才能够加载
@ConditionalOnMissingClass // 不存在 class 的时候才能够加载

@ConditionalOnWebApplication // 在 web 环境下才能够加载
@ConditionalOnNotWebApplication // 不在 web 环境下才能够加载

这些注解使用 AutoConfigurationImportFilter 接口处理,排除掉不需要的对象。在 spring 的实现中,分别是 OnBeanConditionOnClassConditionOnWebApplicationCondition

加载过程

介绍完这些注解,我们回到加载过程。

SpringApplication 的实例方法 run 方法,在 run 方法中,会创建一个新的上下文实例 context,并调用 contextrefresh 方法,refresh 方法中,会自动配置我们需要的默认上下文

下面是我们非常熟悉的容器初始化的步骤:

@Override
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();
       }
       // ...
   }

   
}

invokeBeanFactoryPostProcessors 会解析 @Import 注解,并根据 @Import 的属性进行下一步操作。

invokeBeanFactoryPostProcessors 的主要操作如下:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // ...
}

下面是 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 的方法代码,部分代码如下:

public static void invokeBeanFactoryPostProcessors(
            ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

    // Invoke BeanDefinitionRegistryPostProcessors first, if any.
    Set<String> processedBeans = new HashSet<>();

    if (beanFactory instanceof BeanDefinitionRegistry) {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
        List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

        // 记录是否是定义类的 Processor 或者普通的 Processor

        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let the bean factory post-processors apply to them!
        // Separate between BeanDefinitionRegistryPostProcessors that implement
        // PriorityOrdered, Ordered, and the rest.
        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

        // ...
        // 应用 Bean 定义类的后置处理器
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        // ...

}

private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

invokeBeanDefinitionRegistryPostProcessors 函数对每一个定义类的后置处理器分别进行应用, @Configure 的解析就在这个函数中。如下:

// 从注册表中的配置类派生更多的bean定义
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // ...
    this.registriesPostProcessed.add(registryId);
    // Build and validate a configuration model based on the registry of Configuration classes.
    processConfigBeanDefinitions(registry);
}

进入最关键的类 ConfigurationClassPostProcessor,这个类用户来注册所有的 @Configure@Bean。他的 processConfigBeanDefinitions 函数如下:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();

    // 记录所有候选的未加载的配置

    // Return immediately if no @Configuration classes were found
    if (configCandidates.isEmpty()) {
        return;
    }

    // 按照 Ordered 对配置进行排序

    // 加载自定义 bean 名命策略

    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<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        // 解译候选集
        parser.parse(candidates);
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        // ...
    } while (!candidates.isEmpty());

    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
        // for a shared cache since it'll be cleared by the ApplicationContext.
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}

在解释候选集 parser.parse(candidates) 中,会调用 sourceClass = doProcessConfigurationClass(configClass, sourceClass) 方法依次解析注解,得到所有的候选集。

note

  • doProcessConfigurationClass 顺次解析 @PropertySource@ComponentScan@Import@ImportResource@Bean父类

注意 process 中的 this.deferredImportSelectorHandler.process() 方法:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }

    this.deferredImportSelectorHandler.process();
}

解析完成之后,会找到所有以 @PropertySource@ComponentScan@Import@ImportResource@Bean 注解的类及其对象,如果有 DeferredImportSelector,会将其加入到 deferredImportSelectorHandler 中,并调用 this.deferredImportSelectorHandler.process() 对这些 DeferredImportSelector 进行处理。

AutoConfigurationImportSelector

实际上,在 spring boot 中,容器初始化的时候,主要就是对 AutoConfigurationImportSelector 进行处理。

Spring 会将 AutoConfigurationImportSelector 封装成一个 AutoConfigurationGroup,用于处理。最终会调用 AutoConfigurationGroupprocess 方法。

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            // 主要通过该函数找到所有需要自动配置的类
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }
        
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    return configurations;
}

如上,我们可以看到 process 最终调用了我们非常熟悉的函数 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());,该方法以 EnableAutoConfiguration 类为键(org.springframework.boot.autoconfigure.EnableAutoConfiguration),取得所有的值。

在该函数中,还会调用 configurations = filter(configurations, autoConfigurationMetadata) 方法,将不需要的候选集全部排除。(该方法内部使用 AutoConfigurationImportFilter 的实现类排除)。

我们看一个常见的 configuration,即 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,这个类中有大量的 @Bean 注解的方法,用来产生 bean,如下:

@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
    adapter.setIgnoreDefaultModelOnRedirect(
            this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
    return adapter;
}

==spring 通过读取所有所需要的 AutoConfiguration,就可以加载默认的上下文容器,实现自动注入。==

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容