Springboot自动装配源码及启动原理理解

springboot版本:2.2.2

传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件 等,相较而言,Spring Boot显得更加方便、快捷和高效。那么,Spring Boot究竟如何做到这些的呢? 接下来分别针对Spring Boot框架的依赖管理、自动配置和执行流程进行深入分析

依赖管理

首先,创建一个springboot工程实现web服务,pom.xml文件有两个核心依赖,分别是 spring-boot-starter-parentspring-boot-starter-web

  • spring-boot-starter-parent

    在pom.xml中找到对应的依赖如下

    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.7.5</version>
       <relativePath/> <!-- lookup parent from repository -->
    </parent>

    上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并 将项目版本号统一为2.7.5,该版本号根据实际开发需求是可以修改的

    使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下

    <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-dependencies</artifactId>
     <version>2.7.5</version>
    </parent>

    发现还有爷爷辈,继续查看底层源文件,核心代码如下

    <properties>
     <activemq.version>5.16.5</activemq.version>
     <antlr2.version>2.7.7</antlr2.version>
     <appengine-sdk.version>1.9.98</appengine-sdk.version>
     <artemis.version>2.19.1</artemis.version>
     <aspectj.version>1.9.7</aspectj.version>
     <assertj.version>3.22.0</assertj.version>
     <atomikos.version>4.0.6</atomikos.version>
     <awaitility.version>4.2.0</awaitility.version>
     <build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version>
     <byte-buddy.version>1.12.18</byte-buddy.version>
     <cache2k.version>2.6.1.Final</cache2k.version>
    ...
    </properties>

    可以发现,该文件通过标签对一些常用技术框架的依赖文件 进行了统一版本号管理,说明pom.xml引入依赖文件不需要标注依赖文件版本号

    思考一下,spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的?

  • spring-boot-starter-web

    查看spring-boot-starter-web依赖文件源码,核心代码具体如下

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.7.5</version>
        <scope>compile</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
        <version>2.7.5</version>
        <scope>compile</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.7.5</version>
        <scope>compile</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.23</version>
        <scope>compile</scope>
    </dependency>

    从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层 所有依赖

    正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而 不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由 spring-boot-starter-parent父依赖进行的统一管理。


自动配置(启动流程)

概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需 要少量配置就能运行编写的项目

那Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

自问自答:Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法, @SpringBootApplication能够扫描Spring组件并自动配置Spring Boot;自定义了一个demo,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下:

@SpringBootApplication
public class SpringbootDemoApplication {

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

}

点进去看一下 @SpringBootApplication 有什么

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration                  //表明该类为配置类
@EnableAutoConfiguration                  //启动自动配置功能
@ComponentScan(                           //包扫描器
   excludeFilters = {
   @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),  
   @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})
  }
)
public @interface SpringBootApplication {...}

@SpringBootConfiguration :表示Spring Boot配置类。查看@SpringBootConfiguration注解源 码,核心代码具体如下。

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

从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解 @Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被SpringBoot进行了重新封装命名而已

重点关注一下 @EnableAutoConfiguration:表示开启自动配置功能,该注解是SpringBoot框架最重要的注解,也是实现自动化配置的注解。同样,查看该注解内部查看源码信息,核心代码具体如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage                         //自动配置包
@Import(AutoConfigurationImportSelector.class)    //借助@Import注解来收集所有符合自动配置条件的bean定义,并加载到IoC容器
public @interface EnableAutoConfiguration {...}

可以发现它是一个组合注解,Spring 中有很多以Enable开头的注解,其作用就是借助@Import来 收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfiguration 就是借助@Import 来收集所有符合自动配置条件的bean定义,并加载到IoC容器。下面,对这两个核心注解分别讲解 :

@AutoConfigurationPackage: 会把 @SpringBootApplication 注解标注的类所在的包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

//@Import为Spring框架底层注解,它的作用就是给容器中导入某个组件类,
@Import(AutoConfigurationPackages.Registrar.class) //默认将主配置类(@SpringbootAppication)所在的包及其子包里面所有的组件扫描到容器中;
public @interface AutoConfigurationPackage {...}

