从SpringMVC到Springboot

1、Springboot内嵌容器原理

2、Springboot入口注解

3、Springboot的类扫描

使用spring源码模拟Springboot的内嵌容器

下面的程序也是springMVC源码调试程序。

1、build.gradle

plugins {
    id 'java'
}
group 'org.springframework'
version '5.1.10.BUILD-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}
dependencies {
    compile project(':spring-context')
    compile project(':spring-webmvc')
    testCompile group: 'junit', name: 'junit', version: '4.12'
    provided group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.21'
    compile group: 'com.alibaba', name: 'fastjson', version: '1.2.47'
}

2、启动类和配置类

public class SpringbootStart {
    // 启动类
    public static void main(String[] args) {
        WebApplication.run(SpringbootStart.class);
    }
}
public class WebApplication {
    /**
     * 获取系统的临时目录
     * System.getProperty("java.io.tmpdir")
     */
    public static void run(Class clazz){
        // 初始化spring的环境
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        //      context.setServletContext(servletContext);
        // 添加@EnableWebMvc注解时需要注释掉刷新这里,不然会出现
        // Error creating bean with name 'resourceHandlerMapping' defined in DelegatingWebMvcConfiguration
        // 没有向AnnotationConfigWebApplicationContext中设置 servletContext,解决方法就是在刷新前设置servletContext或者不刷新
        context.refresh();
        // 获取系统的临时目录
        File file = new File(System.getProperty("java.io.tmpdir"));
        // 创建tomcat,将web环境关联到tomcat上
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(9090);
        Context ctx = tomcat.addContext("/", file.getAbsolutePath());
        // 创建注册servlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        Tomcat.addServlet(ctx, "springmvc", servlet).setLoadOnStartup(0);
        ctx.addServletMapping("/","springmvc");
        try {
            // 启动tomcat
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}
@Configuration
@ComponentScan("top.gmfcj")
public class AppConfig {}

@Controller
public class IndexController {

    @RequestMapping("/index")
    @ResponseBody
    public String index(){
        System.out.println("index controller");
        return "index";
    }
}

看一下springboot中对tomcat容器的处理

// TomcatServletWebServerFactory#getWebServer
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    // baseDir
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 连接器
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 配置host的engine
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    
    prepareContext(tomcat.getHost(), initializers);
    // 启动tomcat
    return getTomcatWebServer(tomcat);
}

Springboot入口

SpringBoot的入口类被@SpringBootApplication注解标注,说明这个类就是SpringBoot的主配置类,可以运行这个主配置类的main方法来启动SpringBoot项目。

@Target({ElementType.TYPE})     // 指定注解可以标注的范围
@Retention(RetentionPolicy.RUNTIME) // 注解存活的范围
@Documented // 注解可以被javadoc识别
@Inherited  // 注解到了一个类上是,整个注解会一直继承下去,其他则只起标识作用
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}

@SpringBootConfiguration

@SpringBootConfiguration:表示一个类是SpringBoot的配置类,底层是Spring的注解@Configuration

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

@Configuration标注的类不可以是final类型(无法进行cglib代理)扫描出来会验证@Bean和@Configuration

@EnableAutoConfiguration

@EnableAutoConfiguration:开启自动配置功能

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}

EnableAutoConfiguration注解上有包含了一个@AutoConfigurationPackage注解和导入了AutoConfigurationImportSelector类

@AutoConfigurationPackage

@AutoConfigurationPackage:自动配置包

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {}

Registrar静态类:注册AutoConfigurationPackages类

