Spring Boot解读

入口

@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,@EnableAutoConfiguration以及@ComponentScan的组合。

@SpringBootConfiguration

这个注解实际上和@Configuration有相同的作用,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。

@ComponentScan

这个注解完成的是自动扫描的功能,相当于Spring XML配置文件中的:

<context:component-scan>

@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};
}

重点关注@Import(EnableAutoConfigurationImportSelector.class

EnableAutoConfigurationImportSelector

在这个类中,提供了一个getCandidateConfigurations()方法用来加载配置文件。借助Spring提供的工具类SpringFactories的loadFactoryNames()方法加载配置文件。扫描的默认路径位于META-INF/spring.factories中。

启动流程

创建SpringApplication实例

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
                //spring applicationContext
        ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}

SpringApplication的构造函数

跟进SpringApplication.run(),调用了SpringApplication的构造函数,这个构造函数的主要作用是创建一个SpringApplication实例,applicationContext将加载指定的主要来源提供的bean。

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应用类型(NONE/SERVLET/REACTIVE),默认为SERVLET
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	//设置将应用于Spring的ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
    	//设置将应用于SpringApplication的ApplicationListener(监听器)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //推断并设置主类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

配置source

this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

推断web应用

WebApplicationType是一个枚举,deduceFromClasspath方法:

static WebApplicationType deduceFromClasspath() {
        // 类路径中是否包含DispatcherHandler,且不包含DispatcherServlet,也不包含ServletContainer,返回基于reactive的web应用
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
        // 如果Servlet或者ConfigurableWebApplicationContext不存在,返回非web应用
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		//返回基于servlet的web应用
		return WebApplicationType.SERVLET;
	}

创建初始化构造器

setInitializers()方法

getSpringFactoriesInstances()

getSpringFactoriesInstances(ApplicationContextInitializer.class)将会调用org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories从META-INF/spring.factories配置文件中找到所有ApplicationContextInitializer接口对应的实现类配置,然后通过反射机制构造出对应的实例对象

spring.factories文件

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=


# Application Listeners
org.springframework.context.ApplicationListener=

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=

设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

也是通过getSpringFactoriesInstances()方法在spring.factories配置文件中找到所有ApplicationListener接口对应的实现类配置

配置应用main方法所在类

this.mainApplicationClass = deduceMainApplicationClass();	

private Class<?> deduceMainApplicationClass() {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}	
}

run方法分析

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



public ConfigurableApplicationContext run(String... args) {
        //Spring计时器StopWatch
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
        
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
         // 获取监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            // 创建并配置Environment(这个过程会加载application配置文件)
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
            //打印banner
			Banner printedBanner = printBanner(environment);
            //根据应用类型创建对应的Context容器
			context = createApplicationContext();
            //配置启动错误回调
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 刷新Context容器前置处理
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            //刷新context容器
			refreshContext(context);
            //刷新Context容器后置处理
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
            // Context容器refresh完毕发布
            //即ApplicationRunner,CommandLineRunner
			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;
	}

应用启动监听器模块

SpringApplicationRunListeners

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

创建并配置Environment

Environment

Environment的组成

  1. profiles

    通常使用profiles划分多环境,每个环境会有有些bean不同、配置不同等

  2. properties

    在java中properties代表着key-value的键值对象集合。Environment内部设计了key-value结构的对象来存储相应的键值。

 Environment创建及配置过程

prepareEnvironment

  • prepareEnvironment方法创建一个Environment实例,也就是spring的运行环境
private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//配置environment
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        //环境准备,触发监听器
		listeners.environmentPrepared(environment);
        //绑定到SpringApplication
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

listeners.environmentPrepared(environment)方法触发监听器EventPublishingRunListener添加spring.factories中配置的ApplicationListener(包括ConfigFileApplicationListener,构造SpringApplication时调用setListeners()载入),随后再触发ConfigFileApplicationListener,ConfigFileApplicationListener将会加载如application.properties/yml这样的配置文件  

  • getOrCreateEnvironment

创建environment对象,根据web容器类型构造,由上文中的deduceFromClassPath推断的类型

