【springboot】5、自动配置

servlet容器要遵循servlet规范。如tomcat、netty

jdbc的接口Driver,在用MySQL的时候,要有Driver的实现类。初始化驱动的时候,class.forName()会加载驱动,mysql的话实现类是com.mysql.Driver。他在工厂中 把接口作为文件名,里面写上实现类,tomcat就会读这个文件,这个是servlet规范

  • java的文件路径是META-INF/services
  • tomcat 的文件路径是META-INF/services/javax.servlet.ServletContainerInitializer。DispatcherServlet是servlet
  • springboot的文件路径是META-INF/spring.factories

一、SPI与SpringFactories

1 java的SPI机制

SPI的全名为Service Provider Interface。大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。

简单的总结下java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Java SPI 规范

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全路径名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个无参构造方法
// 对比下java-spi的源码 // Service.load(接口.class)
public final class ServiceLoader<S> implements Iterable<S>{

    private static final String PREFIX = "META-INF/services/";

2 Spring Boot中的SPI机制:SpringFactories

在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

com.xxx.interface=com.xxx.A,com.xxx.B // 多个实现类可以用,分隔
    // 在Spring Boot的很多包中都能够找到spring.factories文件

这种自定义的SPI机制是Spring Boot Starter实现的基础。

但是SpringFactoriesLoader.java并不是springboot的内容,而是spring的内容

Spring Factories实现原理:

spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:

在Spring Boot中,使用的最多的就是starter。starter可以理解为一个可拔插式的插件,例如,你想使用JDBC插件,那么可以使用spring-boot-starter-jdbc;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb。
初学的同学可能会说:如果我要使用MongoDB,我直接引入驱动jar包就行了,何必要引入starter包?starter和普通jar包的区别在于,它能够实现自动配置,和Spring Boot无缝衔接,从而节省我们大量开发时间。

@EnableAutoConfiguration自动配置:从classpath中搜索所有META-INF/spring.factories配置文件,并将其中org.springframework.boot.aotoconfigure.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的配置类,然后汇总为一个并加载到IOC容器。

配置文件到底能写什么?怎么写?自动配置原理;

配置文件能配置的属性参照https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties

二 自动配置原理

1 main

1、程序从main方法开始运行
2、使用SpringApplication.run()加载主程序类
3、主程序类需要标注@SpringBootApplication

@SpringBootApplication //标注在主程序类上,表明是一个springboot应用
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}

SpringApplication.run(HelloWorldMainApplication.class,args);
//重载为  
SpringApplication.run(new Class<?>[] { HelloWorldMainApplication.class }, args);
//再重载为 
new SpringApplication(HelloWorldMainApplication.class).run(args);
/* 构建 SpringApplication 并运行,创建并且刷新一个新的 ApplicationContext */

2 new SpringApplication

加载spring.factories文件形参一个map

先实例化ApplicationContextInitializer、ApplicationListener的实现类,然后赋值给 SpringApplication属性

// 构造器
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}//重载构造器
public SpringApplication(ResourceLoader resourceLoader,
                         Class<?>... primarySources) {
    
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
     // 判断是否能够成功加载一些关键的类来确认 web 应用类型,这个类型后面会用到
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 关注下面两行
    /* 获取并设置 Spring 上下文初始化器。先调用getSpringFactoriesInstances() 
    拿到工厂instances后赋值给SpringApplication.initializers
    注意这里只是从spring.factories中拿到ApplicationContextInitializer这个接口的kv对
    */
    // 设置初始化器 // set之前先get,get的时候就实例化了
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// List> initializers;
    
    // 设置容器的监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// List> listeners;
    
    // 追述到应用主类,也就是 main 方法所在的类
    this.mainApplicationClass = deduceMainApplicationClass();
}

// 先简单看下set的部分,我们可以拿到就是拿到之后赋值给属性而已,而如何拿到的,我们要看getSpringFactoriesInstances()
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<>(initializers);
}
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
    this.listeners = new ArrayList<>(listeners);
}

2.1 getSpringFactoriesInstances

实例化spring.factories中的对象

我们要实例化其中的对象,首先得加载文件的内容,然后再根据文件的内容调用构造器实例化

这也是SPI思想。

要提醒一下,其实不是在这里进行自动配置的,这是我们在这里提前了解一下这个方法什么意思。

在new SpringApplication构造函数只是用到了ApplicationContextInitializer、ApplicationListener这两个kv对,而自动配置要到后面的run方法里用

// get , 从这就开始要联想java-spi的思想了 // 获取spring.factories里面的实例对象
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//重载
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, //接口 ApplicationContextInitializer、ApplicationListener
                                                      Class<?>[] parameterTypes,
                                                      Object... args) {
    
    ClassLoader classLoader = getClassLoader
        
    // SpringFactoriesLoader用于加载spring.factories文件中的内容,返回指定接口的实现类全类名 // Set保证不重复  
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    
    // 创建上面找到的类实例。 // 方法名字为工厂实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    
    // 构造完了 根据 @Orde r和 @Priority 进行排序
    AnnotationAwareOrderComparator.sort(instances);// list【instances】.sort(new AnnotationAwareOrderComparator(););
    return instances;
}


2.1.1 loadFactoryNames

加载每个jar包路径下的META-INF/spring.factories文件,把里面的内容处理成map的kv对,注意该map是多值map,一个接口可以对应多个实现类

获取传入的类所对应的值,意思是说看看properties有没有以factoryType类名为key的键值对

SpringFactoriesLoader:
// 加载spring工厂,返回要文件中要注册bean的键值对,是一次性加载全部kv对
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    /* 加载工厂配置,根据传入的 factoryClass 获取工厂名称集合 */
    return loadSpringFactories(classLoader).
        getOrDefault(factoryClassName, // key // //getOrDefault()获取我们自定义的值或直接调用默认值
                     Collections.emptyList());// 第二个参数为默认值
}
// SpringFactoriesLoader.java 
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 尝试直接从缓存中拿 // cache的key是类加载器 // 而result是多值map,key是接口,value是List
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) { return result;  }
    try {
        // 加载资源, META-INF/spring.factories // //得到urls //因为每个jar包下都有类路径,我们有多个jar包,所以可能得到一个list的url
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : //  "META-INF/spring.factories";
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        // 保存结果的map
        result = new LinkedMultiValueMap<>();
        //遍历url,因为有多个spring.factories文件
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            //把扫描到的文件的内容包装成成一个properties对象
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            //遍历properties对象里的每个键值对拿到
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // value 用逗号分隔组成集合
                List<String> factoryClassNames = Arrays.asList(
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));// 切割字符串
                // 添加 key 和集合的映射
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        // 结果缓存
        cache.put(classLoader, result);
        return result;
    }catch (IOException ex) { 报错从"META-INF/spring.factories"加载factories失败 }
}

注意,什么new这个里面其实并没有我们要的自动注入的内容,上面的实例化只是实例化ApplicationContextInitializer、ApplicationListener这两个接口的。我们的自动注入是在run里的

【springboot】5、自动配置_第1张图片

2.1.2 createSpringFactoriesInstances
// createSpringFactoriesInstances
private <T> List<T> createSpringFactoriesInstances(Class<T> type, // 接口// 如ApplicationListener.class
                                                   Class<?>[] parameterTypes,
                                                   ClassLoader classLoader, Object[] args, 
                                                   Set<String> names) {// 接口对应的实现类
    List<T> instances = new ArrayList<>(names.size());
    // 遍历 实例化该接口对应的实现类
    for (String name : names) {
        Class<?> instanceClass = ClassUtils.forName(name, classLoader);
        Assert.isAssignable(type, instanceClass);
        // 获取构造器
        Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
        // 用构造器实例化
        T instance = (T) BeanUtils.instantiateClass(constructor, args);
        instances.add(instance);

        // ...如果有异常,报错无法实例化type + " : " + name
    }
    return instances;
}

3 run

我们之前new SpringApplication,里面基本什么都没有

而我们从spring.factories也只是拿出了2个接口的实现类赋值给SpringApplication对象,还有99%的接口实现类还没用

接下来我们需要关注:

  • createApplicationContext();
  • refreshContext(context);
【springboot】5、自动配置_第2张图片
// ApplicationContext.java
public ConfigurableApplicationContext run(String... args) {
    // StopWatch 是一个简单的秒表,允许多个任务的计时,暴露每个命名任务的总运行时间和运行时间
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 获取 SpringApplicationRunListener 集合,同样是从上面加载的配置中获取
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //将main函数传入的args参数构造成ApplicationArguments列表
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 配置应用环境信息
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 根据环境信息配置要忽略的bean信息 ,spring.beaninfo.ignore
        configureIgnoreBeanInfo(environment);
        // 打印 banner,就是我们在控制台看到的那个 Spring 的 logo
        Banner printedBanner = printBanner(environment);
        
        // 根据不同的 webApplicationType 返回不同的应用上下文实例
        // DEFAULT_SERVLET_WEB_CONTEXT_CLASS BeanUtils.instantiateClass(contextClass);。
        // 创建了 AnnatationConfigServletWebServerAC
        context = createApplicationContext();
        
        // 从spring.factories中获取接口的实现类,实例化、赋值属性
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                         new Class[] { ConfigurableApplicationContext.class }, context);
        
        // 完成整个容器的创建与启动以及bean的注入功能
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 刷新容器,完成初始化 // applicationContext.refresh();  // OnRefresh()方法是个钩子方法
        // 通过ServletWebServerApplicationContext(继承AbstractApplicationContext)创建及启动web容器
        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;
}