从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解实现的,它是 spring框架的底层注解,它的作用就是给容器中导入某个组件类;

@Import(AutoConfigurationPackages.Registrar.class):可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前Springboot创建并使用的IoC容器中。

从上述源码可以看出,在Registrar类中有一个 registerBeanDefinitions() 方法,使用Debug模式启动项目,可以看到选中的部分就是com.yun。也就是说,@AutoConfigurationPackage 注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫 描;

@Import({AutoConfigurationImportSelector.class}):AutoConfigurationImportSelector 这个类导入到spring容器中, AutoConfigurationImportSelector 可以帮助springboot应用将所有符合条件的 @Configuration 配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中;

继续研究 AutoConfigurationImportSelector 这个类,通过源码分析这个类中是通过 selectImports 这 个方法告诉springboot都需要导入那些组件:

进入 loadMetData() 方法

进入 getAutoConfigurationEntry() 在进入 getCandidateConfigurations() 方法

该方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的 名字。

继续点开loadFactory方法

会去读取一个 spring.factories 的文件,读取不到会表这个错误,我们继续根据会看到,最终路径的长 这样,而这个是spring提供的一个工具类

它其实是去加载一个外部的文件,而这文件是在

@EnableAutoConfiguration 就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射(Java Refletion)实例化为对应的标注了 @Configuration 的 JavaConfig形式的配置类,并加载到IOC容器中;

总结
springboot底层实现自动配置的步骤是:
  1. springboot应用启动;

  2. @SpringBootApplication起作用;

  3. @EnableAutoConfiguration;

  4. @AutoConfigurationPackage:这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而 Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理 的容器中;

  5. @Import(AutoConfigurationImportSelector.class):它通过将 AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用 是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找 classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给 SpringFactory加载器进行一系列的容器创建过程

@ComponentScan:具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决 定,在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到Spring Boot项 目主程序启动类所在包的具体位置


自定义starter

  • 新建maven工程 工程名为wsy-spring-boot-starter

    <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-autoconfigure</artifactId>
               <version>2.3.12.RELEASE</version>
           </dependency>
  • 编写javaBean

    @Data
    @EnableConfigurationProperties(SimpleBean.class) //开启@ConfigurationProperties
    @ConfigurationProperties(prefix = "simplebean")  //prefix命名全部为小写
    public class SimpleBean {

       private String id;

       private String name;
    }
  • 编写配置类 MyAutoConfiguration

    @Configuration
    @ConditionalOnClass  //当类路径class下有指定的类的情况,就会进行自动配置
    public class MyAutoConfiguration {

       static {
           System.out.println("MyAutoConfiguration init...");
      }

       @Bean
       public SimpleBean simpleBean(){
           return new SimpleBean();
      }

    }
  • resource下创建/META-INF/spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.yun.config.MyAutoConfiguration

使用starter

  • 在之前的springboot-demo项目的pom.xml中引入自定义starter依赖

    <dependency>
        <groupId>com.yun</groupId>
        <artifactId>wsy-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
  • 在全局配置文件中配置属性值

    simplebean.id=1
    simplebean.name=wsy
  • 编写测试方法

    @Autowired
    private SimpleBean simpleBean;

    @Test
    public void wsyStarterTest(){
      System.out.println(simpleBean);
    }
  • 查询结果

    MyAutoConfiguration init...
    2022-12-01 16:21:54.168 INFO 13996 --- [           main] c.y.s.SpringbootDemoApplicationTests     : Started SpringbootDemoApplicationTests in 4.143 seconds (JVM running for 6.581)
    SimpleBean{id='1', name='wsy'}

执行原理

每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。

那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

带着疑问我们debug走一遍内部源码查看逻辑;

@SpringBootApplication
public class SpringbootDemoApplication {

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

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

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

从上述源码可以看出,SpringApplication.run() 方法内部执行了两个操作,分别是 SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下

  1. SpringApplication实例的初始化创建