private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}
  • configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService
					.getSharedInstance();
			environment.setConversionService(
					(ConfigurableConversionService) conversionService);
		}
    //添加、删除、重排序配置源(@PropertySource、@ImportResource)
		configurePropertySources(environment, args);
    //配置profiles
		configureProfiles(environment, args);
	}
  • ConfigFileApplicationListener

ConfigFileApplicationListener处理逻辑,最后调用new Loader(environment, resourceLoader).load()

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 只触发Environment相关的事件
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        // 执行后置处理器
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}
  • Loader

        Loader构造器

        构造方法中将会从spring.factories中加载PropertySourceLoader接口的具体实现类,有两个实现,是YamlPropertySourceLoader(ym、yaml)和PropertiesPropertySourceLoader(property、xml)

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    this.environment = environment;
    this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
    this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
    // 文件application配置文件的资源加载器,包括propertis/xml/yml/yaml扩展名
    this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
            getClass().getClassLoader());
}

       load()方法

//第一个load方法
public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消费一个profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加载
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加载
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加所有properties到Environment中
    addLoadedPropertySources();
}

//第二个load方法
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    // 获取并遍历所有待搜索的位置,没有自定义一些搜索位置,那么默认搜索classpath:/、classpath:/config/、file:./、file:./
    getSearchLocations().forEach((location) -> {
        boolean isFolder = location.endsWith("/");
        // 获取所有待加载的配置文件名,没有自定义则返回默认的配置文件名application
        Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
        // 加载每个位置的每个文件
        names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
    });
}

//第三个load方法
//主要逻辑表明将会加载每个加载器可以支持的配置文件,在Loader初始化的时候我们获得了两个加载器,同时每个加载器支持两种格式
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
        DocumentConsumer consumer) {
   ...
    Set<String> processed = new HashSet<>();
    // 遍历加载器
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
        // 获取扩展名
        for (String fileExtension : loader.getFileExtensions()) {
            if (processed.add(fileExtension)) {
                // 加载对应扩展名的文件
                loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                        consumer);
            }
        }
    }
}