3.1 createApplicationContext

这里会创建AnnotationConfigServletWebServerApplicationContext类。
而该类继承了ServletWebServerApplicationContext,而这个类是最终集成了AbstractApplicationContext

//创建上下文
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {

        switch(this.webApplicationType) {
            case SERVLET:
                //创建AnnotationConfigServletWebServerApplicationContext
                contextClass = Class.forName("...boot.web.servlet.AnnotationConfigServletWebServerApplicationContext");
                break;
            case REACTIVE:
                contextClass = Class.forName("...boot.web.reactive.AnnotationConfigReactiveWebServerApplicationContext");
                break;
            default:
                contextClass = Class.forName("...annotation.AnnotationConfigApplicationContext");
        }
        // ...switch过程中可能抛异常"无法创建默认ApplicationContext,请指定ApplicationContextClass", var3);
    }
    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

3.2 prepareContext

3.3 refreshContext

context = createApplicationContext();已经创建了AnnotationConfigServletWebServerApplicationContext

然后在onRefresh里createWebServer();

//SpringApplication.java
//刷新上下文
private void refreshContext(ConfigurableApplicationContext context) {//传入new极端的
    this.refresh(context);//进入
    if (this.registerShutdownHook) {
        context.registerShutdownHook();
    }
}

//这里直接调用最终父类AbstractApplicationContext.refresh()方法
protected void refresh(ApplicationContext applicationContext) {
    // 调用new 的ac.refresh();
    ((AbstractApplicationContext)applicationContext).refresh();
}


public abstract class AbstractApplicationContext extends DefaultResourceLoader
    implements ConfigurableApplicationContext {

    @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()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
                // 钩子方法,正常是不做事情的
                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();
            }
        }
    }
3.3.1 invokeBeanFactoryPostProcessors(beanFactory);
3.3.2 registerBeanPostProcessors(beanFactory);
3.3.3 onRefresh();

// 钩子方法,正常是不做事情的

public class ServletWebServerApplicationContext 
    extends GenericWebApplicationContext
    implements ConfigurableWebServerApplicationContext {

    @Override // 在这个方法里看到了熟悉的面孔,this.createWebServer,神秘的面纱就要揭开了。
    protected void onRefresh() {
        super.onRefresh();

        // 
        createWebServer();
    }
3.3.4 finishBeanFactoryInitialization(beanFactory);

三、@SpringBootApplication

【三】的注册时机是【二3.3.1实例化工厂后置处理器】

@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

  • ①SpringBootConfiguration告诉是配置类
  • ②EnableAutoConfiguration开启自动配置
  • ③ComponentScan扫描包

源码

@Target(ElementType.TYPE)//自定义注解的使用地方,TYPE表示是类上的
@Retention(RetentionPolicy.RUNTIME)// 自定义注解会在编码source(编译后丢弃)、class(运行时丢弃)、runtime执行阶段都有效
@Documented // 生成帮助文档,比如属性放上面就会出现注释。
@Inherited // 自定义注解可以被子类继承,该类的子类也有了自定义注解

@SpringBootConfiguration  //配置类
@EnableAutoConfiguration //自动配置
@ComponentScan(excludeFilters = { //注解扫描 //排除 
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
	/*
    public enum FilterType {
        ANNOTATION,//按照注解方式
        ASSIGNABLE_TYPE,//按照指定类型的方式
        ASPECTJ,//使用ASPECTJ表达式的方式
        REGEX,//利用正则表达式进行指定
        CUSTOM//自己实现TypeFilter接口进行自定义规则
	}
    */

public @interface SpringBootApplication {
    //AliasFor这个注解的意思是说这个exclude属性在@SpringBootApplication注解上标注的时候就会传递给@EnableAutoConfiguration注解
    
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};//不想加载的东西

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

1 @SpringBootConfiguration

Spring Boot的配置类:和@Configuration完全一样

源码如下,@SpringBootConfiguration注解继承了@Configuration注解,此外就定义了一个属性proxyBeanMethods而已,

@AliasFor的作用代表属性proxyBeanMethods的值会同步到Configuration的属性proxyBeanMethods上

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

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

而@Configuration就是一个Component会自动注入而已(@Configuration可以保证@Bean单例)

@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;//CGLIB
}
// 在@Configuration注解的类中有两个@Bean,返回一样的对象,那么是单例的。而@Component注释的不是单例的

2 @EnableAutoConfiguration

@EnableXXX都是借助@Import的支持,收集和注册特定场景相关的bean定义

@EnableAutoConfiguration:开启自动配置功能;以前我们需要配置的东西,Spring Boot帮我们自动配置;

/*
@AutoConfigurationPackage原理也是利用@Import给容器中导入组件,用的是上面第三种情况
@AutoConfigurationPackage的作用是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;
*/
@AutoConfigurationPackage//自动配置包(扫描配置类包下的所有组件) ,注册
//这个@Import导入的类是上面第二种情况,里面的方法返回要注册的bean的数组
@Import(AutoConfigurationImportSelector.class)// 该类会注入spring.factories中的
public @interface EnableAutoConfiguration {
    ...
}

2.0 Import使用与原理

  • @Import:给容器中注册组件,他有三种用法,每种都重要
    • @Import(要导入到容器中的组件.class):容器中就会自动注册这个组件
    • @Import(ImportSelector):会自动调用这个类里的一个方法返回将要自动注册的类数组
    • @Import(ImportBeanDefinitionRegistrar):通过AnnotationMetadata手工注册bean到容器中
  • AnnotationMetadata:整个类的信息,包括类上面的注解信息

我们先记住几个概念再开始分析,免得分析不下去

先要记住的概念:

  • @EnableXXX都是利用@Imort给容器中组件
  • xxxxAutoConfiguration:帮我们给容器中自动配置组件;
  • xxxxProperties:配置类来封装配置文件的内容;
@Import的原理

在run方法的createApplicationContext();创建了个AnnotationConfigServletWebServerApplicationContext对象

AnnotationConfigServletWebServerApplicationContext类有两个重要的属性:

  • private final AnnotatedBeanDefinitionReader reader;
  • private final ClassPathBeanDefinitionScanner scanner;

创建AnnotationConfigServletWebServerApplicationContext对象时, 调用构造方法,会初始化该对象的上面两个属性。

public AnnotationConfigServletWebServerApplicationContext() {
    // 传入this工厂 // 重点
    this.reader = new AnnotatedBeanDefinitionReader(this);//里面有个AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry,Environment environment) {

    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    // 其中会对spring内置管理的几个特殊类封装成BeanDefinition,缓存到map中;
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}



public static Set registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {

    。。。;
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        // 注入ConfigurationClassPostProcessor类的BeanDefinition
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    。。。;
    return beanDefs;
}

ConfigurationClassPostProcessor extends BeanDefinitionRegistryPostProcessor;
BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor;// 是个工厂后置处理器
重写了 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法;
重写了 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法;

然后调用run方法的refreshContext(context);时,会实例化各种bean

ConfigurationClassPostProcessor

在实例化ConfigurationClassPostProcessor这个bd注册器时(也是个工厂处理器),调用它的postProcessBeanDefinitionRegistry()方法,所以需要看看这个方法。该方法又调用processConfigBeanDefinitions(registry);然后看下面的调用栈吧

【springboot】5、自动配置_第3张图片

processConfigBeanDefinitions(registry);内部会构建一个ConfigurationClassParser对象 Parse each @Configuration class 通过parser.parse(candidates);定位到processConfigurationClass(ConfigurationClass configClass);再到doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) ;这里会处理

  • @PropertySource
  • @ComponentScan
  • @Import
  • @ImportResource
  • @Bean
  • 及 Process default methods on interfaces;// Process any @Import annotations —> processImports(configClass, sourceClass, getImports(sourceClass), true); 该方法里会有对@Import注解3种方式ImportSelector实现类和ImportBeanDefinitionRegistrar实现类以及未实现这些接口的类的处理;

【springboot】5、自动配置_第4张图片

processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 遍历上面的candidateNames,排除掉作为 处理过的configuration配置类bd。没处理的放到configCandidates中
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 获取已经注册的bean定义名称
    String[] candidateNames = registry.getBeanDefinitionNames();

    // 这里处理的是手动注入的bd,不是注解,可以跳过
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            // 1.1. 如果BeanDefinition 中的configurationClass 属性为full 或者lite ,则意味着已经处理过了,直接跳过
        }
        // 1.2. 判断对应bean是否为配置类,如果是,则加入到configCandidates
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }

    // 2. 对configCandidates 进行 排序,按照@Order 配置的值进行排序
    Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
        @Override
        public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
        }
    });

    // Detect any custom bean name generation strategy supplied through the enclosing application context
    // 3. 如果BeanDefinitionRegistry 是SingletonBeanRegistry 子类的话
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {//DefaultListableBeanFactory是SingletonBeanRegistry
        sbr = (SingletonBeanRegistry) registry;// 强转
        if (!this.localBeanNameGeneratorSet) {
            // 如果localBeanNameGeneratorSet 等于false 
            // 并且SingletonBeanRegistry 中有 id 为internalConfigurationBeanNameGenerator的bean ,
            // 则将componentScanBeanNameGenerator,importBeanNameGenerator 赋值为 该bean.
            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();
    }

    // 4 构建一个ConfigurationClassParser对象,解析每个配置类@Configuration
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    // 实例化2个set,candidates 用于将之前加入的configCandidates 进行去重
    // alreadyParsed 用于判断是否处理过
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    
    // 5. 进行解析
    do {
        // ====关键步骤1
        // 通过他定位到processConfigurationClass(ConfigurationClass configClass)
        // 解析配置类,在此处会解析配置类上的注解(ComponentScan扫描出的类,@Import注册的类,以及@Bean方法定义的类)
        // 注意:这一步只会将加了@Configuration注解以及通过@ComponentScan注解扫描的类才会加入到BeanDefinitionMap中
        // 通过其他注解(例如@Import、@Bean)的方式,在parse()方法这一步并不会将其解析为BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass类
        // 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中实现的
        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
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
        }
        /*===关键步骤2
        将上一步parser解析出的Configuration配置类加载成BeanDefinition
        实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,
        但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,
        或者bean中存在被@Bean注解的方法
        因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法
        */
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        // 这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition
        // 实际上就是看配置类(例如AppConfig类会向BeanDefinitionMap中添加bean)
        // 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length
        // 这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            // 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                        !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    } while (!candidates.isEmpty());

    // 6 如果SingletonBeanRegistry 不包含org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry,则注册一个,bean 为 ImportRegistry. 一般都会进行注册的
    // 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());
    }

    // 7情况缓存
    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();
    }
}
1)parse()

