Spring Boot 启动过程深度解析

本文主要是为了解决大家阅读springboot时候的痛点。我觉得大家的痛点应该是,虽然大家看过代码,但可能被代码的复杂性和细节淹没了,大家看了也是知其然不知其所以然。因为Spring Boot 的启动流程本身是一个多步骤、涉及多个关键组件的过程,如果没有一个清晰的框架,确实容易迷失方向。所以,我回从整体到局部,逐步深入,把每一步的逻辑讲清楚,同时不忽视代码中的关键点,比如那些看似不起眼但却至关重要的设计。
请注意,本文是你们截止目前所看到最全的最系统的一篇关于springboot启动流程的文章

1.文章介绍

  • 本文基于springboot-3.4.2,JDK17分析
	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  • 先从整体上概述 Spring Boot 的启动流程,列出主要步骤,给用户一个全局的视野。
  • 然后深入到每个步骤,详细讲解每一步的代码逻辑和设计思想。比如,先是 - SpringApplication 的初始化,然后是 run 方法的执行,再到上下文的创建和刷新。
  • 在讲解每个步骤的时候,找到关键点和痛点,比如自动配置、Bean 的生命周期管理等,逐一展开。
  • 最后,加一些扩展内容,比如自定义启动流程、性能优化技巧等等,让用户不仅能理解 Spring Boot 的启动流程,还能根据自己的需求进行扩展和优化。

2.整体概述

  • 启动流程图
    在这里插入图片描述
  • 左侧部分:SpringApplication初始化

1.ResourceLoader: 设置ResourceLoader。
2.primarySources: 主要的配置源。
3.WebApplicationType: 推断Web应用类型。
4.BootstrapRegistryInitializer列表: 初始化引导注册表初始化器列表。
5.ApplicationContextInitializer列表: 初始化应用上下文初始化器列表。
6.ApplicationListener列表: 初始化应用监听器列表。
7.deduceMainApplicationClass: 推断主应用程序类。

  • 右侧部分:run

1.Startup.create: 创建一个Startup对象。
2.enableShutdownHookAddition: 启用关闭钩子添加。
3.createBootstrapContext: 创建引导上下文。
4.configureHeadlessProperty: 配置无头模式属性。
5.getRunListeners: 获取运行监听器。
6.listeners.starting: 通知监听器应用启动开始。
7.new DefaultApplicationArguments: 创建默认的应用参数对象。
8.prepareEnvironment: 准备应用环境。
9.printBanner(environment): 打印启动横幅。
10.createApplicationContext: 创建应用上下文。
11.context.setApplicationStartup: 设置应用启动上下文。
12.refreshContext(context): 刷新Spring容器。
13.afterRefresh(context, applicationArguments): 刷新容器后执行的操作。
14.startup.started: 执行启动后的操作。
15.new StartupInfoLogger: 记录启动信息日志。
16.callRunners: 执行启动后的任务。
17.listeners.ready: 通知监听器应用已启动。

3.步骤介绍

3.1.左侧部分:SpringApplication初始化

3.1.1.ResourceLoader: 设置ResourceLoader

this.resourceLoader = resourceLoader;

  • 介绍
    将构造方法传入的resourceLoader设置到全局变量。方便后续使用
  • 目的
    这里主要是将构造方法传入的ResourceLoader进行注入,方便用户在启动的时候,配置自定义的ResourceLoader。
  • 关键点

ResourceLoader 是 Spring 的一个接口,主要用于加载资源。在 Spring 应用中,资源可以是配置文件、模板、图片等各种类型的文件或数据源。通过 ResourceLoader,系统能够以统一的方式访问这些资源,而不需要关心它们的具体存储位置(如文件系统、网络路径或类路径等)

1.统一资源访问:ResourceLoader 提供了一个统一的接口来加载不同来源的资源,简化了对资源的管理。
2.支持多种资源类型:它可以加载各种类型的资源,包括但不限于文件系统资源、classpath 资源、URL 资源等。
3.灵活的实现:Spring 提供了多个 ResourceLoader 的实现类,如 DefaultResourceLoader 和 FileSystemResourceLoader 等。
4.集成 Spring 环境:ResourceLoader 可以与 Spring 的应用上下文紧密集成,使得在应用上下文中获取资源变得非常方便。

  • 扩展点