// 实现了ImportBeanDefinitionRegistrar接口 注册AutoConfigurationPackages.PackageImport类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    // 存储基本包名称
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // getPackageName方法就是去获取@AutoConfigurationPackage注解所在的包名,在这里就是@SpringBootApplication注解类的包名
        // 因为这里的metadata -> @AutoConfigurationPackage
        register(registry, new PackageImport(metadata).getPackageName());
    }
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }
}
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    if (registry.containsBeanDefinition(BEAN)) {
        // bdMap中包含了 org.springframework.boot.autoconfigure.AutoConfigurationPackages
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // 如果已经存在bdMap中了,那么就通过基本包,构建这个 AutoConfigurationPackages bean对象
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 不存在bdMap中,那么注册一个beanName = org.springframework.boot.autoconfigure.AutoConfigurationPackages
        // 的 beanDefinition
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

@EnableConfigurationProperties

@EnableConfigurationProperties实现properties文件自动装配原理

  • 导入了EnableConfigurationPropertiesImportSelector类

  • 注入了两个类,都实现了ImportBeanDefinitionRegistrar接口

  • ConfigurationPropertiesBeanRegistrar读取ConfigurationProperties注解并注册被注解的类

  • ConfigurationPropertiesBindingPostProcessorRegistrar注册两个类

    • ConfigurationPropertiesBindingPostProcessor(BeanPostProcessor)绑定ConfigurationProperties类的属性和配置文件中的值
    • ConfigurationBeanFactoryMetadata(BeanFactoryPostProcessor)缓存所有bean对象的工厂方法
  • 读取注解中的前缀,使用该注解的类的类型。就会对比前缀.属性与properties配置中的key,相同就会注入,最终将组装好的对象交由spring

    @Import(EnableConfigurationPropertiesImportSelector.class)
    public @interface EnableConfigurationProperties {}
    // 向容器中注入两个类
    // ConfigurationPropertiesBeanRegistrar
    // ConfigurationPropertiesBindingPostProcessorRegistrar
    class EnableConfigurationPropertiesImportSelector implements ImportSelector {
    
      private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
              ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
      @Override
      public String[] selectImports(AnnotationMetadata metadata) {
          return IMPORTS;
      }
    }
    

AutoConfigurationImportSelector

@EnableAutoConfiguration注解中导入了AutoConfigurationImportSelector类,AutoConfigurationImportSelector使用了SpringFactoriesLoader.loadFactoryNames方法直接加载类所在路径的META-INF/spring.factories文件

// annotationMetadata 这里就代表 EnableAutoConfiguration 注解上的所有注解信息
public class AutoConfigurationImportSelector implements DeferredImportSelector{
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            // 不开启自动装配
            return NO_IMPORTS;
        }
        // load META-INF/spring-autoconfigure-metadata.properties 文件中的信息
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        // 从 META-INF/spring.factories 文件中获取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
        // 根据 @EnableAutoConfiguration 注解属性,进行筛选
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        // 返回所有的自动装配类
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取注解中的所有属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 从META-INF/spring.factories中获取配置信息 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重
    configurations = removeDuplicates(configurations);
    // 筛选,去除排除的自动装配
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    // 发布事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回包含自动装配集合和不自动装配集合的entry
    return new AutoConfigurationEntry(configurations, exclusions);
}
protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        // 获取配置文件中 key = spring.boot.enableautoconfiguration, 的值
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // META-INF/spring.factories文件中key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
    // getSpringFactoriesLoaderFactoryClass() => EnableAutoConfiguration.class
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
    return configurations;
}
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
    // 拿到META-INF/spring.factories文件中 key=org.springframework.boot.autoconfigure.AutoConfigurationImportListener 的监听器对象集合
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            // 装配不同的环境
            invokeAwareMethods(listener);
            // 发布自动装配事件
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

这个文件中就申明了有哪些自动配置

# Auto Configure            org.springframework.boot.autoconfigure.EnableAutoConfiguration=\     org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

Springboot的包扫描

在springboot中会默认扫描启动类所在包下的所有子包中的注解类,这种情况是由@ComponentScan注解引起的。无论springboot中创建的是哪一种容器,在spring中对ConfigurationClassPostProcessor的回调是不会发生改变的。因此springboot中包扫描和spring的包扫描一致,都是在ConfigurationClassPostProcessor类中进行包的扫描和bd的注册。

直接跳到spring的包扫描的逻辑分析

// ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    if (basePackages.isEmpty()) {
        // 如果没有配置basePacke,则会将当前class的包名作为 basePackage
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    // 正式开始扫描
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

而当前注解了@ComponentScan的类就是启动类,所以会扫描启动类所在包下的所有子包。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容