https://blog.csdn.net/qq_26000415/article/details/78917682

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

    实例化deferredImportSelectors
        遍历configCandidates ,进行处理.根据BeanDefinition 的类型 做不同的处理,一般都会调用ConfigurationClassParser#parse 进行解析
        处理ImportSelect;
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();

        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());
        }
    }
    processDeferredImportSelectors();
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
}



2)loadBeanDefinitions

doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)

这里会处理 @PropertySource, @ComponentScan,@Import,@ImportResource,@Bean 及 Process default methods on interfaces;

public void processGroupImports() {
    // 遍历
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        // 
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());

            processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
                           Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
                           exclusionFilter, false);
        });
    }
}

2.1 扫描自己的包@AutoConfigurationPackage自动配置包

在@EnableAutoConfiguration注解上有一个@AutoConfigurationPackage,把扫描的包放入bd,有get方法可以提供给spring-data-jpa用

  • 就有2个自动扫描包的字段
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 继承
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage上有一个@Import(AutoConfigurationPackages.Registrar.class)

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, 
                 new PackageImports(metadata).getPackageNames().toArray(new String[0]));//拿到自动扫描的包(没指定的话默认是配置类的包)
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }
}

只指定主类并没有什么用,我们要扫描主类下所有层次的包,而不是主扫描主类那个一层包

PackageImports(AnnotationMetadata metadata) {// 构造函数
    // 获取AutoConfigurationPackage类的全路径,获取AutoConfigurationPackage注解的属性
    // getAnnotationAttributes是获取注解里的value值
    AnnotationAttributes attributes = AnnotationAttributes
        .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
    List<String> packageNames = new ArrayList<>();
    // 如果指定了basePackages
    for (String basePackage : attributes.getStringArray("basePackages")) {
        packageNames.add(basePackage);
    }
    // 如果指定了basePackageClasses
    for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
        packageNames.add(basePackageClass.getPackage().getName());
    }
    // 如果没有指定,添加配置类的包(从这里看出指定了就不扫描主类了)
    if (packageNames.isEmpty()) {//metadata.getClassName()获取被注解的类
        packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
    }
    // 封装为不可变的集合
    this.packageNames = Collections.unmodifiableList(packageNames);
}

拿到了类下所有层次的包,返回了一个数组[]

// 扫描包下注解的bean,形成bd,放入ioc
public static void register(BeanDefinitionRegistry registry, 
                            String... packageNames) {//可以是数组
    // BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // 将构造函数的第一个参数设置为包名列表
        constructorArguments.addIndexedArgumentValue(0,
                addBasePackages(constructorArguments, packageNames));
    }
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        // 将beanClass设置为BasePackages,他的类型
        beanDefinition.setBeanClass(BasePackages.class);
        // 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 注册beanDefinition
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

上面就创建了一个BasePackages类型的bean定义

2.2 扫描spring的包AutoConfigurationImportSelector自动配置

由来:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

怎么获得我们需要的类数组呢?是从@EnableAutoConfiguration上获取的,AnnotationMetadata这个类就是获取注解信息的类。
https://blog.csdn.net/hancoder/article/details/111413351  ConfigurationClassPostProcessor工厂后置处理器

个人认为进行完了扫描主类包后才能找到这个注解

解释:@Import(AutoConfigurationImportSelector.class)

可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前IOC容器

根据@Import的用法,我们从名字就能推断出来他实现了ImportSelector接口,这个接口的selectImports()方法会返回我们要注册的类数组。

前面自动配置包中扫描了主类所在的包所有类

但是没有扫描到spring的包,这个部分就是去找spring的包下的类

  • 他要返回要注册的bd的类数组
  • 首先获得所有的自动配置类
  • 排除掉重复的
  • 排除在exclusions写的
  • 排除掉没有引入的自动配置类
  • 返回排除剩下的类数组
  • 依次实例化工厂后置处理器,轮询到

@Condition注解是在这里生效的,在filter这个函数中

public class AutoConfigurationImportSelector implements DeferredImportSelector, // ImportSelector
BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    @Override // 给容器中注册组件 //方法栈1
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {//如果没有注册元数据
            return NO_IMPORTS;//返回空{}
        }
        // 找到要注册的组件
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//会返回这个//获取候选的配置
        // 返回要注册的组件
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    //---------AutoConfigurationImportSelector.java---------------
    // selectImports()中调用了此函数 //方法栈2
    /*
    根据相关的AnnotationMetadata返回【自动配置集合AutoConfigurationEntry】
    参数:配置类的注解元数据annotation metadata
    返回值:需要导入的自动配置auto-configurations
    */
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//只获取AutoConfiguration的集合
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 获取@EnableAutoConfiguration的属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // *******
        //从spring.factories中获取EnableAutoConfiguration所对应的所有的值,如AopAutoConfiguration、RedisAutoConfiguration AutoConfigurationImportSelector
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

        // 已经拿到了
        // 去掉重复的值,如类似AopAutoConfiguration、AopAutoConfiguration
        configurations = removeDuplicates(configurations);//return new ArrayList<>(new LinkedHashSet<>(list));
        // 提取exclude和excludeName配置,可以用来排除我们不需要的配置
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // 检查需要移除的配置是否在候选配置列表中,如果有不存在的则抛出异常
        checkExcludedClasses(configurations, exclusions);
        // 从候选配置中移除所有需要排除的配置
        configurations.removeAll(exclusions);
        
        
        
        /* 过滤掉不需要的配置,这里过滤掉@Condition要求的 */
        configurations = getConfigurationClassFilter().filter(configurations);
        /* 将自动配置导入事件通知监听器 */
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    //------------------------
    // 方法栈3 //获取候选的自动配置,即spring.factories里对应的EnableAutoConfiguration
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //从类路径下spring.factories文件中获取 EnableAutoConfiguration所对应的所有的values值
        List<String> configurations = 
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),// 即EnableAutoConfiguration.class;
                                                   getBeanClassLoader());

        Assert.notEmpty(configurations, " META-INF/spring.factories中无自动配置类. ");
        return configurations;//返回所有要注册的小组件,如AopAutoConfiguration...
    }
}
filter

过滤@Condition注解不符合的候选自动配置配置

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : this.filters) {
        // 这里是拿到了所有的自动配置的filter,这个filter也是spring.factories中的
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {// 跟过滤器没有匹配搭配,就丢掉
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) { // 发生改变了,就返回新的
        return configurations;
    }
    // 否则返回原有的,没经过过滤的
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate);
        }
    }
    return result;
}

这个过滤器在spring.factories中写过了

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

四、onRefresh与createWebServer

1 createWebServer

onfresh作为【二3.3.3】中,在容器刷新基本完成时回调