Return a {@code Resource} handle for the specified resource location.
The handle should always be a reusable resource descriptor,
allowing for multiple {@link Resource#getInputStream()} calls.
Must support fully qualified URLs, for example, “file:C:/test.dat”.
Must support classpath pseudo-URLs, for example, “classpath:test.dat”.
Should support relative file paths, for example, “WEB-INF/test.dat”.

从上述代码注释中可以看到。ResourceLoader可以加载多种形式的配置信息。用户可以根据自己的需求,在应用程序运行时,加载信息。示例如下:

定义Resource


//定义Resource
public class CustomResource implements Resource {
   
   

    private final String location;

    public CustomResource(String location) {
   
   
        this.location = location;
        System.out.println("CustomResource: Created for location " + location);
    }

    @Override
    public boolean exists() {
   
   
        try {
   
   
            URL url = new URL(location);
            url.openConnection().connect();
            System.out.println("CustomResource: Resource exists at " + location);
            return true;
        } catch (IOException e) {
   
   
            System.out.println("CustomResource: Resource does not exist at " + location);
            return false;
        }
    }

    @Override
    public boolean isReadable() {
   
   
        return exists();
    }

    @Override
    public boolean isOpen() {
   
   
        return false;
    }

    @Override
    public boolean isFile() {
   
   
        return false;
    }

    @Override
    public URL getURL() throws IOException {
   
   
        URL url = new URL(location);
        System.out.println("CustomResource: Returning URL " + url.toString());
        return url;
    }

    @Override
    public URI getURI() throws IOException {
   
   
        return null;
    }

    @Override
    public File getFile() throws IOException {
   
   
        throw new FileNotFoundException("Resource cannot be resolved to an absolute file path");
    }

    @Override
    public ReadableByteChannel readableChannel() throws IOException {
   
   
        return Resource.super.readableChannel();
    }

    @Override
    public byte[] getContentAsByteArray() throws IOException {
   
   
        return Resource.super.getContentAsByteArray();
    }

    @Override
    public String getContentAsString(Charset charset) throws IOException {
   
   
        return Resource.super.getContentAsString(charset);
    }

    @Override
    public InputStream getInputStream() throws IOException {
   
   
        URL url = new URL(location);
        InputStream inputStream = url.openStream();
        System.out.println("CustomResource: Opened input stream for " + location);
        return inputStream;
    }

    @Override
    public long contentLength() throws IOException {
   
   
        return -1;
    }

    @Override
    public long lastModified() throws IOException {
   
   
        return 0;
    }

    @Override
    public Resource createRelative(String relativePath) throws IOException {
   
   
        String newPath = location + "/" + relativePath;
        System.out.println("CustomResource: Creating relative path " + newPath);
        return new CustomResource(newPath);
    }

    @Override
    public String getFilename() {
   
   
        return new File(location).getName();
    }

    @Override
    public String getDescription() {
   
   
        return "Custom resource at location: " + location;
    }
}

定义ResourceLoader


public class CustomResourceLoader implements ResourceLoader {
   
   

    @Override
    public Resource getResource(String location) {
   
   
        System.out.println("CustomResourceLoader: Loading resource from " + location);
        return new CustomResource(location);
    }

    @Override
    public ClassLoader getClassLoader() {
   
   
        return getClass().getClassLoader();
    }
}

在应用中加载并使用资源


@Service
public class ResourceService {
   
   

    private final ResourceLoader resourceLoader;

    @Autowired
    public ResourceService(ApplicationContext applicationContext) {
   
   
        this.resourceLoader = applicationContext;
    }

    public void loadResource(String location) throws Exception {
   
   
        Resource resource = resourceLoader.getResource(location);
        if (resource.exists()) {
   
   
            System.out.println("Loaded resource from " + location);
        } else {
   
   
            System.out.println("Resource not found at " + location);
        }
    }
}

ResourceLoader可以通过启动类set方法加载或者通过@Bean注解加载

3.1.2.primarySources: 主要的配置源。

Assert.notNull(primarySources, “PrimarySources must not be null”);
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

启动应用时作为主要来源的类集合。这些类通常是包含 @SpringBootApplication 注解的主应用程序类和其他关键配置类

  • primarySources 的作用

引导上下文初始化:
在 Spring Boot 启动时,primarySources 可以用来引导初始上下文的创建。Spring Boot 使用这决定需要加载哪些配置类、自动配置类以及其他必要的组件。

确定自动配置类:
primarySources 还用于确定哪些自动配置类应该被启用。Spring Boot 的自动配置机制会根据这些主要源中的注解和类信息,自动加载合适的自动配置类。

控制启动顺序:
primarySources 可以帮助 Spring Boot 控制应用的启动顺序,确保核心组件和配置在正确的时间点被加载和初始化。

  • 介绍

primarySources 是指在 Spring Boot 应用启动时,用于引导初始上下文创建的类,除了启动类,还包括其他重要的配置类或组件类
主要包含以下几类
1.配置类 (@Configuration):定义和管理 Bean。
2.属性配置类 (@ConfigurationProperties):简化配置管理。
3.自动配置类 (@EnableAutoConfiguration):自动加载和配置常用 Bean。
4.组件扫描类 (@ComponentScan):自动发现和注册 Bean。
5.控制器类 (@Controller, @RestController):处理 HTTP 请求。
6.服务类 (@Service):封装业务逻辑。
7.数据访问层 (@Repository):与数据库交互。
8.事件监听器 (@EventListener):监听和处理 Spring 事件。
9.初始化器 (ApplicationContextInitializer):在上下文初始化之前执行自定义逻辑。
10.启动监听器 (ApplicationListener):监听应用启动事件。
11.异常处理器 (@ControllerAdvice, @ExceptionHandler):全局处理控制器异常。

  • 关键点

显式控制:直接在代码中指定了哪些类是主要源,这使得控制更加直观和明确。
灵活性:可以根据不同的运行环境或条件动态地添加或移除这些类。
局部性:这种配置仅限于当前应用实例,不会影响其他使用相同依赖的应用

  • 扩展点

这里可以直接在代码中指定了哪些类是主要源。相较于spring.factories更加直观。
示例代码:

  1. 配置类 (@Configuration)
    配置类用于定义和管理 Spring 容器中的 Bean。通过 @Bean 注解方法来创建和注册 Bean。如数据库连接、消息队列等。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
  1. 属性配置类 (@ConfigurationProperties)
    从配置文件(如 application.properties 或 application.yml)中读取属性,并将其绑定到 Java 对象上。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {

    private String name;
    private int timeout;

    // Getters and Setters
}
  1. 自动配置类 (@EnableAutoConfiguration)
    自动加载和配置一些常用的 Bean 和功能。
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}
  1. 初始化器 (ApplicationContextInitializer)
    在 Spring 上下文初始化之前执行自定义逻辑。比如预处理上下文:在上下文完全初始化之前执行一些准备工作。动态配置:可以根据环境变量或条件动态调整配置。
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class CustomInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 执行自定义初始化逻辑
    }
}
  1. 异常处理器 (@ControllerAdvice, @ExceptionHandler)
    全局处理控制器抛出的异常。统一异常处理,避免在每个控制器中重复编写异常处理逻辑。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return new ResponseEntity<>("An error occurred: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
3.1.3.WebApplicationType: 推断Web应用类型。
  • 介绍
    自动推断应用的 Web 应用类型。根据类路径中的依赖来决定应用是 Servlet Web 应用、Reactive Web 应用还是无 Web 应用。
  • 关键点
//这段代码很简单,就是判断Web 应用类型的源代码
static WebApplicationType deduceFromClasspath() {
   
   
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
   
   
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
   
   
			if (!ClassUtils.isPresent(className, null)) {
   
   
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

判断是否为 REACTIVE 类型
类路径中存在 DispatcherHandler,但不存在 DispatcherServlet 和 ResourceConfig,则为 REACTIVE 类型。

判断是否为 SERVLET 类型
所有 SERVLET_INDICATOR_CLASSES 中的类都存在于类路径中,则为 SERVLET 类型

默认返回 SERVLET 类型
都不满足,返回 WebApplicationType.SERVLET

Servlet Web 应用:用于传统的基于 Servlet API 的 Web 应用,使用 Spring MVC 处理 HTTP 请求,通常与嵌入式 Servlet 容器(如 Tomcat)一起使用。
Reactive Web 应用:用于响应式编程模型的 Web 应用,使用 Spring WebFlux 处理 HTTP 请求,通常与嵌入式 Reactive 容器(如 Netty)一起使用。
无 Web 应用:适用于不需要任何 Web 功能的应用,如后台任务、批处理、定时任务等,启动时不会初始化任何 Web 容器。

  • 扩展点
    Servlet Web 应用:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Reactive Web 应用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

无 Web 应用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
3.1.4.BootstrapRegistryInitializer列表: 初始化引导注册表初始化器列表。

this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

  • 介绍

通过 getSpringFactoriesInstances 方法从 META-INF/spring.factories 文件中加载所有实现了 BootstrapRegistryInitializer 接口的类,并将它们存储在一个 ArrayList 中。

  • 关键点

BootstrapRegistryInitializer 是 Spring Boot 提供的一个扩展点,允许在应用启动的早期阶段注册一些初始化的逻辑。这些逻辑会在应用上下文创建之前执行,因此可以用于配置非常早期的依赖项或进行一些必要的预处理工作。

  • 关键代码介绍

使用SpringFactoriesLoader.forDefaultResourceLocation方法加载指定类型的的实力。

	private <T> List<T> getSpringFactoriesInstances(Class<T> type) {
   
   
			return getSpringFactoriesInstances(type, null);
		}
	
	private <T> List<T> getSpringFactoriesInstances(Class<T> type, ArgumentResolver argumentResolver) {
   
   
			return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);
	}

从META-INF/spring.factories中读取数据。其中loadFactoriesResource(resourceClassLoader, resourceLocation)是读取factories文件内容的关键代码。很简单,这里就不在描述

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {
   
   
			return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);
		}
		
	public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
   
   
		Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
		ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
				SpringFactoriesLoader.class.getClassLoader());
