springboot原理(一)

Spring Boot特点

  • springboot 是一个快速整合第三方框架 (maven子父项目依赖)
  • 简化XML配置(完全采用注解化)
  • 内置Http服务器(Jetty、undertow,Tomcat)
  • 最终以java应用程序进行执行(当然也可以打成war包)

完全采用注解化

  • 传统的WEB-INF/web.xml
  • @EnableWebMvc (Spring mvc) -- 用java代码操作spingmvc初始化过程

内置Http服务器

SPI机制

来源
  • jdk 为了方便应用程序进行扩展,提供了默认的 SPI (Service Provider Interface) 实现(ServiceLoader)
  • SPI机制主要针对厂商或者插件,有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要
约定

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

DEMO
// 来源于网络
// 定义搜索接口(会有不同实现方式)
package my.xyz.spi;  
import java.util.List;  
public interface Search {  
   public List serch(String keyword);  
}   
/* 
* A公司实现 
在jar包中META-INF/services/my.xyz.spi.Search文件中写下如下内容:
com.A.spi.impl.FileSearch
* B公司实现 
在jar包中META-INF/services/my.xyz.spi.Search文件中写下如下内容:
  com.B.spi.impl.DatabaseSearch
*/


package com.xyz.factory;  
import java.util.Iterator;  
import java.util.ServiceLoader;  
import my.xyz.spi.Search;  
public class SearchFactory {  
    private SearchFactory() {  
    }  
    public static Search newSearch() {  
        Search search = null;  
        ServiceLoader<Search> serviceLoader = ServiceLoader.load(Search.class);  
        Iterator<Search> searchs = serviceLoader.iterator();  
        if (searchs.hasNext()) {  
            search = searchs.next();  
        }  
        return search;  
    }  
}  

package my.xyz.test;  
import java.util.Iterator;  
import java.util.ServiceLoader;  
import com.xyz.factory.SearchFactory;  
import my.xyz.spi.Search;  
public class SearchTest {  
    public static void main(String[] args) {  
        Search search = SearchFactory.newSearch();  
        search.search("java spi test");  
    }  
} 

Spring SPI
  • spring 也为我们提供了spi实现 SpringFactoriesLoader , 允许开发人员通过

    META-INF/spring.factories 文件进行扩展

  • spring boot 正是利用这个扩展点,在 spring-framework 的基础上为我们集成了常用的开源框架

  • spring spi 内置tomcat
  • java 代码创建tomcat实例
  • tomcat 加载.class ..

内置tomcat

  • 其实就是一个jar包 tomcat-embed-core.jar (springboot2.0依赖tomcat8.5+)

  • 然后用java代码创建tomcat实例

  • 可以用tomcat加载servlet

    new Tomcat - > tomcatServer
    setPort();
    new StandardContext
    StandardContext.setPath
    //监听上下文
    StandardContext.addLifecycleListener(new FixContextListener())
    //把context添加到server-host
    tomcatServer.getHost.addChild(StandardContext);    
    tomcatServer.addServlet(new XXXServlet());
    tomcatServer.addServletMapping
    tomcatServer.start();
    tomcatServer.await();
    
    
  • 用tomcat加载springmvc注释启动方式 ,就是springboot启动的大致原理了

    • 实现 AbstractAnnotationConfigDispatcherServletInitializer初始化dispatcherservlet

    • 实现以下方法

      • 加载spring 根配置信息

        // RootConfig  componentScan("packages")
        class[] getRootConfigClasses()
        
      • 加载springmvc config

        // 用java代码方式配置springmvc  实现WebMvcConfigurerAdapter 
        // @componentScan("packages"),@enableWebMvc,@Config
        class[] getServletConfigClasses()
        
      • url mapping,拦截所有请求

        String [] getServletMappings(new String[]{"/"})
        
    • 用tomcat加载target/classes下面的class

Spring Boot启动原理

@Target({ElementType.TYPE})  //注解的适用范围,其中TYPE用于描述类,接口(包括包注解类型)活enum声明
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期,保留到class文件中(三个生命周期)
@Documented  //  表明这个注解应该被javadoc记录
@Inherited  // 子类可以继承该注解
// 以上四个注解是java注解的元注解,不做说明
@SpringBootConfiguration   // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration   // 开启springboot 的注解功能,springboot的四大神器之一,其中借助了@import的帮助
@ComponentScan(      // 扫描路径设置
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    ...
}

@Configuration

springIOC容器的配置类,任何一个标注了@Configuration的java类都是一个javaConfig配置类

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IOC容器中,方法名默认为该bean定义的id。如果一个bean的定义依赖其他bean,则直接调用对应的javaConfig类中依赖bean的创建方法就可以了。

@ComponentScan

@ComponentScan这个注解就是自动扫描加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到Ioc容器中。我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认spring框架实现会从声明@ComponentScan所在类的package进行扫描。(这也就是为什么springboot 的启动类最好放在root package路径下的原因,因为默认不指定basePackages)

@EnableAutoConfiguration

借助@Import,将所有符合自动配置条件的bean定义加载到Ioc容器中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 以上四个注解是java注解的元注解,不做说明
@AutoConfigurationPackage  //  自动配置包
@Import({AutoConfigurationImportSelector.class})  //  导入自动配置的组件
public @interface EnableAutoConfiguration {
    ...
}

@Import({AutoConfigurationImportSelector.class}) ,借助AutoConfigurationImportSelector,

@EnableAutoConfiguration可以帮助springboot应用将所有符合条件的配置都加载到当前SpringBoot创建并使用的Ioc容器。借助SpringFactoriesLoader工具类的支持,@EnableAutoConfiguration可以智能的自动配置

由以上的分析可知,自动配置的幕后英雄是SpringFactoriesLoader

SpringFactoriesLoader

先来看看该类定义是什么样子的

public final class SpringFactoriesLoader {
// 最耀眼的属性, 定义了一个文件路径 META-INF/spring.factories
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...   
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
 ...
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
... 
    }
}

配置文件META-INF/spring.factories中最后的类名都是XXXAutoConfiguration,然后就是配合@EnableAutoConfiguration注解,拿着配置文件中定义好的类名通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

执行流程

通过查看源码,我们知道SpringBoot应用的执行入口是run方法。我们也是从该方法入手的。大体的流程如下:

  • 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication

    的实例,然后调用这个创建好的SpringApplication是实例方法。

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

根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。

  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer

  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener

推断并设置main方法的定义类。

  • SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
  • 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
  • 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。
  • 如果SpringApplication的showBanner属性被设置为true,则打印banner。
  • 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。
  • ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。(回调)
  • 遍历回调所有SpringApplicationRunListener的contextPrepared()方法。
  • 我们可以按需实现 ApplicationContextInitializer , SpringApplicationRunListener
  • 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
  • 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。
  • 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
  • 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。

以上就是SpringBoot的启动流程。

自定义Starter

考虑使用到的依赖

编写自动配置

// 参考WebMvcAutoConfiguartion.java // springmvc自动配置
@Configuration //指定这个类为配置类
@ConditionalOnXXX  // 满足条件配置类生效
@AutoConfigureAfter // 配置顺序
@Bean // 向容器中添加组件

@ConfigurationProperties
@EnableConfigurationProperties // 让xxxProperties生效并加入到容器

// 自动配置类要想能够加载,需要把它配置到 META-INF/spring.factories

定义模式

  • (启动器)xxx-starter : 一个空的jar包

    • 提供依赖管理,这些依赖可用于自动装配或者其它类库

    • 如果他人要使用此启动器,直接依赖xxx-starter.jar就可以

    • 推荐命名规则

      • 官方的:spring-boot-starter-模块名 ,eg. spring-boot-starter-web , spring-boot-starter-jdbc
      • 自定义:模块名-spring-boot-starter ,eg. mybatis-spring-boot-starter
  • (自动配置模块)xxx-starter-autoconfigurer

    • 被启动器依赖
posted @ 2021-06-27 17:27  沉梦匠心  阅读(74)  评论(0)    收藏  举报