public class ServletWebServerApplicationContext 
    extends GenericWebApplicationContext
    implements ConfigurableWebServerApplicationContext {

    @Override // 在这个方法里看到了熟悉的面孔,this.createWebServer,神秘的面纱就要揭开了。
    protected void onRefresh() {
        super.onRefresh();

        // 
        createWebServer();
    }

createWebServer

createWebServer用于创建webServer

  • 先从spring ServletWebServerApplicationContext容器中获得ServletWebServerFactory类型的bean。按理说只有一个
  • getSelfInitializer()创建
// ServletWebServerApplicationContext
//这里是创建webServer
private void createWebServer() {
    WebServer webServer = this.webServer;//webServer有tomcat、netty、undertow
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {

        // 从工厂里拿ServletWebServerFactory。在refresh方法中创建的,内嵌tomact中的@Bean
        ServletWebServerFactory factory = getWebServerFactory();

        // 根据工厂获取服务器,里面是new Tomcat()然后设置
        // 之前在TomcatServletWebServerFactory设置了Initializer。
        // 把servlet注入到了webServer中
        // 这里【不会调用】dispatchServlet、字符串编码过滤器、请求过滤器 等.onStartup
        this.webServer = factory.getWebServer(getSelfInitializer());

        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                           new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                                           new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        getSelfInitializer().onStartup(servletContext);
    }
    initPropertySources();
}



//接口
public interface ServletWebServerFactory {
    WebServer getWebServer(ServletContextInitializer... initializers);
}

//实现
AbstractServletWebServerFactory
    JettyServletWebServerFactory
    TomcatServletWebServerFactory
    UndertowServletWebServerFactory

1.1 getWebServerFactory();

protected ServletWebServerFactory getWebServerFactory() {
    // 从ac工厂中找ServletWebServerFactory类的bean。ac工厂是我们【二】中new的
    // 在工厂后置处理器的时候肯定已经创建好了
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) { 
        报错启动ServletWebServerApplicationContext时找不到ServletWebServerFactory bean 
    }
    if (beanNames.length > 1) { 
        报错找到了不只一个ServletWebServerFactory beans  :beanNames
    }
    // 返回ac容器中的ServletWebServerFactory
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

1.2 getSelfInitializer()+ServletContextInitializer

getSelfInitializer()用于创建一个ServletContextInitializer对象

这里用到了lambda的知识

::的意思函数方法的引用,而在函数式接口中,如

@FunctionalInterface
public interface ServletContextInitializer {

	/**
	 * Configure the given {@link ServletContext} with any servlets, filters, listeners
	 * context-params and attributes necessary for initialization.
	 * @param servletContext the {@code ServletContext} to initialize
	 */
	void onStartup(ServletContext servletContext) throws ServletException;
}

我们观察到onStartup和下面的selfInitialize方法方法签名是一样的

lambda规定,既然返回值是ServletContextInitializer接口的类型,那么我new一个实现类对应的对象即可

那么先要有实现类,实现方法在哪?不自己写了,别的方法中有现成的了,虽然方法名不同,也可以拿过来直接用

那么getSelfInitializer()的作用就是先用现有方法作为实现类的方法,然后new一个对应的实现类对象

private ServletContextInitializer getSelfInitializer() {
    // 返回ServletContextInitializer接口的实现类
    return this::selfInitialize;
}

// ServletWebServerApplicationContext.onStartup(ACFacade)
private void selfInitialize(ServletContext servletContext) {//传进来的是ACFacade
    prepareWebApplicationContext(servletContext);
    // 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
    registerApplicationScope(servletContext);

    // 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

    // getServletContextInitializerBeans()拿到4个servlet和filter的注册bean。
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        // 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
        // 显示执行disRegistrationBean的onStartup
        beans.onStartup(servletContext);
    }
}

最终的效果类似于

public class MyServletContextInitializer implements ServletContextInitializer{
    @Override
    private void onStartup(ServletContext servletContext) {//传进来的是ACFacade
        prepareWebApplicationContext(servletContext);
        // 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
        registerApplicationScope(servletContext);

        // 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

        // getServletContextInitializerBeans()拿到4个servlet和filter的注册bean。
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            // 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
            // 显示执行disRegistrationBean的onStartup
            beans.onStartup(servletContext);
        }
    }
}

1.3 factory.getWebServer(initializer)

我们在上面

  • 拿到了ServletWebServerFactory,
  • 还拿到了initializer对象

factory不是我们的spring容器,而是TomcatServletWebServerFactory。

从哪里来:从spring容器中可以getBean

spring容器从何而来:ServletWebServerFactoryAutoConfiguration自动配置中,根据EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等配置@Import注册一个。

知道工厂是什么了:那么该工厂就是new 内嵌tomcat

前面已经通过调用onStartup方法得到了servlet,接下来把他们注册到tomcat中

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
    implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    //TomcatServletWebServerFactory
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {

        // 创建tomcat对象
        Tomcat tomcat = new Tomcat();// Server 1级
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        
        // 创建Connector对象
        Connector connector = new Connector(this.protocol);//connector
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);//host 3级 webapp
        configureEngine(tomcat.getEngine());//engine 2级
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        // 把刚才new的ServletContextInitializer对象关联到tomcat
        prepareContext(tomcat.getHost(), initializers);
        
        // 去里面创建tomcatServer
        return getTomcatWebServer(tomcat);
    }

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    }

    private void configureEngine(Engine engine) {
        engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
        for (Valve valve : this.engineValves) {
            engine.getPipeline().addValve(valve);
        }
    }

1.3.1 new tomcat

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    // new TomcatEmbeddedContext
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    context.setName(getContextPath());
    context.setDisplayName(getDisplayName());
    context.setPath(getContextPath());
    File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
    context.setDocBase(docBase.getAbsolutePath());
    context.addLifecycleListener(new FixContextListener());
    context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
                                 : ClassUtils.getDefaultClassLoader());
    resetDefaultLocaleMapping(context);
    addLocaleMappings(context);

    context.setCreateUploadTargets(true);

    configureTldSkipPatterns(context);
    WebappLoader loader = new WebappLoader();
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    loader.setDelegate(true);
    context.setLoader(loader);
    if (isRegisterDefaultServlet()) {
        addDefaultServlet(context);
    }
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);
    }
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    configureContext(context, initializersToUse);
    postProcessContext(context);
}
//Tomcat.java
//返回Engine容器,看到这里,如果熟悉tomcat源码的话,对engine不会感到陌生。
public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {
        return service.getContainer();
    }
    Engine engine = new StandardEngine();
    engine.setName( "Tomcat" );
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}
//Engine是最高级别容器,Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器

getWebServer这个方法创建了Tomcat对象,并且做了两件重要的事情:把Connector对象添加到tomcat中,configureEngine(tomcat.getEngine());
getWebServer方法返回的是TomcatWebServer。

1.3.1 getTomcatWebServer(tomcat);

//TomcatWebServer.java
//这里调用构造函数实例化TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
	this.tomcat = tomcat;
	this.autoStart = autoStart;
	initialize();
}

1.3.2 initialize()

//TomcatWebServer.java
private void initialize() throws WebServerException {

    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            Context context = findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    removeServiceConnectors();
                }
            });

            //===启动tomcat服务===
            this.tomcat.start();

            rethrowDeferredStartupExceptions();

            ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());

            //开启阻塞非守护进程
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

2 tomcat.start

tomcat四大容器:Engine、host、context、wrapper

//Tomcat.start()就是调用
// Tomcat.java
public void start() throws LifecycleException {
    getServer();//new个server。复制给属性tomcat.server,该server里有个service
    server.start();
}

public Server getServer() {
    // 如果已经有了,直接return现有的
    if (server != null) { return server; }
    // 设置环境变量
    System.setProperty("catalina.useNaming", "false");

    // 标准的StandardServer
    server = new StandardServer();

    initBaseDir();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

    server.setPort( -1 );

    
    // 给server里添加一个service
    Service service = new StandardService();
    service.setName("Tomcat");
    server.addService(service);
    return server;
}

3 Server.start

public final class StandardServer
    extends LifecycleMBeanBase // 又继承了LifecycleBase,而start()方法在其中
    implements Server {
@Override // LifecycleBase
public final synchronized void start() throws LifecycleException {

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
               !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        // 回调StandardServer的方法
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

// StandardServer
protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (Service service : services) {
            // 又去调用service.start()
            service.start();
        }
    }

    if (periodicEventDelay > 0) {
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    startPeriodicLifecycleEvent();
                }
            }, 0, 60, TimeUnit.SECONDS);
    }
}

4 service.start

public class StandardService
    extends LifecycleMBeanBase // 又继承了LifecycleBase,而start()方法在其中
    implements Service {
@Override // LifecycleBase
public final synchronized void start() throws LifecycleException {

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
               !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        // 回调StandardService的方法
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

// StandardService
protected void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    // Start our defined Container first
    if (engine != null) {
        synchronized (engine) {
            // 又去调用
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

5 engine.start();

public class StandardEngine
    extends ContainerBase // 不一样了
    implements Engine {

跟上面一样start里又会调用engine重写的startInternal

protected synchronized void startInternal() {

    // Standard container startup
    super.startInternal();
}

ContainerBase.startInternal

public abstract class ContainerBase extends LifecycleMBeanBase
    implements Container {
    
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        // Start our subordinate components, if any
        logger = null;
        getLogger();
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            ((Lifecycle) cluster).start();
        }
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }

        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            // 添加任务到线程池执行
            results.add(startStopExecutor.submit(new StartChild(child)));
        }

        MultiThrowable multiThrowable = null;

        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }

        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                                         multiThrowable.getThrowable());
        }

        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }

        setState(LifecycleState.STARTING);

        // Start our thread
        if (backgroundProcessorDelay > 0) {
            monitorFuture = Container.getService(ContainerBase.this).getServer()
                .getUtilityExecutor().scheduleWithFixedDelay(
                new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
        }
    }

执行的任务