    查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //把项目启动类.class设置为属性存储起来
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    //判断当前webApplicationType应用的类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    //设置初始化器(Initializer),最后会调用这些初始化器
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器(Listener)
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    //用于推断并设置项目main()方法启动的主程序启动类
    this.mainApplicationClass = this.deduceMainApplicationClass();
    }

    从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。

    1. this.webApplicationType = WebApplicationType.deduceFromClasspath() 用于判断当前webApplicationType应用的类型。deduceFromClasspath() 方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传 统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

    2. this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)) 用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器 SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中 获取所有可用的应用初始化器类 ApplicationContextInitializer

    3. this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)) 用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样, 也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的 spring.factores文件中获取所有可用的监听器类 ApplicationListener

    4. this.mainApplicationClass = this.deduceMainApplicationClass() 用于推断并设置项目main()方法启动的主程序启动类

  2. 项目的初始化启动

    分析完 (new SpringApplication(primarySources)).run(args) 源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:

    public ConfigurableApplicationContext run(String... args) {
      long startTime = System.nanoTime();
      DefaultBootstrapContext bootstrapContext = createBootstrapContext();
      // 初始化应用上下文和异常报告集合
      ConfigurableApplicationContext context = null;
      // 配置headless属性
      configureHeadlessProperty();
       // 第一步:获取并启动监听器
      SpringApplicationRunListeners listeners = getRunListeners(args);
      listeners.starting(bootstrapContext, this.mainApplicationClass);
      try {
         // 创建 ApplicationArguments 对象 初始化默认应用参数类
         // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问 如 -- server.port=8080
         ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
         // 第二步:根据SpringApplicationRunListeners以及参数来准备环境
         // 创建并配置当前SpringBoot应用将要使用的Environment
         // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
         ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
         configureIgnoreBeanInfo(environment);
         // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
         Banner printedBanner = printBanner(environment);
         // 第三步:创建Spring容器
         context = createApplicationContext();
         context.setApplicationStartup(this.applicationStartup);
         // 第四步:Spring容器前置处理
         // 主要是在容器刷新之前的准备动作,包含一个非常关键的操作;将启动类注入容器,为后续开启自动化配置奠定基础。
         prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
         // 第五步:刷新容器
         refreshContext(context);
         // 第六步:Spring容器后置处理
         afterRefresh(context, applicationArguments);
         Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
         if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
         // 第七步:发出结束执行的事件
         listeners.started(context, timeTakenToStartup);
         // 第八步:执行Runners
         // Runners 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务器启动后执行一次
         // SpringBoot提供了ApplicationRunner和COmmandLineRunner两种服务接口
         callRunners(context, applicationArguments);
      }
      catch (Throwable ex) {
         // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
         handleRunFailure(context, ex, listeners);
         throw new IllegalStateException(ex);
      }
      // 发布应用上下文就绪事件
      // 表示在前面一切初始化启动都没有问题的情况下,使用运行监听器 SpringApplicationRunListener 持续运行配置好的应用上下文 ApplicationContext,
      // 这样整个SpringBoot项目就正式启动完成了
      try {
         Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
         listeners.ready(context, timeTakenToReady);
      }
      catch (Throwable ex) {
         // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
         handleRunFailure(context, ex, null);
         throw new IllegalStateException(ex);
      }
      //返回容器
      return context;
    }

    从上述源码可以看出,项目初始化启动过程大致包括以下部分:

    第一步:获取并启动监听器

    this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

    第二步:根据SpringApplicationRunListeners以及参数来准备环境

    prepareEnvironment(listeners, bootstrapContext, applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境

    第三步:创建Spring容器

    根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置

    第四步:Spring容器前置处理

    这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础

    第五步:刷新容器

    开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭

    第六步:Spring容器后置处理

    扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

    第七步:发出结束执行的事件

    获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启。

    第八步:执行Runners

    用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,SpringBoot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后SpringBoot项目启动后会立即执行这些特定程序。
最后绘制一个Spring Boot执行流程图便于理解

Springboot自动装配源码及启动原理理解的更多相关文章

  1. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  2. SpringBoot自动装配-源码分析

    1. 简介 通过源码探究SpringBoot的自动装配功能. 2. 核心代码 2.1 启动类 我们都知道SpringBoot项目创建好后,会自动生成一个当前模块的启动类.如下: import org. ...

  3. SpringBoot自动装配源码解析

    序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...

  4. SpringBoot自动装配源码

    前几天,面试的时候被问到了SpringBoot的自动装配的原理.趁着五一的假期,就来整理一下这个流程. 我这里使用的是idea创建的最简单的SpringBoot项目. 我们都知道,main方法是jav ...

  5. SpringBoot自动配置源码调试

    之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...

  6. SpringBoot启动代码和自动装配源码分析

    ​ 随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三 ...

  7. springboot自动配置源码解析

    springboot版本:2.1.6.RELEASE SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfig ...

  8. 原创001 | 搭上SpringBoot自动注入源码分析专车

    前言 如果这是你第二次看到师长的文章,说明你在觊觎我的美色!O(∩_∩)O哈哈~ 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 本系列为SpringBoot深度源码专车系列,第一篇发车 ...

  9. JUC回顾之-ConcurrentHashMap源码解读及原理理解

    ConcurrentHashMap结构图如下: ConcurrentHashMap实现类图如下: segment的结构图如下: package concurrentMy.juc_collections ...

  10. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration 2.EnableAuto ...

随机推荐

  1. Windows安装Jenkins详细教程(图文教程)

    一.安装前准备 1.提前安装好jdk,可参考以下链接进行安装 Windows安装JDK详细教程(图文教程) 2.Jenkins官网下载安装包(因为本人jdk安装的是1.8,所以会和最新版jenkins ...

  2. Solutions:如何运用Elastic App Search快速建立出色的React搜索体验

    建立搜索体验是一项艰苦的工作. 乍一看似乎很容易:建立一个搜索栏,将数据放入数据库,然后让用户输入对该数据库的查询. 但是,在数据建模,底层逻辑以及(当然)总体设计和用户体验方面,有很多事情要考虑. ...

  3. Python离线安装Flask

    受限于内网,无法使用pip install Flask直接安装. 以Flask-0.12.2为例 安装Flask需要以下的依赖性,在安装Flask离线版时可以看到依赖性要求. 离线安装文件地址: ht ...

  4. switch分支

    说明: 当表达式的值等于case中的常量,则会执行其中包含的语句块 break用于跳出循环,如果不写,则直接执行下一个常量的语句块,不再去判断表达式的值是否等于下一个case的常量(case穿透) 最 ...

  5. [笔记] 二维FFT

    假设现在有2个矩阵a和b,分别是n行m列和x行y列,现在你要计算它们的二维卷积,也就是求出矩阵s满足: \(s_{i,j}=\sum_{i'\leq i,j'\leq j}a_{i',j'}b_{i- ...

  6. P4588 [TJOI2018]数学计算 (线段树)

    用线段树维护操作序列,叶子结点存要乘的数,非叶子结点存区间乘积,每次输出tr[1] 就是答案. 1 #include<bits/stdc++.h> 2 #define ll long lo ...

  7. 一篇文章带你了解网页框架——Vue简单入门

    一篇文章带你了解网页框架--Vue简单入门 这篇文章将会介绍我们前端入门级别的框架--Vue的简单使用 如果你以后想从事后端程序员,又想要稍微了解前端框架知识,那么这篇文章或许可以给你带来帮助 温馨提 ...

  8. uoj221【NOI2016】循环之美

    前面部分比较简单,就是无脑化式子,简单点讲好了. 首先肯定在\((x,y)=1\)时才考虑这个分数,要求纯循环的话,不妨猜猜结论,就是y必须和K互质.所以答案是\(\sum_{i=1}^n \sum_ ...

  9. Codeforces 1672 E. notepad.exe

    题意 这是一道交互题,有n个字符串,每个字符串长度:0-2000, n :0-2000 有一个机器对他进行排版,你可以给他一个每行的最大宽度w,那么每行只能放长度为w的字符: 每行相邻两个字符串之间至 ...

  10. Mybatis-Plus多表联查

    表格结构: CREATE TABLE `ssmpdemo`.`person_test` ( `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_gener ...