//从缓存中获取与 resourceClassLoader 对应的 loaders 。如果映射不存在,则创建一个新的 ConcurrentReferenceHashMap 并将其放入缓存中
		Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(
				resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
//从 loaders 映射中获取与 META-INF/spring.factories 对应的 SpringFactoriesLoader 实例。如果实例不存在,则创建一个新的 SpringFactoriesLoader 实例并将其放入映射中并返回
		return loaders.computeIfAbsent(resourceLocation, key ->
				new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
	}

使用loadFactoryNames(factoryType)获取factoryType类型信息,使用instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse)进行初始化

public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
			@Nullable FailureHandler failureHandler) {
   
   

		Assert.notNull(factoryType, "'factoryType' must not be null");
		//获取factoryType配置信息
		List<String> implementationNames = loadFactoryNames(factoryType);
		logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
		List<T> result = new ArrayList<>(implementationNames.size());
		FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
		for (String implementationName : implementationNames) {
   
   
		//初始化对象
			T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
			if (factory != null) {
   
   
				result.add(factory);
			}
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
  • 扩展点
/**
 * Callback interface that can be used to initialize a {@link BootstrapRegistry} before it
 * is used. 是一个回调接口,可以在 BootstrapRegistry 被使用之前对其进行初始化
 * @see SpringApplication#addBootstrapRegistryInitializer(BootstrapRegistryInitializer)
 * @see BootstrapRegistry
 */
@FunctionalInterface
public interface BootstrapRegistryInitializer {
   
   

	/**
	 * Initialize the given {@link BootstrapRegistry} with any required registrations.
	 * @param registry the registry to initialize
	 */
	void initialize(BootstrapRegistry registry);

}

是一个函数式接口,意味着它可以被用作 Lambda 表达式的类型。函数式接口只能有一个抽象方法。请自行学习

典型的扩展使用如:spring cloud config的使用。如下图:
在这里插入图片描述
自定义扩展使用,远程拉取application.yml:
1.线下BootstrapRegistryInitializer,重新initialize方法:


public class RmoteConfigBootstrapRegistryInitializer implements BootstrapRegistryInitializer {
   
   
    @Override
    public void initialize(BootstrapRegistry registry) {
   
   
        try {
   
   
            // 加载配置
            Map properties = loadConfiguration();
            // 注册配置PropertySource进入BootstrapRegistry
            registry.register(ConfigurableEnvironment.class, context -> {
   
   
                ConfigurableEnvironment environment = context.get(ConfigurableEnvironment.class);
                PropertySource<?> propertySource = new OriginTrackedMapPropertySource("remoteConfig", properties);
                environment.getPropertySources().addFirst(propertySource);
                return environment;
            });

        } catch (Exception e) {
   
   
            throw new RuntimeException("Failed to initialize configuration", e);
        }
    }

    private Map<String, Object> loadConfiguration() throws IOException {
   
   

        Map<String, Object> properties = new HashMap<>();
        properties.put("key1", "value1");
        properties.put("key2", "value2");
        // 自定义实现
        return properties;
    }
}

2.配置 META-INF/spring.factorie

org.springframework.boot.BootstrapRegistryInitializer=\
com.example.RmoteConfigBootstrapRegistryInitializer
3.1.5.ApplicationContextInitializer列表: 初始化应用上下文初始化器列表。
  • 介绍

通过 getSpringFactoriesInstances 方法从 META-INF/spring.factories 文件中加载所有实现了 ApplicationContextInitializer 接口的类,并将它们存储在一个 ArrayList

getSpringFactoriesInstances(ApplicationContextInitializer.class):
方法是从 META-INF/spring.factories 文件中加载所有实现了 ApplicationContextInitializer 接口的类,并返回这些类的实例集合。

  • 关键点
    与BootstrapRegistryInitializer类似,这里不再描述
  • 扩展点
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
   
   
    void initialize(C applicationContext);
}

是一个函数式接口,意味着它可以被用作 Lambda 表达式的类型。函数式接口只能有一个抽象方法。请自行学习

1.自定义 PropertySource

public class MyCustomPropertySource extends PropertySource<Map<String, Object>> {
   
   

    public MyCustomPropertySource() {
   
   
        super("myCustomPropertySource");
        Map<String, Object> source = new HashMap<>();
        source.put("custom.property", "Hello !");
    }

    @Override
    public Object getProperty(String name) {
   
   
        return this.source.get(name);
    }
}

2.实现 ApplicationContextInitializer

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
   
   

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
   
   
        // 在这里可以进行一些初始化操作
        System.out.println("com.ant.ck.server.MyApplicationContextInitializer: Initializing application context...");
        
        // 添加一个属性源
        applicationContext.getEnvironment().getPropertySources().addFirst(new MyCustomPropertySource());
    }
}