public abstract class ContainerBase extends LifecycleMBeanBase
    implements Container {
    
    private static class StartChild
        implements Callable<Void> {

        private Container child;

        public StartChild(Container child) {
            this.child = child;
        }

        @Override
        public Void call() throws LifecycleException {
            child.start();// 又去找startInternal();
            return null;
        }
    }

然后找到了,StandardContext

6 StandardContext

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {

StandardContext.startInternal()

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
     initializers.entrySet()) {

    
    // 拿到TomcatStarter,去执行他的onStartup
    entry.getKey()// 拿到TomcatStarter,他是个servlet容器
        .onStartup(entry.getValue(),
                   getServletContext());// 获取到的是ACFacade

}

7 TomcatStarter.onStartup(

执行到了TomcatStarter.onStartup(

classes为null

servletContext为ACFacade

initializer3个

@Override // TomcatStarter
public void onStartup(Set<Class<?>> classes,
                      ServletContext servletContext){//ACFacade

    // initializers是1.2中用lambda创建出来的ServletWebServerApplicationContext
    for (ServletContextInitializer initializer : this.initializers) {
        // 重点是这个onStartup怎么执行的,是执行的selfInitialize方法
        // 其实他就是执行我们ac容器的方法 
        initializer.onStartup(servletContext);//传入的servletContext是ACFacade
    }
}

【springboot】5、自动配置_第5张图片

然后去执行ServletWebServerApplicationContext的onStartup

9 ServletWebServerAC.onStartup+selfInitialize

ServletWebServerApplicationContext.onStartup

RegistrationBean是spring扫描进去的

// ServletWebServerApplicationContext.onStartup(ACFacade)
private void selfInitialize(ServletContext servletContext) {//传进来的是ACFacade
    prepareWebApplicationContext(servletContext);
    // 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
    registerApplicationScope(servletContext);

    // 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

    // getServletContextInitializerBeans()拿到4个 servlet和filter 的RegistrationBean
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        // 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
        // 显示执行disRegistrationBean的onStartup,注册servlet到tomcat中
        beans.onStartup(servletContext);
    }
}

如下图4个注册RegistrationBean

【springboot】5、自动配置_第6张图片

在for之前先要执行getServletContextInitializerBeans(),然后

1 getServletContextInitializerBeans

获得的是RegistrationBean

RegistrationBean在spring容器中,在自动配置的时候已经扫描进去了,如dispatcherServletRegistrationBean(我简写为disRegistrationBean)

// ServletWebServerApplicationContext // 用抽象父类接收
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    // 标准工厂里的servlet
    return new ServletContextInitializerBeans(getBeanFactory());
}

// 

public class ServletContextInitializerBeans 
    extends AbstractCollection<ServletContextInitializer> { // tomcat感兴趣的类

    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                          Class<? extends ServletContextInitializer>... initializerTypes) {

        // 多值map,3个key分别为serlvet、filter、listener
        this.initializers = new LinkedMultiValueMap<>();
        // 要从容器中找的类是ServletContextInitializer.class,RegistrationBean实现了它
        this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
            : Collections.singletonList(ServletContextInitializer.class);
        
        // dispatcherServletRegistrationBean
        addServletContextInitializerBeans(beanFactory);
        // 执行完上句后,我们发现initializers从null变为有个`dispatcherServletRegistrationBean`  。key为Servlet,v为dispatcherServletRegistrationBean
        
        addAdaptableBeans(beanFactory);
        // 执行完上句,initializers中又多个了个filter的kv对,k为Filter,v对应有3个FilterRegistrationBean值
        
        List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
            .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
            .collect(Collectors.toList());
        this.sortedList = Collections.unmodifiableList(sortedInitializers);
        logMappings(this.initializers);
    }


    protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
        MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
        // servlet
        addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
        // Filter
        addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
        for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
            // EventListener
            addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                                  new ServletListenerRegistrationBeanAdapter());
        }
    }

执行完后getServletContextInitializerBeans后,initializers这个map中才放入了值。下图是执行完的状态

【springboot】5、自动配置_第7张图片

2 disRegistrationBean.onStartup

// dispatcherServletRegistrationBean // 	其实是调用父类的
// RegistrationBean.JAVA  实现了ServletContextInitializer, Ordered
public final void onStartup(ServletContext servletContext) { // 传入的是ACFacade
    String description = getDescription();
    // 从容器内把响应的bean获取到后,注册到tomcat容器
    register(description, servletContext);
}
@Override // DynamicRegistrationBean.java
protected final void register(String description, ServletContext servletContext) {
    // 添加之后返回一个D,D是Dynamic,
    D registration = addRegistration(description, servletContext);
    // 还需要继续配置,如果addMapping  setLoadOnStartup
    configure(registration);
}

@Override
protected ServletRegistration.Dynamic addRegistration(String description, 
                                                      ServletContext servletContext) {// ApplicaitonContextFacade //里面有个属性是context,TomcatEmbededContext
    String name = getServletName();
    // 往tomcat里添加servlet
    return servletContext.addServlet(name, this.servlet);// 
}

public class ServletRegistrationBean<T extends Servlet> 
    extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
    
    @Override
	protected void configure(ServletRegistration.Dynamic registration) {
		super.configure(registration);
		String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
		registration.addMapping(urlMapping);
		registration.setLoadOnStartup(this.loadOnStartup);
	}
    
    
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic>
    extends RegistrationBean {
    
    protected final void register(String description, ServletContext servletContext) {
		D registration = addRegistration(description, servletContext);
		configure(registration);
	}
    
public abstract class RegistrationBean 
    implements ServletContextInitializer, Ordered { // 实现了ServletContextInitializer,
    
    public final void onStartup(ServletContext servletContext) {//注册servlet到tomcat中
		String description = getDescription();
		register(description, servletContext);// 
	}



// 简陋代码,删去次要代码
public class DispatcherServletRegistrationBean 
    extends ServletRegistrationBean<DispatcherServlet>
		implements DispatcherServletPath {
    public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
		super(servlet);
		this.path = path;
		super.addUrlMappings(getServletUrlMapping());
	}
    



我们自定义的时候就可以new ServletRegistrationBean 。因为我们要注册到tomcat中,但得先在spring配置类中放
    ServletRegistrationBean继承了RegistrationBean,实现了ServletContextInitializer

    DynamicRegistrationBean
    RegistrationBean
    ServletContextInitializer
    Ordered
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    // 构造函数创建
    return new ServletContextInitializerBeans(getBeanFactory());// getBeanFactory()的结果是DefaultListableBeanFactory
}

public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                      Class<? extends ServletContextInitializer>... initializerTypes) {
    // 刚开始只有一个Servlet集合,value为DispatcherServlet
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
        : Collections.singletonList(ServletContextInitializer.class);
    addServletContextInitializerBeans(beanFactory);
    // 遍历容器的bean看哪些与ServletContextInitializer适配,也就是实现了的。原来只有servlet,执行完后增加了filter
    addAdaptableBeans(beanFactory);
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
        .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
        .collect(Collectors.toList());
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    logMappings(this.initializers);
}

【springboot】5、自动配置_第8张图片

@HandlesTypes(WebApplicationInitializer.class)//找到感兴趣的类
public class SpringServletContainerInitializer 
    implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses,
                          ServletContext servletContext) {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        for (Class<?> waiClass : webAppInitializerClasses) {
            //如果是非抽象bean的话就加入
            initializers.add( (WebApplicationInitializer)
                             ReflectionUtils.accessibleConstructor(waiClass).newInstance());
        }

        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);// 调用初始化器的.onStartup
        }
    }

五、注册servlet三大组件

上面我们知道了通过ServletRegistrationBean可以注册组件,那么我们就可以在spring容器中注入这个类型的bean,spring扫描到注册后,启动tomcat时就能发现了。

1 原生Servlet如何使用

加上注解@WebServlet()

然后扫描@ServletComponentScan(basePackages=“com.aa”)。

没有经过spring的拦截器:

我们应该使用ServletRegistrationBean

2 注册RegistrationBean

@Configuration
public class BootConfig {

    @Bean // 单例的,在@Configuration中不加proxyBeanMethod=false
    public ServletRegistrationBean myServlet(){//第一次访问才初始化
        // 会保证new MyServlet 只创建一次
        ServletRegistrationBean srb = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        return srb;
    }

    @Bean
    public FilterRegistrationBean myFilter(){
        FilterRegistrationBean frb = new FilterRegistrationBean();
        frb.setFilter(new MyFilter());
        frb.setUrlPatterns(Arrays.asList("/myServlet", "/myFilter"));
        return frb;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        ServletListenerRegistrationBean slrb = new ServletListenerRegistrationBean(new MyListener());
        return slrb;
    }
}

servlet

public class MyServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        super.init();
        System.out.println("MyServlet被初始化");
    }
    @Override
    protected void doGet(HttpServletRequest req, 
                         HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("发生请求");
    }
    @Override
    protected void doPost(HttpServletRequest req, 
                          HttpServletResponse resp) throws ServletException, IOException {
    }
}

过滤器

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器被初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("发生过滤拦截");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("过滤器被销毁");
    }
}

监听器

public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Servlet容器被初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Servlet容器被销毁");
    }
}

六、内置tomcat的更换

内置tomcat

spring-boot-starter-web有pom依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-tomcatartifactId>
    <version>2.3.1.RELEASEversion>
    <scope>compilescope>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <scope>compilescope>
dependency>

tomcat的自动配置

run----createApplicationContext()—AnnotationConfigServletWebServerApplicationContext

run----

java语言的servlet容器要遵循servlet规范

自动配置里有个ServletWebServerFactoryAutoConfiguration

ServletWebServerFactoryAutoConfiguration

在工厂后置处理器阶段会把自动配置的bd注册进工厂

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class) // CP属性
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
         ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, // 内嵌tomcat // TomcatServletWebServerFactory
         ServletWebServerFactoryConfiguration.EmbeddedJetty.class, // 内嵌netty
         ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) // 内嵌Undertow