//
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
        Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
    DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
    // 当前没有profile
    if (profile != null) {
        String profileSpecificFile = prefix + "-" + profile + fileExtension;
        load(loader, profileSpecificFile, profile, defaultFilter, consumer);
        load(loader, profileSpecificFile, profile, profileFilter, consumer);
        for (Profile processedProfile : this.processedProfiles) {
            if (processedProfile != null) {
                String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
    // 加载具体格式的文件
    load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

//第四个load方法
private void load(
        PropertySourceLoader loader, 
        String location, 
        Profile profile, 
        DocumentFilter filter,
        DocumentConsumer consumer
        ) {
    try {
        // 获取资源
        Resource resource = this.resourceLoader.getResource(location);
  
        // 加载为Document对象
        List<Document> documents = loadDocuments(loader, name, resource);
   
        List<Document> loaded = new ArrayList<>();
        // 遍历Document集合
        for (Document document : documents) {
            if (filter.match(document)) {
                // 添加profile
                addActiveProfiles(document.getActiveProfiles());
                addIncludedProfiles(document.getIncludeProfiles());
                loaded.add(document);
            }
        }
        Collections.reverse(loaded);
        if (!loaded.isEmpty()) {
            // 回调处理每个document
            loaded.forEach((document) -> consumer.accept(profile, document));
        }
    } catch (Exception ex) {}
}

//第一个load调用
private void addLoadedPropertySources() {
    MutablePropertySources destination = this.environment.getPropertySources();
    List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
    // 反向排序
    Collections.reverse(loaded);
    String lastAdded = null;
    Set<String> added = new HashSet<>();
    // 遍历loaded
    for (MutablePropertySources sources : loaded) {
        // 遍历配置
        for (PropertySource<?> source : sources) {
            // 排重
            if (added.add(source.getName())) {
                // 添加每个到Environment中
                addLoadedPropertySource(destination, lastAdded, source);
                lastAdded = source.getName();
            }
        }
    }
}

        加载默认的application.properties/yml

private void initializeProfiles() {
    // 第一个profile为null,这样能保证首个加载application.properties/yml
    this.profiles.add(null);
    Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
    this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
    addActiveProfiles(activatedViaProperty);
    // 没有额外配置profile的时候,将使用默认的
    if (this.profiles.size() == 1) {
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            Profile defaultProfile = new Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }
}

扩展知识:Spring Cloud Config及其他配置中心,配置刷新使用springboot监听机制

打印banner

Banner printedBanner = printBanner(environment);

	private Banner printBanner(ConfigurableEnvironment environment) {
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
		ResourceLoader resourceLoader = (this.resourceLoader != null)
				? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
		SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
				resourceLoader, this.banner);
		if (this.bannerMode == Mode.LOG) {
			return bannerPrinter.print(environment, this.mainApplicationClass, logger);
		}
		return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
	}
//控制banner展示
	static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
	static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
	static final String DEFAULT_BANNER_LOCATION = "banner.txt";
	static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
//没有配置则打印SpringBootBanner.BANNER

创建Context容器

createApplicationContext()方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回。

ConfigurableApplicationContext继承的两个方向:

  • LifeCycle:生命周期类,定义了start启动、stop结束、isRunning是否运行中等生命周期空值方法
  • ApplicationContext:应用上下文类,其主要继承了beanFactory(bean的工厂类)
protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

Context容器前置处理

prepareContext()方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联。加载过程先填充Environment以及设置的参数,然后执行注册到spring.factoriesApplicationContextInitializer切面,如果自己实现ApplicationContextInitializer的话要注意这时context已经有的信息是什么。接着发布ApplicationContextInitializedEvent事件,然后加载bean,最后发布ApplicationPreparedEvent事件。

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 将环境和上下文关联起来
    context.setEnvironment(environment);

    // 为上下文配置Bean生成器以及资源加载器(如果它们非空)
    postProcessApplicationContext(context);

    // 调用初始化器
    applyInitializers(context);

    // 触发Spring Boot启动过程的contextPrepared事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 添加两个Spring Boot中的特殊单例Beans - springApplicationArguments以及springBootBanner
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 加载sources - 对于DemoApplication而言,这里的sources集合只包含了它一个class对象
    Set<Object> sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");

    // 加载动作 - 构造BeanDefinitionLoader并完成Bean定义的加载
    load(context, sources.toArray(new Object[sources.size()]));

    // 触发Spring Boot启动过程的contextLoaded事件
    listeners.contextLoaded(context);
}

刷新Context容器

refreshContext(context)方法将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//记录启动时间、状态,web容器初始化其property,复制listener
			prepareRefresh();
			// 告诉子类刷新内部bean工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			//beanFactory注入一些标准组件,例如ApplicationContextAwareProcessor,ClassLoader等
			prepareBeanFactory(beanFactory);
			try {
				//允许在context子类中对bean工厂进行后处理。即给实现类留的一个钩子,例如注入BeanPostProcessors,这里是个空方法
				postProcessBeanFactory(beanFactory);
				// 调用在context注册为bean的工厂处理器。这步注册定义的bean
				invokeBeanFactoryPostProcessors(beanFactory);
				// 注册拦截Bean创建的Bean处理器
				registerBeanPostProcessors(beanFactory);
				// Initialize message source for this context.
				initMessageSource();
				// 为此上下文初始化事件广播器。
				initApplicationEventMulticaster();
				//  给实现类留的一钩子,可以执行其他refresh的工作,这里是个空方法
				onRefresh();
				// 将listener注册到广播器中
				registerListeners();
				// 实例化未实例化的bean
				finishBeanFactoryInitialization(beanFactory);
				//清理缓存,注入DefaultLifecycleProcessor,发布ContextRefreshedEvent
				finishRefresh();
			}
			catch (BeansException 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();
			}
		}
	}

刷新Context后置处理

afterRefresh(context, applicationArguments)方法是个空实现

Runner

ApplicationRunner和CommandLineRunner是springboot启动加载类

callRunners方法中调用了ApplicationRunner和CommandLineRunner的run方法

private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

监听器模块启动

    public void running(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {                          
                listener.running(context);
        }    
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值