3.在META-INF/spring.factories中添加ApplicationContextInitializer实现类

org.springframework.context.ApplicationContextInitializer=\
com.example.MyApplicationContextInitializer

4.如果不想通过spring.factories配置,还可以再启动类中进行配置,代码如下:

@SpringBootApplication
public class MyApplication {
   
   

    public static void main(String[] args) {
   
   
        SpringApplication application = new SpringApplication(MyApplication.class);

        application.addBootstrapRegistryInitializer(new RmoteConfigBootstrapRegistryInitializer());
        
        application.addInitializers(new MyApplicationContextInitializer());
        // 启动应用
        ConfigurableApplicationContext context = application.run(args);

        // 打印自定义属性值以验证初始化是否成功
        String customPropertyValue = context.getEnvironment().getProperty("custom.property");
        System.out.println("Custom property value: " + customPropertyValue);
    }
}
3.1.6.ApplicationListener列表: 初始化应用监听器列表。

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

  • 介绍

通过 getSpringFactoriesInstances 方法从 META-INF/spring.factories 文件中加载所有实现了 ApplicationListener 接口的类,并将它们存储在一个 ArrayList 中。

  • 关键点

1.加载逻辑与BootstrapRegistryInitializer一样,这里不再描述
2.Spring 框架中用于监听应用程序事件的核心接口之一,可以在应用生命周期的不同阶段执行自定义逻辑

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
   
   

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

	/**
	 * Return whether this listener supports asynchronous execution.
	 * @return {@code true} if this listener instance can be executed asynchronously
	 * depending on the multicaster configuration (the default), or {@code false} if it
	 * needs to immediately run within the original thread which published the event
	 * @since 6.1
	 * @see org.springframework.context.event.SimpleApplicationEventMulticaster#setTaskExecutor
	 */
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值