public class ServletWebServerFactoryAutoConfiguration { // servlet web工厂
    
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        // 给serverProperties赋值。把顺序文件中的内容拿到设置到map-factory中
        return new ServletWebServerFactoryCustomizer(serverProperties);
        // map.from(this.serverProperties::getPort).to(factory::setPort);
    }

    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
        ServerProperties serverProperties) {
        // 把属性里的tomcat设置拷贝到tomcat中
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
    @ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
    public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
        ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
        FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }

最后剩下两个WebServerFactoryCustomizer类型的bean,任何WebServerFactoryCustomizer类型的bean都可以收到WebServerFactory的回调,所以我们可以实现WebServerFactoryCustomizer来定义WebServerFactoryServletWebServerFactoryCustomizer的作用是将ServerProperties的属性配置设置到ConfigurableServletWebServerFactory的对应属性上,这里主要设置的是一些通用的属性,上面注册的TomcatServletWebServerFactory就实现了ConfigurableServletWebServerFactory。而TomcatServletWebServerFactoryCustomizer则是为TomcatServletWebServerFactory设置了一些它需要的特有属性。

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

    // 内嵌的tomcat。被import
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {

        @Bean // 因为可以启动多个tomcat,所以创建个tomcat工厂
        TomcatServletWebServerFactory tomcatServletWebServerFactory( // 自动传入和tomcat相关的3个Customizers
                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {

            // 里面有个重要的方法getWebServer
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers()
                .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers()
                .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers()
                .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }

排除内置tomcat

发布的时候,目前大多数的做法还是排除内置的tomcat,打瓦包(war)然后部署在生产的tomcat中

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    
    <exclusions>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
        exclusion>
    exclusions>
dependency>

<dependency>
    <groupId>javax.servletgroupId>
    <artifactId>javax.servlet-apiartifactId>
    <version>3.1.0version>
    <scope>providedscope>
dependency>

更新main函数,主要是继承SpringBootServletInitializer,并重写configure()方法。

@SpringBootApplication
public class MySpringbootTomcatStarter extends SpringBootServletInitializer {
    public static void main(String[] args) {
        Long time=System.currentTimeMillis();
        SpringApplication.run(MySpringbootTomcatStarter.class);
        System.out.println("===应用启动耗时:"+(System.currentTimeMillis()-time)+"===");
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}

修改默认配置

@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
    //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties

  • 引入场景依赖

    • https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
  • 查看自动配置了哪些(选做)

    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
  • 是否需要修改

    • 参照文档修改配置项
      • https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
      • 自己分析。xxxxProperties绑定了配置文件的哪些。
    • 自定义加入或者替换组件
      • @Bean、@Component。。。
    • 自定义器 XXXXXCustomizer

七自动配置举例

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;

1 HttpEncoding自动配置

4)、以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

@Configuration(proxyBeanMethods = false) //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(ServerProperties.class)//启动指定类的ConfigurationProperties功能  并把ServerProperties加入到容器中
/*
@EnableConfigurationProperties的作用是@Import(EnableConfigurationPropertiesRegistrar.class)
而EnableConfigurationPropertiesRegistrar的父类是ImportBeanDefinitionRegistrar,
也就是说,这是@Import的第三种用法,自己重写他里面的registerBeanDefinitions()方法,自己去注册bean
//AnnotationMetadata,它可以获取当前类(被@Import标记)的注解信息,它使用标准的反射来获取制定类的内部注解信息。
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerInfrastructureBeans(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		//拿到注解上的ServerProperties.class
		getTypes(metadata).forEach(beanRegistrar::register);
	}
	
	而ServerProperties.class
	@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties { //
把配置里的server的内容赋给了ServerProperties实例的属性
从配置文件中获取指定的值和bean属性进行绑定
同时server后面能跟上面也在这个类里的属性固定着。配置文件能配置什么就可以参照对应的属性类
XXXProperties.class类都是这个作用
*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;    判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class)//判断当前项目有没有这个类CharacterEncodingFilter:SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
//判断配置文件中是否存在某个配置  spring.http.encoding.enabled;
//matchIfMissing如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
    
    // 他已经和springboot的配置文件映射了
    private final Encoding properties;
    
    // 只有一个有参构造器的情况下,参数的值就会从容器中拿,怎么拿到呢?就是@EnableConfigurationProperties的作用,@EnableConfigurationProperties通过ServerProperties把配置文件绑定起来
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();//映射配置文件中的值
	}
    
    @Bean //如果这个类生效了,给容器中添加一个组件 //这个组件中的某些值需要从properties中获取
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());//从properties中获取字符串编码
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}
}

要点:

  • 根据当前不同的条件判断,决定这个配置类是否生效?
  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
@Configuration   //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class)  //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中

@ConditionalOnWebApplication 

@ConditionalOnClass(CharacterEncodingFilter.class)  

@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)  
public class HttpEncodingAutoConfiguration {
  
  	//他已经和SpringBoot的配置文件映射了
  	private final HttpEncodingProperties properties;
  
   //只有一个有参构造器的情况下,参数的值就会从容器中拿
  	public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
		this.properties = properties;
	}
  
    @Bean   //给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

根据当前不同的条件判断,决定这个配置类是否生效?

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

EnableConfigurationProperties

@EnableConfigurationProperties注解的作用是:使 使用 @ConfigurationProperties 注解的类生效。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {

	/*** The bean name of the configuration properties validator. */
	String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

	Class<?>[] value() default {};
}

/*
@EnableConfigurationProperties的作用是@Import(EnableConfigurationPropertiesRegistrar.class)
而EnableConfigurationPropertiesRegistrar的父类是ImportBeanDefinitionRegistrar,
也就是说,这是@Import的第三种用法,自己重写他里面的registerBeanDefinitions()方法,自己去注册bean
//AnnotationMetadata,它可以获取当前类(被@Import标记)的注解信息,它使用标准的反射来获取制定类的内部注解信息。
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerInfrastructureBeans(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		//拿到注解上的ServerProperties.class
		getTypes(metadata).forEach(beanRegistrar::register);
	}
	
	而ServerProperties.class
	@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties { //
把配置里的server的内容赋给了ServerProperties实例的属性
从配置文件中获取指定的值和bean属性进行绑定
同时server后面能跟上面也在这个类里的属性固定着。配置文件能配置什么就可以参照对应的属性类
XXXProperties.class类都是这个作用
*/
EnableConfigurationPropertiesRegistrar
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {//bean定义注册器

    // 关键步骤 // 相当于手动注册bean到容器中
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerInfrastructureBeans(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		getTypes(metadata).forEach(beanRegistrar::register);
	}

	private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
		return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
				.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
				.filter((type) -> void.class != type).collect(Collectors.toSet());
	}

	@SuppressWarnings("deprecation")
	static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
		ConfigurationPropertiesBindingPostProcessor.register(registry);
		BoundConfigurationProperties.register(registry);
		ConfigurationPropertiesBeanDefinitionValidator.register(registry);
		ConfigurationBeanFactoryMetadata.register(registry);
	}
}
ConfigurationPropertiesBeanRegistrar
final class ConfigurationPropertiesBeanRegistrar {

	private final BeanDefinitionRegistry registry;

	private final BeanFactory beanFactory;

	ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
		this.registry = registry;
		this.beanFactory = (BeanFactory) this.registry;
	}

	void register(Class<?> type) {
		MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
				.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
		register(type, annotation);
	}

	void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
		String name = getName(type, annotation);
		if (!containsBeanDefinition(name)) {
			registerBeanDefinition(name, type, annotation);
		}
	}

	private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
		String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
		return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
	}

	private boolean containsBeanDefinition(String name) {
		return containsBeanDefinition(this.beanFactory, name);
	}

	private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
		if (beanFactory instanceof ListableBeanFactory
				&& ((ListableBeanFactory) beanFactory).containsBeanDefinition(name)) {
			return true;
		}
		if (beanFactory instanceof HierarchicalBeanFactory) {
			return containsBeanDefinition(((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(), name);
		}
		return false;
	}

	private void registerBeanDefinition(String beanName, Class<?> type,
			MergedAnnotation<ConfigurationProperties> annotation) {
		Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
				+ " annotation found on  '" + type.getName() + "'.");
		this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
	}

	private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
		if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
			return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
		}
		GenericBeanDefinition definition = new GenericBeanDefinition();
		definition.setBeanClass(type);
		return definition;
	}

}

注意:如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。但是为什么源码里为什么无需使用@Component呢?因为在@EnableAutoConfiguarion里已经把这些类都import了

@ConfigurationProperties@EnableConfigurationProperties 区别:

  • HttpEncodingAutoConfiguration标注的是@EnableConfigurationProperties(.class)
  • @ConfigurationProperties用来将属性赋值到bean属性上
  • @EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。
  • 如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的,当然在@ConfigurationProperties加入注解的类上加@Component也可以使交于springboot管理。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。
  • @EnableConfigurationProperties注解应用到你的@Configuration时, 任何被@ConfigurationProperties注解的beans将自动被Environment属性配置。 这种风格的配置特别适合与SpringApplication的外部YAML配置进行配合使用。

5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(prefix = "spring.http.encoding")  //从配置文件中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {

   public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

2 DispatcherServlet自动配置

注册了2个bean

  • DispatcherServlet
  • DispatcherServletRegistrationBean:通过把他原生的DispatcherServlet注册进web应用

默认支持的webServer

    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器

