Spring Boot的启动过程是一个复杂且精心设计的过程,涉及多个关键阶段和组件的协同工作。理解整个启动流程对于后续的启动速度优化至关重要。下面我们将从源码层面详细分析Spring Boot的启动流程。
Spring Boot应用的启动通常从SpringApplication.run()
方法开始,这是整个启动流程的入口点。让我们看一下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);
}
public ConfigurableApplicationContext run(String... args) {
// 记录启动开始时间
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 获取并启动所有SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 创建应用参数对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner
Banner printedBanner = printBanner(environment);
// 创建应用上下文
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 准备应用上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文
refreshContext(context);
// 刷新后的处理
afterRefresh(context, applicationArguments);
// 计算并记录启动时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 发布应用启动完成事件
listeners.started(context, timeTakenToStartup);
// 执行所有Runner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 处理启动异常
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布应用就绪事件
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
// 处理就绪事件异常
if (context != null) {
context.close();
}
throw new IllegalStateException(ex);
}
return context;
}
从上述代码可以看出,run()
方法是整个启动流程的核心,它完成了从环境准备到应用上下文刷新的一系列关键操作。主要步骤包括:
SpringApplicationRunListeners
,用于监听启动过程中的各个阶段Spring Boot的启动过程可以分为多个核心阶段,每个阶段都有其特定的职责和执行逻辑。下面我们将详细分析这些阶段:
初始化阶段主要完成SpringApplication
对象的创建和基本配置,代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用类型(SERVLET、REACTIVE或NONE)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断主应用类
this.mainApplicationClass = deduceMainApplicationClass();
}
在这个阶段,Spring Boot会:
ApplicationContextInitializer
)ApplicationListener
)main
方法的类环境准备阶段负责创建和配置应用运行环境,包括命令行参数解析和属性源配置:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 创建环境对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 发布环境准备事件
listeners.environmentPrepared(bootstrapContext, environment);
// 绑定应用参数
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
环境准备阶段的主要工作包括:
在这个阶段,Spring Boot会创建应用上下文并进行必要的准备工作:
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置环境
context.setEnvironment(environment);
// 应用初始化器
postProcessApplicationContext(context);
applyInitializers(context);
// 发布上下文准备事件
listeners.contextPrepared(context);
// 加载源
load(context, this.primarySources.toArray(new Object[0]));
// 发布上下文加载完成事件
listeners.contextLoaded(context);
}
这个阶段的关键操作包括:
@SpringBootApplication
注解的类)刷新应用上下文是Spring Boot启动过程中最核心也是最耗时的阶段:
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
refresh(context);
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
这里调用了AbstractApplicationContext
的refresh()
方法,这是Spring框架的核心方法,负责整个应用上下文的初始化和bean的创建:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新上下文
prepareRefresh();
// 获取并刷新bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 配置bean工厂
prepareBeanFactory(beanFactory);
try {
// 允许子类对bean工厂进行后处理
postProcessBeanFactory(beanFactory);
// 调用所有注册的bean工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册所有的bean后处理器
registerBeanPostProcessors(beanFactory);
// 初始化消息源
initMessageSource();
// 初始化应用事件多播器
initApplicationEventMulticaster();
// 初始化其他特殊bean
onRefresh();
// 检查并注册监听器
registerListeners();
// 实例化所有剩余的非懒加载单例bean
finishBeanFactoryInitialization(beanFactory);
// 完成刷新,发布相应事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"canceling refresh attempt: " + ex);
}
// 销毁已创建的bean
destroyBeans();
// 重置'active'标志
cancelRefresh(ex);
// 将异常传播出去
throw ex;
}
finally {
// 重置公共缓存
resetCommonCaches();
}
}
}
这个方法包含了Spring应用上下文初始化的完整流程,主要包括:
应用上下文刷新完成后,Spring Boot会进行一些启动后的处理工作:
private void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
// 调用Lifecycle处理器的onRefresh方法
lifecycleProcessor.onRefresh();
// 注册命令行关闭钩子
registerApplicationShutdownHook(context);
// 注册使用了@ImportRuntimeHints注解的类
runtimeHints.processRegistrations(context);
}
这个阶段主要完成:
Lifecycle
接口的bean在Spring Boot的启动过程中,有几个关键组件发挥了重要作用:
SpringApplicationRunListeners
是一个监听Spring Boot启动过程的组件集合,它允许在启动的不同阶段插入自定义逻辑:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
这些监听器通过Spring的工厂加载机制从META-INF/spring.factories
文件中加载,默认实现包括EventPublishingRunListener
,它会将启动过程中的事件发布到应用上下文中。
ApplicationContextInitializer
是一个应用上下文初始化器,允许在上下文刷新之前对其进行自定义配置:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 使用Spring的工厂加载机制加载所有实现类
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
常见的初始化器包括ContextIdApplicationContextInitializer
、ConfigurationWarningsApplicationContextInitializer
等,它们会在上下文刷新前执行一些必要的配置工作。
这两个组件是Spring框架的核心扩展点:
BeanFactoryPostProcessor
允许在bean定义加载后但bean实例化前对bean工厂进行修改BeanPostProcessor
允许在bean实例化后但初始化前后对bean进行处理在Spring Boot启动过程中,大量使用了这两个扩展点来实现自动配置、条件加载等功能。例如,ConfigurationClassPostProcessor
是一个重要的bean工厂后处理器,它负责处理配置类和自动配置类。
AutoConfigurationImportSelector
是Spring Boot自动配置的核心组件,它负责从META-INF/spring.factories
文件中加载所有自动配置类:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 加载候选自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复项
configurations = removeDuplicates(configurations);
// 获取要排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查排除的配置类
checkExcludedClasses(configurations, exclusions);
// 移除要排除的配置类
configurations.removeAll(exclusions);
// 应用自动配置导入过滤器
configurations = getConfigurationClassFilter().filter(configurations);
// 发布自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
自动配置类的加载和处理是Spring Boot启动过程中的一个重要环节,也是影响启动速度的关键因素之一。
Spring Boot的启动流程可以看作是一个复杂的时序过程,各个阶段和组件按照特定的顺序协同工作。下面是一个简化的启动时序图:
+-------------------+ +----------------------+ +------------------------+
| 初始化阶段 |-------->| 环境准备阶段 |-------->| 上下文创建与准备阶段 |
| | | | | |
| - 创建SpringApplication| | - 创建环境对象 | | - 创建应用上下文 |
| - 设置初始化器和监听器| | - 配置环境 | | - 应用初始化器 |
| - 推断应用类型 | | - 绑定应用参数 | | - 加载配置源 |
+-------------------+ +----------------------+ +------------------------+
| |
| |
v v
+-------------------+ +----------------------+ +------------------------+
| 上下文刷新阶段 | | 启动后处理阶段 | | 应用就绪阶段 |
| | | | | |
| - 刷新bean工厂 | | - 启动Lifecycle bean | | - 发布应用就绪事件 |
| - 调用后处理器 | | - 注册关闭钩子 | | |
| - 实例化单例bean | | | | |
+-------------------+ +----------------------+ +------------------------+
这个时序图展示了Spring Boot启动过程中的主要阶段和它们之间的依赖关系。理解这个时序关系对于后续分析启动速度瓶颈和优化非常重要。
Java类加载机制是Java平台的核心特性之一,它负责将字节码文件加载到Java虚拟机中。了解类加载机制对于理解Spring Boot的启动过程至关重要。
Java类加载采用双亲委派模型,主要有三个核心类加载器:
java.lang.*
等javax.*
等双亲委派模型的工作流程是:当一个类加载器收到类加载请求时,它首先会将请求委派给父类加载器,只有当父类加载器无法加载该类时,才会尝试自己加载。
下面是一个简化的类加载过程示意图:
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 检查类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 委派给父类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 父类加载器为null,使用Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载器无法加载
}
if (c == null) {
// 父类加载器无法加载,尝试自己加载
c = findClass(name);
}
}
return c;
}
Spring Boot应用的类加载有一些特殊之处,主要体现在以下几个方面:
Spring Boot应用通常打包为一个可执行的JAR文件,其中包含了应用代码和所有依赖。这种嵌套JAR结构需要特殊的类加载器来支持:
public class LaunchedURLClassLoader extends URLClassLoader {
private final boolean exploded;
public LaunchedURLClassLoader(boolean exploded, URL[] urls, ClassLoader parent) {
super(urls, parent);
this.exploded = exploded;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先检查是否已经加载
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
// 尝试从父类加载器加载
loadedClass = getParent().loadClass(name);
} catch (ClassNotFoundException ex) {
// 父类加载器无法加载,尝试自己加载
loadedClass = findClass(name);
}
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
}
@Override
public URL getResource(String name) {
// 处理嵌套JAR中的资源
URL url = findResource(name);
if (url == null) {
url = super.getResource(name);
}
return url;
}
}
Spring Boot的自动配置机制依赖于类路径扫描和条件加载。在启动过程中,Spring Boot会扫描META-INF/spring.factories
文件,加载所有指定的自动配置类:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
Spring Boot的自动配置是基于条件的,只有当满足特定条件时才会加载相应的配置类。这涉及到大量的类存在检查:
public class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 处理所有@ConditionalOnClass注解
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
ClassNameFilter filter = ClassNameFilter.MISSING;
MatchResult matchResult = getMatchResult(context, onClasses, filter);
if (!matchResult.isAllMatched()) {
String className = matchResult.getNotMatched().iterator().next();
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class").items(Style.QUOTE, matchResult.getMatched());
}
// 处理所有@ConditionalOnMissingClass注解
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
ClassNameFilter filter = ClassNameFilter.PRESENT;
MatchResult matchResult = getMatchResult(context, onMissingClasses, filter);
if (!matchResult.isAllMatched()) {
String className = matchResult.getMatched().iterator().next();
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class").items(Style.QUOTE, className));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class").items(Style.QUOTE, matchResult.getNotMatched());
}
return ConditionOutcome.match(matchMessage);
}
private MatchResult getMatchResult(ConditionContext context, List<String> candidates, ClassNameFilter filter) {
ClassLoader classLoader = context.getClassLoader();
MatchResult matchResult = new MatchResult();
for (String candidate : candidates) {
try {
// 检查类是否存在
filter.matches(candidate, classLoader);
matchResult.recordMatched(candidate);
} catch (Throwable ex) {
matchResult.recordNotMatched(candidate);
}
}
return matchResult;
}
}
类加载过程对Spring Boot应用的启动速度有显著影响,主要体现在以下几个方面:
Spring Boot的自动配置和组件扫描机制需要扫描整个类路径,查找符合条件的类和配置。类路径越大,扫描时间越长。例如,ClassPathScanningCandidateComponentProvider
类负责扫描类路径:
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 检查是否符合包含和排除过滤器
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
Spring框架大量使用反射来创建和管理bean。反射调用比直接方法调用慢得多,特别是在启动阶段需要创建大量bean时:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// 如果有自定义的实例化策略,使用它
if (bd.getMethodOverrides().isEmpty()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
// 使用反射获取构造函数
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
// 使用反射创建实例
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// 使用带方法覆盖的实例化策略
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
每个类在第一次使用时需要进行初始化,包括执行静态代码块、静态变量初始化等。如果类路径中有大量类,特别是包含复杂静态初始化逻辑的类,会显著增加启动时间。
针对类加载对Spring Boot启动速度的影响,可以采取以下优化策略:
通过以下方式减少需要扫描的类路径:
限制@ComponentScan
和@EntityScan
的扫描范围:
@SpringBootApplication
@ComponentScan(basePackages = "com.example.app")
@EntityScan(basePackages = "com.example.app.entity")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
使用@Indexed
注解加速自动配置类的加载:
@Configuration
@Indexed
public class MyAutoConfiguration {
// 配置内容
}
Spring Boot的自动配置机制非常强大,但也可能引入不必要的配置类。可以通过以下方式优化:
使用@ConditionalOnClass
和@ConditionalOnMissingClass
等条件注解,确保只有在必要时才加载配置类:
@Configuration
@ConditionalOnClass(DataSource.class)
public class MyDataSourceAutoConfiguration {
// 数据源配置
}
排除不需要的自动配置类:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Java 10及以上版本支持类数据共享(Class-Data Sharing),可以显著减少类加载时间:
生成CDS归档文件:
java -Xshare:dump -XX:SharedClassListFile=classlist -XX:SharedArchiveFile=app-cds.jsa -cp your-application.jar
在启动时使用CDS归档文件:
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -jar your-application.jar
Spring Boot 3.0引入了 Ahead-of-Time (AOT) 编译技术,可以在构建时预先处理部分运行时代码,减少启动时的处理:
添加AOT依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aotartifactId>
<scope>providedscope>
dependency>
生成AOT处理文件:
mvn spring-boot:process-aot
使用预编译的AOT文件启动应用:
public static void main(String[] args) {
AotSpringApplication.run(MyApplication.class, args);
}
虽然Spring框架大量使用反射,但我们可以尽量减少自己代码中的反射使用:
合理安排类的初始化顺序,避免不必要的类加载和初始化:
Spring Boot的配置文件加载是启动过程中的重要环节,它允许开发者外部化应用配置,使应用可以在不同环境中以相同的代码运行。Spring Boot支持多种配置文件格式,包括properties、yaml和xml,并按照特定的顺序加载它们。
Spring Boot按照以下顺序加载配置文件,后加载的配置会覆盖先加载的配置:
@TestPropertySource
注解@SpringBootTest
注解的properties属性ServletConfig
初始化参数ServletContext
初始化参数java:comp/env
)System.getProperties()
)RandomValuePropertySource
提供的随机值application-{profile}.properties
和YAML变体)application-{profile}.properties
和YAML变体)application.properties
和YAML变体)application.properties
和YAML变体)@Configuration
类上的@PropertySource
注解SpringApplication.setDefaultProperties
指定)这个加载顺序确保了更具体的配置(如命令行参数)优先于更通用的配置(如打包在jar内部的配置文件)。
Spring Boot的配置文件加载主要由ConfigFileApplicationListener
类负责,它是一个ApplicationListener
,监听ApplicationEnvironmentPreparedEvent
事件:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
// 配置文件位置
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
// 配置文件名称
private static final String DEFAULT_NAMES = "application";
@Override
public void onApplicationEvent(ApplicationEvent event) {
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());
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// 创建配置文件属性源加载器
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
}
Loader
类负责实际的配置文件加载工作:
private class Loader {
private final ConfigurableEnvironment environment;
private final ResourceLoader resourceLoader;
private final PropertySources propertySources;
public Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
this.propertySources = environment.getPropertySources();
}
public void load() {
// 获取活动的配置文件
Set<String> activeProfiles = getActiveProfiles();
// 加载默认配置文件
loadDefaultProfiles(activeProfiles);
// 加载特定于配置文件的配置
for (String profile : activeProfiles) {
load(profile, this::getPositiveProfileFilter);
}
// 加载默认配置
load(null, this::getNegativeProfileFilter);
}
private void load(String profile, DocumentFilterFactory filterFactory) {
getSearchLocations().forEach(location -> {
boolean isFolder = location.endsWith("/");
Set<String> fileExtensions = getFileExtensions();
fileExtensions.forEach(extension -> {
if (isFolder) {
loadForFileExtension(location, profile, extension, filterFactory);
}
else {
load(location, profile, extension, filterFactory);
}
});
});
}
private void load(String location, String profile, String fileExtension, DocumentFilterFactory filterFactory) {
String fileName = location + (profile != null ? profile + "." : "") + DEFAULT_NAMES + "." + fileExtension;
Resource resource = this.resourceLoader.getResource(fileName);
if (resource.exists()) {
load(resource, profile, filterFactory);
}
}
private void load(Resource resource, String profile, DocumentFilterFactory filterFactory) {
try {
String name = "applicationConfig: [" + resource.getURI() + "]";
// 根据文件类型解析配置文件
List<Document> documents = loadDocuments(resource);
if (documents != null) {
DocumentFilter filter = filterFactory.getFilter(profile);
for (Document document : documents) {
if (filter.match(document)) {
addPropertySource(document.getPropertySource(), name);
}
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property source from resource '" + resource + "'", ex);
}
}
}
配置文件处理过程可能会对Spring Boot应用的启动速度产生显著影响,主要体现在以下几个方面:
配置文件的读取和解析需要消耗时间,特别是当配置文件较大或格式复杂时:
Spring Boot会将配置属性绑定到Java对象,这个过程涉及反射和类型转换:
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(bean, beanName);
return bean;
}
private void bind(Object bean, String beanName) {
ConfigurationProperties annotation = findAnnotation(bean, beanName, ConfigurationProperties.class);
if (annotation != null) {
bind(bean, beanName, annotation);
}
}
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
try {
// 绑定配置属性到bean
binder.bind(annotation.prefix(), BeanReference.of(bean, beanName));
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean.getClass(), annotation, ex);
}
}
}
配置文件的数量和位置也会影响启动速度:
针对配置文件处理对启动速度的影响,可以采取以下优化策略:
尽量合并配置文件,避免分散的配置文件导致的额外加载开销:
虽然yaml文件更具可读性,但properties文件的解析速度通常更快。对于大型配置文件,考虑使用properties格式:
# application.properties
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret
合理安排配置文件的位置,减少搜索范围:
config/
目录下,避免多个搜索位置spring.config.location
属性明确指定配置文件位置,减少搜索开销@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setDefaultProperties(Collections.singletonMap("spring.config.location", "classpath:/custom-config/"));
app.run(args);
}
}
对于不变的配置,可以考虑使用缓存机制减少重复加载:
@Component
public class ConfigCache {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
private final Environment environment;
@Autowired
public ConfigCache(Environment environment) {
this.environment = environment;
}
public Object getConfigValue(String key) {
return cache.computeIfAbsent(key, k -> environment.getProperty(k));
}
}
对于非关键配置,可以考虑延迟加载,而不是在启动时加载:
@Component
public class LazyConfigLoader {
private Map<String, Object> config;
@Autowired
private Environment environment;
public synchronized Map<String, Object> getConfig() {
if (config == null) {
// 延迟加载配置
config = loadConfig();
}
return config;
}
private Map<String, Object> loadConfig() {
Map<String, Object> result = new HashMap<>();
// 从环境中加载配置
return result;
}
}
对于复杂的配置文件,可以在构建时进行预处理,生成简化的配置文件:
对于微服务架构,可以考虑使用配置服务器(如Spring Cloud Config)集中管理配置:
Spring Boot的条件配置机制允许根据特定条件加载不同的配置,这可以进一步优化启动过程:
通过@ConditionalOnProperty
注解,可以根据配置属性的值有条件地加载配置类:
@Configuration
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
public class FeatureConfig {
// 配置内容
}
通过@ConditionalOnResource
注解,可以根据资源是否存在有条件地加载配置类:
@Configuration
@ConditionalOnResource(resources = "classpath:custom-feature.properties")
public class CustomFeatureConfig {
// 配置内容
}
通过@Profile
注解,可以根据激活的配置文件有条件地加载配置类:
@Configuration
@Profile("production")
public class ProductionConfig {
// 生产环境配置
}
@Configuration
@Profile("development")
public class DevelopmentConfig {
// 开发环境配置
}
通过实现Condition
接口,可以创建自定义条件:
public class MyCustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 根据条件判断是否加载配置
return context.getEnvironment().getProperty("myapp.custom.condition", Boolean.class, false);
}
}
@Configuration
@Conditional(MyCustomCondition.class)
public class CustomConfig {
// 配置内容
}
Spring Boot的自动配置是其核心特性之一,它允许根据类路径中的依赖、配置属性和其他条件自动配置Spring应用。自动配置大大减少了开发者需要编写的样板代码,提高了开发效率。
自动配置的核心组件包括:
@ConditionalOnClass
、@ConditionalOnMissingBean
等,用于控制自动配置的加载条件自动配置的加载流程可以概括为:
@SpringBootApplication
注解包含@EnableAutoConfiguration
注解,启用自动配置机制AutoConfigurationImportSelector
通过Spring的工厂加载机制从META-INF/spring.factories
文件中加载所有自动配置类下面是AutoConfigurationImportSelector
的核心代码:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 加载候选自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复项
configurations = removeDuplicates(configurations);
// 获取要排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查排除的配置类
checkExcludedClasses(configurations, exclusions);
// 移除要排除的配置类
configurations.removeAll(exclusions);
// 应用自动配置导入过滤器
configurations = getConfigurationClassFilter().filter(configurations);
// 发布自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 从META-INF/spring.factories加载自动配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
条件注解是自动配置的关键,它们允许根据特定条件决定是否加载某个配置类。常见的条件注解包括:
@ConditionalOnClass
:当类路径中存在指定类时条件成立@ConditionalOnMissingClass
:当类路径中不存在指定类时条件成立@ConditionalOnBean
:当容器中存在指定bean时条件成立@ConditionalOnMissingBean
:当容器中不存在指定bean时条件成立@ConditionalOnProperty
:当指定属性存在且具有特定值时条件成立@ConditionalOnResource
:当指定资源存在时条件成立@ConditionalOnWebApplication
:当应用是Web应用时条件成立@ConditionalOnNotWebApplication
:当应用不是Web应用时条件成立这些条件注解可以组合使用,形成复杂的条件判断逻辑。
自动配置机制虽然强大,但也可能对Spring Boot应用的启动速度产生负面影响,主要体现在以下几个方面:
自动配置需要扫描类路径,查找所有符合条件的配置类和组件。类路径越大,扫描时间越长。例如,Spring Boot会扫描META-INF/spring.factories
文件中的所有自动配置类:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
每个自动配置类都需要经过一系列条件判断,确定是否应该加载。这些条件判断可能涉及类路径检查、配置属性检查等,会增加启动时间:
public class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 处理所有@ConditionalOnClass注解
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
ClassNameFilter filter = ClassNameFilter.MISSING;
MatchResult matchResult = getMatchResult(context, onClasses, filter);
if (!matchResult.isAllMatched()) {
String className = matchResult.getNotMatched().iterator().next();
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class").items(Style.QUOTE, matchResult.getMatched());
}
// 处理所有@ConditionalOnMissingClass注解
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
ClassNameFilter filter = ClassNameFilter.PRESENT;
MatchResult matchResult = getMatchResult(context, onMissingClasses, filter);
if (!matchResult.isAllMatched()) {
String className = matchResult.getMatched().iterator().next();
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class").items(Style.QUOTE, className));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class").items(Style.QUOTE, matchResult.getNotMatched());
}
return ConditionOutcome.match(matchMessage);
}
private MatchResult getMatchResult(ConditionContext context, List<String> candidates, ClassNameFilter filter) {
ClassLoader classLoader = context.getClassLoader();
MatchResult matchResult = new MatchResult();
for (String candidate : candidates) {
try {
// 检查类是否存在
filter.matches(candidate, classLoader);
matchResult.recordMatched(candidate);
} catch (Throwable ex) {
matchResult.recordNotMatched(candidate);
}
}
return matchResult;
}
}
如果应用不需要某些自动配置类,但它们仍然被加载和处理,会浪费启动时间。例如,一个非Web应用可能不需要Web相关的自动配置,但如果没有明确排除,这些配置类仍然会被处理。
针对自动配置对启动速度的影响,可以采取以下优化策略:
通过@SpringBootApplication
注解的exclude
属性,可以排除不需要的自动配置类:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
WebMvcAutoConfiguration.class
})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
也可以通过配置属性排除自动配置类:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
在自定义自动配置类中,使用@Conditional
注解确保只有在必要时才加载配置:
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(name = "myapp.redis.enabled", havingValue = "true", matchIfMissing = true)
public class RedisAutoConfiguration {
// Redis配置
}
当需要控制自动配置类的加载顺序时,使用@AutoConfigurationBefore
和@AutoConfigurationAfter
注解:
@Configuration
@AutoConfigurationBefore(WebMvcAutoConfiguration.class)
public class MyWebMvcConfiguration {
// 在WebMvcAutoConfiguration之前加载的配置
}
当需要更精确地控制自动配置类的加载顺序时,使用@AutoConfigurationOrder
注解:
@Configuration
@AutoConfigurationOrder(Ordered.HIGHEST_PRECEDENCE)
public class MyHighPriorityConfiguration {
// 高优先级的配置
}
对于自定义starter或自动配置模块,优化META-INF/spring.factories
文件,只包含必要的自动配置类:
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration1,\
com.example.MyAutoConfiguration2
Spring Boot 3.0引入的AOT( Ahead-of-Time )编译技术可以预编译自动配置,减少启动时的处理:
添加AOT依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aotartifactId>
<scope>providedscope>
dependency>
生成AOT处理文件:
mvn spring-boot:process-aot
使用预编译的AOT文件启动应用:
public static void main(String[] args) {
AotSpringApplication.run(MyApplication.class, args);
}
Spring Boot提供了自动配置报告功能,可以帮助分析哪些自动配置被应用,哪些被排除:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
// 打印自动配置报告
AutoConfigurationReport report = AutoConfigurationReport.get(context.getBeanFactory());
System.out.println(report.getConditionEvaluationReport().getExclusions());
}
}
也可以通过启动参数启用自动配置报告:
java -jar myapp.jar --debug
当创建自定义自动配置时,可以采取以下策略优化启动速度:
减少自动配置类中的条件检查,只保留必要的条件。每个条件检查都会增加启动时间。
对于非关键组件,使用@Lazy
注解延迟初始化:
@Configuration
public class MyAutoConfiguration {
@Bean
@Lazy
public MyService myService() {
return new MyService();
}
}
在自动配置类中,避免复杂的初始化逻辑。复杂的初始化逻辑会增加启动时间。
在自动配置类中,优先使用@Import
注解导入组件,而不是使用@ComponentScan
进行扫描:
@Configuration
@Import({MyComponent1.class, MyComponent2.class})
public class MyAutoConfiguration {
// 配置内容
}
当提供默认实现时,使用@ConditionalOnMissingBean
注解,允许用户覆盖默认实现:
@Configuration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new DefaultMyService();
}
}
Spring Bean的创建是Spring框架的核心功能之一,也是Spring Boot启动过程中的重要环节。理解Bean的创建流程对于优化Spring Boot应用的启动速度至关重要。
Spring Bean的创建过程可以概括为以下几个基本步骤:
InitializingBean
接口的afterPropertiesSet
方法或@PostConstruct
注解的方法)DisposableBean
接口的destroy
方法或@PreDestroy
注解的方法)下面是Spring框架中Bean创建的核心源码,主要位于AbstractAutowireCapableBeanFactory
类中:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 记录创建Bean的开始时间
if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// 解析Bean的Class
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// 准备方法覆盖
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"Validation of method overrides failed", ex);
}
try {
// 给BeanPostProcessors一个机会来返回一个代理而不是目标Bean实例
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 真正创建Bean实例
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 创建BeanWrapper实例
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 实例化Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 允许后处理器修改合并后的Bean定义
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// 处理循环引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 初始化Bean实例
Object exposedObject = bean;
try {
// 填充Bean属性
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 初始化Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// 处理循环引用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// 注册DisposableBean
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
Bean的创建过程是Spring Boot启动过程中的主要耗时点之一,主要体现在以下几个方面:
Spring框架大量使用反射来创建和管理Bean。反射调用比直接方法调用慢得多,特别是在启动阶段需要创建大量Bean时:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// 如果有自定义的实例化策略,使用它
if (bd.getMethodOverrides().isEmpty()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
// 使用反射获取构造函数
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
// 使用反射创建实例
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// 使用带方法覆盖的实例化策略
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
每个Bean的创建都需要解析其依赖关系,如果依赖关系复杂,会增加启动时间:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
if (bw == null) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
}
else {
// 没有属性值,跳过填充
return;
}
}
// 给InstantiationAwareBeanPostProcessors一个机会来防止默认的属性填充
boolean continueWithPropertyPopulation = true;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false;
break;
}
}
}
}
if (!continueWithPropertyPopulation) {
return;
}
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
// 处理自动装配模式
int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// 根据名称自动装配
if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// 根据类型自动装配
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
if (hasInstAwareBpps || needsDepCheck) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
if (hasInstAwareBpps) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvs == null) {
return;
}
}
}
}
if (needsDepCheck) {
checkDependencies(beanName, mbd, filteredPds, pvs);
}
}
if (pvs != null) {
// 应用属性值
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
应用中定义的Bean数量越多,启动时间越长。此外,如果Bean的初始化逻辑复杂,也会显著增加启动时间。
Bean后处理器(如BeanPostProcessor
和InstantiationAwareBeanPostProcessor
)会在Bean的创建过程中被调用,如果后处理器逻辑复杂,会增加启动时间:
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
// 调用Aware接口方法
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 应用BeanPostProcessors的postProcessBeforeInitialization方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 调用初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
// 应用BeanPostProcessors的postProcessAfterInitialization方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
针对Bean创建对启动速度的影响,可以采取以下优化策略:
减少应用中定义的Bean数量是最直接的优化方法:
对于非关键Bean,使用@Lazy
注解延迟初始化,直到第一次使用时才创建:
@Service
@Lazy
public class MyService {
// 服务实现
}
构造函数注入有助于减少循环依赖,并使依赖关系更加明确:
@Service
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
// 服务方法
}
简化Bean之间的依赖关系,避免复杂的依赖图:
通过@Conditional
注解,根据条件决定是否创建Bean:
@Configuration
public class MyConfig {
@Bean
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
public MyFeature myFeature() {
return new MyFeature();
}
}
减少或优化Bean后处理器,避免在启动过程中执行耗时操作:
@Order
注解控制后处理器执行顺序,确保关键后处理器优先执行通过@Profile
注解,根据不同环境加载不同的Bean:
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public MyService myService() {
return new ProductionMyService();
}
}
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public MyService myService() {
return new DevelopmentMyService();
}
}
合理使用Bean的作用域,避免不必要的多例Bean:
@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // 默认单例,可以省略
public class MySingletonService {
// 单例服务实现
}
@Service
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class MyRequestScopedService {
// 请求作用域服务实现
}
对于复杂的Bean创建过程,使用工厂方法可以提高可读性和可维护性,同时可能减少启动时间:
@Configuration
public class MyConfig {
@Bean
public MyComplexBean myComplexBean() {
return MyComplexBeanFactory.create();
}
}
public class MyComplexBeanFactory {
public static MyComplexBean create() {
// 复杂的创建逻辑
return new MyComplexBean();
}
}
Spring Boot 3.0引入的AOT( Ahead-of-Time )编译技术可以预编译Bean定义,减少启动时的处理:
添加AOT依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aotartifactId>
<scope>providedscope>
dependency>
生成AOT处理文件:
mvn spring-boot:process-aot
使用预编译的AOT文件启动应用:
public static void main(String[] args) {
AotSpringApplication.run(MyApplication.class, args);
}
除了优化Bean的创建过程,还可以优化Bean的生命周期管理,进一步提高启动速度:
保持初始化方法简单,避免在初始化方法中执行耗时操作:
@Service
public class MyService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 简单的初始化逻辑
}
// 其他方法
}
对于简单的初始化逻辑,使用@PostConstruct
注解比实现InitializingBean
接口更简洁:
@Service
public class MyService {
@PostConstruct
public void init() {
// 初始化逻辑
}
// 其他方法
}
对于非关键的初始化逻辑,可以使用@PostConstruct
结合线程池延迟执行:
@Service
public class MyService {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@PostConstruct
public void init() {
// 关键初始化逻辑
// 非关键初始化逻辑延迟执行
taskExecutor.submit(this::nonCriticalInitialization);
}
private void nonCriticalInitialization() {
// 非关键初始化逻辑
}
// 其他方法
}
类似地,保持销毁方法简单,避免在销毁方法中执行耗时操作:
@Service
public class MyService implements DisposableBean {
@Override
public void destroy() throws Exception {
// 简单的销毁逻辑
}
// 其他方法
}
对于简单的销毁逻辑,使用@PreDestroy
注解比实现DisposableBean
接口更简洁:
@Service
public class MyService {
@PreDestroy
public void cleanup() {
// 销毁逻辑
}
// 其他方法
}
Spring的组件扫描是一种强大的功能,它允许Spring自动发现和注册应用中的组件(如@Component
、@Service
、@Repository
、@Controller
等注解标注的类)。组件扫描大大减少了手动配置Bean的工作量,但也可能成为Spring Boot应用启动速度的瓶颈。
组件扫描的基本原理是:Spring会扫描指定包及其子包下的所有类,检查它们是否带有特定的注解,如果有,则将它们注册为Spring Bean。组件扫描的核心类是ClassPathScanningCandidateComponentProvider
,它负责执行实际的扫描工作。
下面是组件扫描的核心代码:
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 检查是否符合包含和排除过滤器
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
组件扫描可以通过以下几种方式触发:
@ComponentScan注解:直接指定要扫描的包
@Configuration
@ComponentScan(basePackages = "com.example.app")
public class AppConfig {
// 配置内容
}
@SpringBootApplication注解:默认扫描主应用类所在的包及其子包
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
XML配置:通过
标签配置
<context:component-scan base-package="com.example.app" />
组件扫描支持使用过滤器来限制扫描范围:
例如:
@Configuration
@ComponentScan(
basePackages = "com.example.app",
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyCustomAnnotation.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyExcludedClass.class)
)
public class AppConfig {
// 配置内容
}
组件扫描虽然方便,但也可能对Spring Boot应用的启动速度产生负面影响,主要体现在以下几个方面:
如果扫描范围设置得过大,Spring需要检查更多的类,会显著增加启动时间。特别是在类路径包含大量无关类时,这种影响更为明显。
组件扫描需要遍历类路径下的所有文件,查找符合条件的类。这个过程涉及大量的I/O操作和文件解析,会消耗较多的时间。
对于扫描到的每个类,Spring需要解析其注解信息,判断是否为候选组件。注解解析也需要一定的时间,特别是对于复杂的注解配置。
在某些情况下,可能会出现重复扫描的问题,例如多个配置类指定了重叠的扫描范围,这会进一步增加启动时间。
针对组件扫描对启动速度的影响,可以采取以下优化策略:
最直接的优化方法是缩小组件扫描的范围,只扫描必要的包:
@SpringBootApplication(scanBasePackages = "com.example.app")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
或者使用scanBasePackageClasses
指定具体的类所在的包:
@SpringBootApplication(scanBasePackageClasses = {MyService.class, MyRepository.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
使用排除过滤器排除不需要扫描的类或包:
@Configuration
@ComponentScan(
basePackages = "com.example.app",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.app.internal.*"),
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyExcludedAnnotation.class)
}
)
public class AppConfig {
// 配置内容
}
使用包含过滤器只包含需要的类或包:
@Configuration
@ComponentScan(
basePackages = "com.example.app",
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyCustomAnnotation.class)
)
public class AppConfig {
// 配置内容
}
确保不同的配置类不会扫描相同的包,避免重复扫描:
// 错误示例:重复扫描
@Configuration
@ComponentScan(basePackages = "com.example.app")
public class Config1 {
// 配置内容
}
@Configuration
@ComponentScan(basePackages = "com.example.app")
public class Config2 {
// 配置内容
}
// 正确示例:拆分扫描范围
@Configuration
@ComponentScan(basePackages = "com.example.app.service")
public class ServiceConfig {
// 服务配置
}
@Configuration
@ComponentScan(basePackages = "com.example.app.repository")
public class RepositoryConfig {
// 仓库配置
}
在生产环境中,排除测试类和测试配置:
@Configuration
@ComponentScan(
basePackages = "com.example.app",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.app.test.*")
)
public class AppConfig {
// 配置内容
}
对于大型应用,可以使用Spring的@Indexed
注解加速组件扫描:
@Indexed
@Configuration
public class MyConfig {
// 配置内容
}
使用@Indexed
注解后,Spring会在编译时生成元数据索引,减少运行时的扫描开销。
Spring Boot 3.0引入的AOT( Ahead-of-Time )编译技术可以预编译组件扫描信息,减少启动时的处理:
添加AOT依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aotartifactId>
<scope>providedscope>
dependency>
生成AOT处理文件:
mvn spring-boot:process-aot
使用预编译的AOT文件启动应用:
public static void main(String[] args) {
AotSpringApplication.run(MyApplication.class, args);
}
对于已知的组件,可以使用@Import
注解直接导入,避免组件扫描:
@Configuration
@Import({MyService.class, MyRepository.class})
public class AppConfig {
// 配置内容
}
减少类路径中的不必要依赖,避免扫描无关的类:
为了更好地优化组件扫描,可以使用以下方法监控和分析组件扫描的性能:
Spring Boot Actuator提供了metrics
端点,可以监控组件扫描的性能:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
// 获取组件扫描相关的指标
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
meterRegistry.find("spring.beans.scanned").meters().forEach(meter -> {
System.out.println(meter.getId() + ": " + meter.measure());
});
}
}
启用Spring的调试日志,可以查看组件扫描的详细过程:
logging.level.org.springframework.core.type=DEBUG
logging.level.org.springframework.context.annotation=DEBUG
使用JProfiler、YourKit等分析工具,可以详细分析组件扫描的性能瓶颈:
Spring框架提供了强大的缓存抽象,允许开发者通过简单的注解将缓存功能集成到应用中。Spring缓存抽象不绑定到具体的缓存实现,支持多种缓存提供者,如Ehcache、Caffeine、Redis等。
Spring缓存抽象的核心注解包括:
Spring缓存抽象通过CacheManager
接口管理缓存提供者,常见的缓存管理器实现包括:
ConcurrentHashMap
作为缓存存储当应用使用Spring缓存注解时,Spring会在方法调用前后插入拦截器,根据注解的配置执行相应的缓存操作。例如,@Cacheable
注解的工作流程如下:
合理使用缓存可以显著提高应用的运行性能,但如果缓存配置不当,也可能对Spring Boot应用的启动速度产生负面影响:
某些缓存提供者(如Ehcache、Redis)在初始化时需要加载配置文件、建立连接等操作,如果配置不当,可能会增加启动时间。
如果应用配置了缓存预热(即在启动时预先加载数据到缓存中),这也会增加启动时间。
Spring缓存抽象使用AOP代理实现缓存功能,创建代理对象也需要一定的时间,特别是当应用中有大量方法使用缓存注解时。
针对缓存对启动速度的影响,可以采取以下优化策略:
根据应用需求选择合适的缓存提供者:
对于非关键的缓存,可以配置为延迟初始化,避免在启动时占用过多时间:
@Configuration
public class CacheConfig {
@Bean
@Lazy
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES));
return cacheManager;
}
}
合理配置缓存参数,避免过度初始化:
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("cache1", "cache2");
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(50) // 适当的初始容量
.maximumSize(200) // 合理的最大容量
.expireAfterAccess(30, TimeUnit.MINUTES)); // 合理的过期时间
return cacheManager;
}
}
尽量避免在启动时进行缓存预热,特别是对于大量数据的缓存:
@Service
public class MyService {
@Autowired
private CacheManager cacheManager;
// 避免在@PostConstruct中进行大量数据的缓存预热
@PostConstruct
public void init() {
// 只预热少量关键数据
cacheManager.getCache("criticalCache").put("key", "value");
}
// 在首次访问时加载大量数据
public List<Data> loadData() {
Cache cache = cacheManager.getCache("dataCache");
List<Data> data = cache.get("data", List.class);
if (data == null) {
data = fetchDataFromDatabase();
cache.put("data", data);
}
return data;
}
private List<Data> fetchDataFromDatabase() {
// 从数据库获取数据
return new ArrayList<>();
}
}
对于耗时的缓存操作,可以使用异步方式执行:
@Service
public class MyService {
@Autowired
private CacheManager cacheManager;
@Async
public CompletableFuture<Void> refreshCache() {
// 异步刷新缓存
Cache cache = cacheManager.getCache("dataCache");
List<Data> data = fetchDataFromDatabase();
cache.put("data", data);
return CompletableFuture.completedFuture(null);
}
@Cacheable("dataCache")
public List<Data> getData() {
// 从缓存获取数据
return fetchDataFromDatabase();
}
}
通过@Cacheable
、@CachePut
和@CacheEvict
注解的condition
属性,根据条件决定是否执行缓存操作:
@Service
public class MyService {
@Cacheable(value = "dataCache", condition = "#param > 10")
public Data getData(int param) {
// 只有当param > 10时才缓存结果
return fetchData(param);
}
private Data fetchData(int param) {
// 获取数据的方法
return new Data();
}
}
如果使用Redis作为缓存提供者,优化Redis连接配置:
@Configuration
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
// 优化连接池配置
LettucePoolingClientConfiguration poolingConfig = LettucePoolingClientConfiguration.builder()
.poolSize(10) // 适当的连接池大小
.maxIdle(5) // 最大空闲连接数
.minIdle(2) // 最小空闲连接数
.build();
return new LettuceConnectionFactory(config, poolingConfig);
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 缓存过期时间
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())); // 使用JSON序列化
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
对于需要高性能的应用,可以考虑使用多级缓存(如Caffeine + Redis):
@Configuration
public class MultiLevelCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 配置Caffeine本地缓存
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES);
// 配置Redis远程缓存
RedisCacheConfiguration redisConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 创建多级缓存管理器
CompositeCacheManager cacheManager = new CompositeCacheManager();
cacheManager.setCacheManagers(Arrays.asList(
new CaffeineCacheManager("localCache"),
RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisConfig).build()
));
cacheManager.setFallbackToNoOpCache(false);
return cacheManager;
}
}
为了确保缓存配置的合理性,需要对缓存进行监控和调优:
Spring Boot Actuator提供了缓存监控端点,可以查看缓存的使用情况:
management.endpoints.web.exposure.include=cacheManager,caches
访问/actuator/caches
可以查看所有缓存的信息。
通过分析缓存命中率,可以评估缓存的有效性:
@Service
public class MyService {
@Autowired
private CacheManager cacheManager;
public void analyzeCacheHitRate() {
Cache cache = cacheManager.getCache("dataCache");
if (cache instanceof CaffeineCache) {
com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache =
((CaffeineCache) cache).getNativeCache();
System.out.println("Cache hit rate: " + caffeineCache.stats().hitRate());
System.out.println("Cache miss rate: " + caffeineCache.stats().missRate());
}
}
}
根据业务需求和缓存使用情况,动态调整缓存策略:
@CachePut
及时更新缓存懒加载(Lazy Loading)是一种设计模式,它将对象的初始化延迟到实际需要使用该对象的时候。在Spring框架中,懒加载主要通过@Lazy
注解实现,它可以应用于Bean定义或组件类上。
@Lazy
注解可以应用在以下场景:
Bean定义:在@Bean
方法上使用,延迟该Bean的初始化
@Configuration
public class AppConfig {
@Bean
@Lazy
public MyService myService() {
return new MyService();
}
}
组件类:在@Component、@Service等组件类上使用,延迟该组件的初始化
@Service
@Lazy
public class MyService {
// 服务实现
}
依赖注入:在构造函数、字段或方法参数上使用,延迟注入依赖
@Service
public class MyClientService {
private final MyService myService;
@Autowired
public MyClientService(@Lazy MyService myService) {
this.myService = myService;
}
}
Spring通过代理模式实现懒加载。当一个Bean被标记为@Lazy
时,Spring会创建一个代理对象代替实际的Bean。这个代理对象会拦截所有方法调用,直到第一次真正需要使用该Bean时,才会初始化实际的Bean。
以下是简化的懒加载代理实现原理:
public class LazyProxyFactory {
public static Object createLazyProxy(Class<?> targetClass, BeanFactory beanFactory, String beanName) {
// 使用JDK动态代理或CGLIB创建代理
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetClass(targetClass);
// 添加方法拦截器
proxyFactory.addAdvice(new MethodInterceptor() {
private Object target;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 第一次调用时初始化实际的Bean
if (target == null) {
target = beanFactory.getBean(beanName);
}
// 委托给实际的Bean处理方法调用
return invocation.getMethod().invoke(target, invocation.getArguments());
}
});
return proxyFactory.getProxy();
}
}
懒加载对Spring Boot应用启动速度的影响主要体现在以下几个方面:
通过延迟初始化非关键Bean,可以显著减少应用启动时需要创建的Bean数量,从而加快启动速度。特别是对于那些初始化过程复杂或耗时的Bean,懒加载的效果更为明显。
应用上下文刷新阶段是Spring Boot启动过程中最耗时的阶段之一,减少启动时创建的Bean数量可以有效缩短这个阶段的时间。
虽然懒加载减少了启动时的Bean创建,但创建代理对象本身也需要一定的时间和资源。不过,代理创建的开销通常远小于实际Bean的初始化开销。
针对懒加载对启动速度的影响,可以采取以下优化策略:
分析应用中的Bean,识别出那些在启动时不需要立即初始化的非关键Bean,将它们标记为懒加载:
@Service
@Lazy
public class NonCriticalService {
// 非关键服务实现
}
除了逐个标记Bean为懒加载,还可以通过配置全局启用懒加载:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setLazyInitialization(true); // 全局启用懒加载
application.run(args);
}
}
或者通过配置文件:
spring.main.lazy-initialization=true
虽然全局启用懒加载可以显著提高启动速度,但可能会影响应用的运行时性能,因为首次访问懒加载的Bean时会有额外的初始化开销。因此,需要根据应用的实际情况平衡懒加载的使用。
当懒加载的Bean之间存在依赖关系时,可以使用@DependsOn
注解控制它们的初始化顺序:
@Service
@Lazy
@DependsOn("databaseInitializer")
public class MyService {
// 服务实现
}
@Service
public class DatabaseInitializer {
// 数据库初始化服务
}
对于那些包含复杂初始化逻辑的Bean,特别适合使用懒加载:
@Service
@Lazy
public class ComplexInitializationService {
private final Resource intensiveResource;
public ComplexInitializationService() {
// 复杂且耗时的初始化逻辑
this.intensiveResource = loadIntensiveResource();
}
private Resource loadIntensiveResource() {
// 加载大量资源或执行耗时操作
return new Resource();
}
// 服务方法
}
对于那些在应用生命周期中不经常使用的Bean,使用懒加载可以避免不必要的启动开销:
@Service
@Lazy
public class RarelyUsedService {
// 不常用的服务实现
}
对于需要在所有懒加载Bean初始化后执行的操作,可以实现SmartInitializingSingleton
接口:
@Service
public class PostInitializationProcessor implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
// 所有单例Bean(包括懒加载的Bean)初始化完成后执行的操作
}
}
使用懒加载时需要注意以下几点:
懒加载可以解决部分循环依赖问题,但并不能解决所有循环依赖。在设计应用时,仍然应该尽量避免循环依赖。
懒加载的Bean在首次访问时会有额外的初始化开销,可能会影响响应时间。对于关键路径上的操作,需要特别注意。
当懒加载的Bean同时使用AOP时,需要注意代理的嵌套问题。Spring会处理这种情况,但可能会增加一些复杂性。
懒加载的Bean的生命周期回调方法(如@PostConstruct
)会在首次使用时才被调用,而不是在启动时。
当懒加载的Bean与条件注解(如@ConditionalOnProperty
)结合使用时,需要确保条件判断在首次使用Bean时仍然有效。
Ahead-of-Time (AOT) 预编译是Spring Boot 3.0引入的一项重要技术,它通过在构建时预先处理部分运行时代码,将运行时的反射、动态代理等操作提前到编译时进行,从而显著提高应用的启动速度和运行时性能。
传统的Java应用使用Just-In-Time (JIT) 编译技术,即在运行时将字节码编译为机器码。而AOT预编译则是在构建时将Java代码直接编译为机器码或优化后的字节码,减少运行时的编译开销。
Spring Boot AOT的核心目标包括:
Spring Boot AOT预编译的工作原理主要包括以下几个方面:
在构建阶段,Spring Boot AOT插件会分析应用代码,收集元数据信息,包括: