zoukankan      html  css  js  c++  java
  • SpringBoot源码解析

    SpringBoot源码解析

    我们启动SpringBoot的代码:

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

    我们主要从3大点着手解析:

    • @SpringBootApplication注解
    • 构建SpringApplication对象
    • SpringApplication的run方法

    @SpringBootApplication解析

    我们来看看SpringBootApplication的注解的代码:

    @Target(ElementType.TYPE)    //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
    @Retention(RetentionPolicy.RUNTIME) ///表示注解的生命周期,Runtime运行时
    @Documented ////表示注解可以记录在javadoc中
    @Inherited   //表示可以被子类继承该注解
    
    @SpringBootConfiguration //// 标明该类为配置类
    @EnableAutoConfiguration  // 启动自动配置功能
    @ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
    		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication 
    

    排除掉上面的4个元注解外,还剩下3个注解,我们一一分析。

    1. @SpringBootConfiguration

    这个里面什么都没有,就只是在这个注解上标明了@Configuration,代表是一个配置类。
    2. @EnableAutoConfiguration

    这个注解主要是启动自动配置功能,我们点进去详细看看。

    @AutoConfigurationPackage		//自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
    @Import(AutoConfigurationImportSelector.class)  //可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
    public @interface EnableAutoConfiguration {
    

    2.1 我们先看@AutoConfigurationPackage注解

    @Import(AutoConfigurationPackages.Registrar.class)  //  默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
    public @interface AutoConfigurationPackage {
    
    }
    

    可以看出主要是引入了Registrar这个类,继续跟踪Registrar这个类

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
    		// 获取的是项目主程序启动类所在的目录
    		//metadata:注解标注的元数据信息
    		@Override
    		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    			//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
    			register(registry, new PackageImport(metadata).getPackageName());
    		}
    
    		@Override
    		public Set<Object> determineImports(AnnotationMetadata metadata) {
    			return Collections.singleton(new PackageImport(metadata));
    		}
    
    	}
    

    主要目的获取SpringBoot主程序启动类的包名并注册

    2.2 继续看@Import(AutoConfigurationImportSelector.class)

    @Override
    	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    		//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
    		if (!isEnabled(annotationMetadata)) {
    			return NO_IMPORTS;
    		}
    		//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
    		//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
    		// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
    		// 自动配置的类全名.条件=值
    		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    	}
    

    主要是获取并筛选出自动配置类并返回。

    1. @ComponentScan

    这个注解就是包扫描的作用,类似Spring里面的:

    <context:component-scan base-package="com.xxx.xxx"/>
    

    构建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 = new HashSet();
    		this.isCustomEnvironment = false;
    		this.resourceLoader = resourceLoader;
    		Assert.notNull(primarySources, "PrimarySources must not be null");
    
    		//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
    		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    		//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
    		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    		// 设置初始化器(Initializer),最后会调用这些初始化器
    		//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
    		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    		// 设置监听器(Listener)
    		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    		// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
    		this.mainApplicationClass = deduceMainApplicationClass();
    	}
    

    run方法

    public ConfigurableApplicationContext run(String... args) {
    	    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		// 初始化应用上下文和异常报告集合
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		// 配置 headless 属性
    		configureHeadlessProperty();
    
    
    		//   (1)获取并启动监听器
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    		    // 创建  ApplicationArguments 对象 初始化默认应用参数类
    			// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    
    			//(2)项目运行环境Environment的预配置
    			// 创建并配置当前SpringBoot应用将要使用的Environment
    			// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    
    			configureIgnoreBeanInfo(environment);
    			// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
    			Banner printedBanner = printBanner(environment);
    
    			// (3)创建Spring容器
    			context = createApplicationContext();
    			// 获得异常报告器 SpringBootExceptionReporter 数组
    			//这一步的逻辑和实例化初始化器和监听器的一样,
    			// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
    			exceptionReporters = getSpringFactoriesInstances(
    					SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    
    
    			// (4)Spring容器前置处理
    			//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
    			prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    
    			// (5):刷新容器
    			refreshContext(context);
    
    			// (6):Spring容器后置处理
    			//扩展接口,设计模式中的模板方法,默认为空实现。
    			// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
    			afterRefresh(context, applicationArguments);
    			// 停止 StopWatch 统计时长
    			stopWatch.stop();
    			// 打印 Spring Boot 启动的时长日志。
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    			// (7)发出结束执行的事件通知
    			listeners.started(context);
    
    			// (8):执行Runners
    			//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
    			//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
    			//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
    			callRunners(context, applicationArguments);
    		} catch (Throwable ex) {
    		    // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
            //   (9)发布应用上下文就绪事件
    		//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
    		// 这样整个Spring Boot项目就正式启动完成了。
    		try {
    			listeners.running(context);
    		} catch (Throwable ex) {
                // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
                handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		 //返回容器
    		return context;
    	}
    
    书山有路勤为径,学海无涯苦作舟
  • 相关阅读:
    书签
    身份证查询
    Swiper 中文API手册(share)
    seo查询命令
    前端开发薪资之各地区对比(图文分析)(share)
    新浪微博AppKey大集合(share)
    PHP乱码问题,UTF-8(乱码) (share)
    JavaScript 获取数组中最大值、最小值
    设为首页 添加到收藏夹 (share)
    javascript数组 去重
  • 原文地址:https://www.cnblogs.com/javammc/p/15659153.html
Copyright © 2011-2022 走看看