原理

  • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
  • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
  • ServletWebServerApplicationContext启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)
  • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
  • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
  • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize()—>this.tomcat.start();
  • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

多个servlet都能处理到同一层路径,精确优先原则,所以有原生servlet走的是原生servlet

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    //还要加载到tomcat上下文

    /** The bean name for a DispatcherServlet that will be mapped to the root URL "/" */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /** The bean name for a ServletRegistrationBean for the DispatcherServlet "/"*/
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {//静态类:如果你在这个类里面需要外面类的引用,就不要用static。反之就尽量用static,这样可以提高性能。

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
    }


    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,// 自动注入dispatcherServlet
                                                                               WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            // 包装dispatcherServlet为RegistrationBean
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                                                                                                   webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

    }

我们所知道static是不能用来修饰类的,但是成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.

非静态内部类编译后会默认的保存一个指向外部类的引用,而静态类却没有。

即使没有外部类对象,也可以创建静态内部类对象,而外部类的非static成员必须依赖于对象的调用,静态成员则可以直接使用类调用,不必依赖于外部类的对象,所以静态内部类只能访问静态的外部属性和方法

非静态内部类有一个很大的优点:可以自由使用外部类的所有变量和方法

3 WebMvc自动配置

WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)// 没有其他的mvc配置才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, 
                     TaskExecutionAutoConfiguration.class,
                     ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    public static final String DEFAULT_PREFIX = "";
    public static final String DEFAULT_SUFFIX = "";

    private static final String[] SERVLET_LOCATIONS = { "/" };

    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean(FormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }

    static String[] getResourceLocations(String[] staticLocations) {
        String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
        System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
        System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
        return locations;
    }

    
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)//注入class
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

        private final ResourceProperties resourceProperties;
        private final WebMvcProperties mvcProperties;

        private final ListableBeanFactory beanFactory;

        private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

        private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;

        final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

        public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
                                              WebMvcProperties mvcProperties,
                                              ListableBeanFactory beanFactory, 
                                              ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                                              ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                                              ObjectProvider<DispatcherServletPath> dispatcherServletPath) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
        }

        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            this.messageConvertersProvider
                .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
        }

        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
                Object taskExecutor = this.beanFactory
                    .getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
                if (taskExecutor instanceof AsyncTaskExecutor) {
                    configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor));
                }
            }
            Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
            if (timeout != null) {
                configurer.setDefaultTimeout(timeout.toMillis());
            }
        }

        @Override
        @SuppressWarnings("deprecation")
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
            configurer.setUseRegisteredSuffixPatternMatch(
                this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
            this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
                String servletUrlMapping = dispatcherPath.getServletUrlMapping();
                if (servletUrlMapping.equals("/")) {
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    urlPathHelper.setAlwaysUseFullPath(true);
                    configurer.setUrlPathHelper(urlPathHelper);
                }
            });
        }

        @Override
        @SuppressWarnings("deprecation")
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
            configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
            configurer.favorParameter(contentnegotiation.isFavorParameter());
            if (contentnegotiation.getParameterName() != null) {
                configurer.parameterName(contentnegotiation.getParameterName());
            }
            Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
            mediaTypes.forEach(configurer::mediaType);
        }

        @Bean
        @ConditionalOnMissingBean
        public InternalResourceViewResolver defaultViewResolver() {
            InternalResourceViewResolver resolver = new InternalResourceViewResolver();
            resolver.setPrefix(this.mvcProperties.getView().getPrefix());
            resolver.setSuffix(this.mvcProperties.getView().getSuffix());
            return resolver;
        }

        @Bean
        @ConditionalOnBean(View.class)
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
            return resolver;
        }

        @Bean
        @ConditionalOnBean(ViewResolver.class)
        @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
        public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
            ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
            resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
            // ContentNegotiatingViewResolver uses all the other view resolvers to locate
            // a view so it should have a high precedence
            resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return resolver;
        }

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
        public LocaleResolver localeResolver() {
            if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            }
            AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
            localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
            return localeResolver;
        }

        @Override
        public MessageCodesResolver getMessageCodesResolver() {
            if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
                DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
                resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
                return resolver;
            }
            return null;
        }

        @Override
        public void addFormatters(FormatterRegistry registry) {
            ApplicationConversionService.addBeans(registry, this.beanFactory);
        }

        @Override// 静态资源映射
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {// resourceProperties是和yaml匹配的配置,设置为false就禁用了静态资源映射
                logger.debug("Default resource handling disabled");
                return;
            }
            // 浏览器resourceProperties态资源有效时间
            Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
            CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            if (!registry.hasMappingForPattern("/webjars/**")) {
                customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")//逻辑路径
                                                     .addResourceLocations("classpath:/META-INF/resources/webjars/")//物理路径
                                                     .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));//过期时间控制。浏览器能看到
            }
            // 静态资源映射。默认值是/**
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                                     .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))// 这个值就是那四个
                                                     .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }
        }

        private Integer getSeconds(Duration cachePeriod) {
            return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
        }

        private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
            if (this.resourceHandlerRegistrationCustomizer != null) {
                this.resourceHandlerRegistrationCustomizer.customize(registration);
            }
        }

        @Bean
        @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
        @ConditionalOnMissingFilterBean(RequestContextFilter.class)
        public static RequestContextFilter requestContextFilter() {
            return new OrderedRequestContextFilter();
        }

    }
为什么不能加@EnableWebMvc
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}

// 而WebMvcAutoConfiguration类上有WebMvcConfigurationSupport这个,有了会导致mvc自动配置里的全部失效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {}

所以我们自己写的@EnableWebMvc是和WebMvcAutoConfiguration相矛盾的。所以想使用mvc自动配置,就不要自己写注解@EnableWebMvc

spring容器开始启动(工厂后置处理器时),先扫描主类下的注解,已经得到了主类的@Configuration

这时去执行他里面的@Import,他发现了扫描主类包的那个组件,所以扫描了主类包

@Import也帮我们要自动注入spring的一些主键,spring.factories文件下的实现类,但是会经过过滤器过滤@Condition那些组件。所以这个阶段的时候,我们的@EnableWebMvc优先级高,那就会屏蔽一些自动配置类

mvc自动注入

然后我们理解一下WebMvcAutoConfiguration做了什么

我们没有自己写@EnableWebMvc注解的时候,springboot扫描自动配置扫描到了WebMvcAutoConfiguration,判断spring中没有WebMvcConfigurationSupport类型的bean定义,所以WebMvcAutoConfiguration被正式注册到spring容器中

WebMvcAutoConfiguration是个配置类,所以还会看他里面的@Bean和@Import

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)// 只有mvc自己的配置时,生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
                     ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    
    // 注入WebMvcAutoConfigurationAdapter
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)//注意,他里面实现了WebMvcConfigurationSupport,但是我们的配置类以及生效过了,所以不影响
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)//静态类
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}

我们看看WebMvcAutoConfigurationAdapter是什么,他上面有个import,这个import他会收集容器中的WebMvcConfigurer对象

@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration
    extends DelegatingWebMvcConfiguration// 我们看看这个类
    implements ResourceLoaderAware {}

public class DelegatingWebMvcConfiguration
    extends WebMvcConfigurationSupport {
    
    // 注意属性有s,是集合
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    @Autowired(required = false)// 自动注入 容器中所有的webmvcConfigurer,在后面会让他们共享其中的组件
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);//设置到s那个属性中
        }
    }
}

好了WebMvcConfigurer收集好了,那么WebMvcConfigurer中的方法,比如要注入转换器等内容,是怎么加入的?谁执行他的方法

add在什么时候起作用:

在实例化单实例bean时,开始实例化requestMappingHandlerAdapter,在createBeanInstance这步,还没有进行属性注入的时候,发现构造器参数都还没全呢,或者说需要在构造函数里依赖的对象还没实例化呢

@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
 @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
 @Qualifier("mvcConversionService") FormattingConversionService conversionService,//这个
 @Qualifier("mvcValidator") Validator validator) {
 RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
                                                                           conversionService, validator);
 adapter.setIgnoreDefaultModelOnRedirect(
     this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
 return adapter;
}

比如还缺少依赖mvcConversionService,于是又处理这个组件的@Bean

@Bean
@Override
public FormattingConversionService mvcConversionService() {
 Format format = this.mvcProperties.getFormat();
 WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
                                                                   .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
 addFormatters(conversionService);
 return conversionService;//这个name
}

至于@Bean和getBean(mvcConversionService),的顺序问题,spring肯定处理好了,容器中有@Bean这个组件的信息

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    Format format = this.mvcProperties.getFormat();
    WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
                                                                      .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
    // 执行方法,
    addFormatters(conversionService);
    return conversionService;
}

@Override // DelegatingWebMvcConfiguration类
protected void addFormatters(FormatterRegistry registry) {//每个配置中都添加
    // 他会遍历添加
    this.configurers.addFormatters(registry);
}
静态资源自动配置
@Override// 静态资源映射
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {// resourceProperties是和yaml匹配的配置,设置为false就禁用了静态资源映射
        logger.debug("Default resource handling disabled");
        return;
    }
    // 浏览器resourceProperties态资源有效时间
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")//逻辑路径
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")//物理路径
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));//过期时间控制。浏览器能看到
    }
    // 静态资源映射。默认值是/**
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))// 这个值就是那四个
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
mvc的自动配置类`WebMvcAutoConfiguration`里有

- @Configuration注入`WebMvcAutoConfigurationAdapter`,

  - @Import(EnableWebMvcConfiguration.class),该类继承了DelegatingWebMvcConfiguration

    - DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,维护WebMvcConfigurerComposite configurers 

    - mvc自动配置要求在DispatcherServlet的自动配置之后,而DispatcherServlet的自动配置要求在ServlerWebServerFactory自动配置之后。也就是说,先tomcat,再servlet,再mvc

    - ```java
      @AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
      WebMvcAutoConfiguration
          
      @AutoConfigureAfter(ServlerWebServerFactoryAutoConfiguration.class)他里面会注册内嵌tomcat等
      DispatcherServletAutoConfiguration
- 
  • @Bean了很多默认组件

​```java
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

    private final ResourceProperties resourceProperties;

    private final WebMvcProperties mvcProperties;

    private final ListableBeanFactory beanFactory;

    private final WebMvcRegistrations mvcRegistrations;

    private ResourceLoader resourceLoader;

    public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
                                     ObjectProvider mvcPropertiesProvider,
                                     ObjectProvider mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
        this.resourceProperties = resourceProperties;
        this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
        this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
        this.beanFactory = beanFactory;
    }

    @Bean
    @Override
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcValidator") Validator validator) {
        RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
                                                                                  conversionService, validator);
        adapter.setIgnoreDefaultModelOnRedirect(
            this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
        return adapter;
    }

3 HttpMessageConverters自动配置

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
@Import({ JacksonHttpMessageConvertersConfiguration.class, // Jackson消息转换器
         GsonHttpMessageConvertersConfiguration.class, //Gson
         JsonbHttpMessageConvertersConfiguration.class }) // Jsonb
public class HttpMessageConvertersAutoConfiguration {

    @Bean // 
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(
        ObjectProvider<HttpMessageConverter<?>> converters) { // ObjectProvider帮我们在容器内找到HttpMessageConverter
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(StringHttpMessageConverter.class)
    protected static class StringHttpMessageConverterConfiguration {

        @Bean
        @ConditionalOnMissingBean // 构造注入 json的,加上默认的8个,共10个消息转换器
        public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
            return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
        }

        @Bean
        @ConditionalOnMissingBean
        public StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {
            Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);
            StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());
            converter.setWriteAcceptCharset(false);
            return converter;
        }

    }
// 构造函数
public HttpMessageConverters(boolean addDefaultConverters,
                             Collection<HttpMessageConverter<?>> converters) {
    // 默认转换器如ByteArray、String、Resource、ResourceRegion等8个默认的
    // 再加上spring中找到的json等2个,共10个
    List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
                                                                   addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
    combined = postProcessConverters(combined);
    this.converters = Collections.unmodifiableList(combined);
}

我们知道消息转换器自动注入到spring容器中了,是HttpMessageConverters这个bean管理,那么如何交给mvc?就是这么给WebMvcAutoConfiguration里的configurationMessageConverters(converters)是谁调用的?

父类WebMvcConfigurationSupport里注入了一些bean,如RequestMappingHandlerAdapter。他在注入的时候,

会new RequestMappingHandlerAdapter

然后会调用getMessageConverters()方法注入到适配器里,也就是说这时候就需要以及准备好消息转换器了。也就是说得在工厂后置处理器阶段就处理好

我们在debug过程中发现,发现先调用了适配器的内容,还没找消息转换器bean呢,在创建适配器的时候已经自己找到了10个消息转换器了,他会发现需要消息转换器的依赖,直接去@Bean执行

4 spring-boot-starter-jdbc

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
dependency>   
<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
dependency>

默认Hikari、可用, Springboot将使用它

点开starter-jdbc XML我们可以看到Hikari是默认的数据源

让我们使用yaml方式配置,创建application.yaml

在默认情况下, 数据库连接可以使用DataSource池进行自动配置

默认Hikari可用, Springboot将使用它。

我们可以自己指定数据源配置,通过type来选取使用哪种数据源

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/boot_demo
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
   # type: com.zaxxer.hikari.HikariDataSource
   # type: org.apache.commons.dbcp2.BasicDataSource

配置druid数据源:

修改spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

在application.yaml中加入

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/boot_demo
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

创建数据源注册类

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource dataSource(){
        return new DruidDataSource();
    }
}

jdbcTemplate自动配置

SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `tx_user`;
CREATE TABLE `tx_user` (
    `username` varchar(10) DEFAULT NULL,
    `userId` int(10) NOT NULL,
    `password` varchar(10) DEFAULT NULL,
    PRIMARY KEY (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Controller
public class TestController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @ResponseBody
    @RequestMapping("/query")
    public List<Map<String, Object>> query(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT * FROM tx_user");
        return maps;
    }
}

启动springboot访问
http://localhost:8080/query

Springboot中提供了JdbcTemplateAutoConfiguration的自动配置
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,
JdbcTemplateAutoConfiguration源码:


注意:url后面加上时区

useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC

5 mybatis自动配置

引入mysql-connector-java 和 druid包


   org.mybatis.spring.boot
   mybatis-spring-boot-starter
   1.3.1

步骤:

1)、配置数据源相关属性(见上一节Druid)

2)、给数据库建表

DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `pid` int(20) NOT NULL AUTO_INCREMENT,
  `pname` varchar(50) DEFAULT NULL,
  `addr` varchar(50) DEFAULT NULL,
  `gender` int(2) DEFAULT NULL,
  `birth` date DEFAULT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


INSERT INTO `person` VALUES ('1', '亮哥', '北京', '1', '2020-06-02');
INSERT INTO `person` VALUES ('2', 'zhangsan', 'shanghai', '1', '2022-12-11');

3)、创建JavaBean

public class TxPerson {

    private int pid;
    private String pname;
    private String addr;
    private int gender;

    private Date birth;
@Mapper
public interface TxPersonMapper {

    @Select("select * from tx_person")
    public List<TxPerson> getPersons();

    @Select("select * from tx_person t where t.pid = #{id}")
    public TxPerson getPersonById(int id);

    @Options(useGeneratedKeys =true, keyProperty = "pid")
    @Insert("insert into tx_person(pid, pname, addr,gender, birth)" +
            " values(#{pid}, #{pname}, #{addr},#{gender}, #{birth})")
    public void insert(TxPerson person);

    @Delete("delete from tx_person where pid = #{id}")
    public void update(int id);
}

Mapper上可以不加@Mapper注解,通过@MapperScan扫描器来扫描mapper包下。不要两个注解都用,那就在容器中注册多个了。

@SpringBootApplication
@MapperScan("cn.tx.sboot.mapper")
public class FirstSpringApplication {
    public static void main(String[] args) {
        SpringApplication.run(FirstSpringApplication.class, args);
    }
}

解决驼峰模式和数据库中下划线不能映射的问题:数据库是下划线,javaBean中是大写

@Configuration
public class MybatisConfig {

    @Bean // 解决驼峰模式和数据库中下划线不能映射的问题。
    public ConfigurationCustomizer getCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}
实际上在application.yml中直接配置即可,不用单独写bean

查询俄国

TxPerson{pid=1, pname='张三', pAddr='北京', gender=1, birth=Thu Jun 14 00:00:00 CST 2018}
boot整合mybatis配置文件



<mapper namespace="cn.tx.sboot.mapper.PersonMapper">
    <select id="selectById" resultType="Person">
        select * from person t where t.pid = #{pid}
    select>
    <select id="selectAll" resultType="Person">
        select * from person
    select>
    <insert id="insert" parameterType="person">
        <selectKey keyProperty="pid" resultType="int" order="BEFORE">
            select  last_insert_id()
        selectKey>
        insert into person(pid, pname, addr, gender, birth)values(#{pid},#{pname},#{addr},#{gender},#{birth} )
    insert>
    <delete id="delete" parameterType="int" >
        delete  from person  where pid = #{pid}
    delete>
mapper>

在application.yaml中配置mybatis的信息

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/txjava?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  type-aliases-package: cn.tx.sboot.model  # java类的别名
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mapper/*.xml
MybatisAutoConfiguration
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

    private final MybatisProperties properties;

    private final Interceptor[] interceptors;

    private final ResourceLoader resourceLoader;

    private final DatabaseIdProvider databaseIdProvider;

    private final List<ConfigurationCustomizer> configurationCustomizers;

    @PostConstruct // 检查xml配置文件是否存在,为了加载内容
    public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && 	
            StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(), "Cannot find config location: " + resource
                         + " (please add config file or check your Mybatis configuration)");
        }
    }

如果没有配置mapperScan的话,会扫描主启动类所在包下所有的@Mapper

案例

@Configuration
@MapperScan("cn.tx.springboot.mapper")
public class TxMvcConfig implements WebMvcConfigurer{

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toLogin").setViewName("login");
        registry.addViewController("/header").setViewName("header");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/menu").setViewName("menu");
        registry.addViewController("/add").setViewName("add");
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        List<String> excludePatterns = new ArrayList<String>();
        excludePatterns.add("/css/**");
        excludePatterns.add("/images/**");
        excludePatterns.add("/toLogin");
        excludePatterns.add("/login");
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(excludePatterns);
    }
}
@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource dataSource(){
        return new DruidDataSource();
    }
}
@Configuration
public class MybatisConfig {

    @Bean
    public ConfigurationCustomizer getCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

你可能感兴趣的:(Spring)