当前位置: 首页 > news >正文

Spring 容器启动耗时统计

为了了解 Spring 为什么会启动那么久,于是看了看怎么统计一下加载 Bean 的耗时。

极简版

几行代码搞定。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;import java.util.HashMap;
import java.util.Map;public class SpringBeanAnalyse implements BeanPostProcessor {private static final Map<String, Long> mapBeanTime = new HashMap<>();@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {mapBeanTime.put(beanName, System.currentTimeMillis());return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {Long begin = mapBeanTime.get(beanName);if (begin != null) {long ell = System.currentTimeMillis() - begin;System.out.println(beanName + " 耗时: " + ell);}return bean;}
}

使用方法:

@Bean
SpringBeanAnalyse SpringBeanAnalyse() {return new SpringBeanAnalyse();
}

效果如图:
在这里插入图片描述
问题是没有排序,看比较费劲。

高配版

于是,高配版出场了。它更为成熟壮健,并有排序功能。

在这里插入图片描述

原理

Bean 启动时间抓取,主要是围绕 Spring Bean 生命周期。BeanPostProcessor 相关方法

  1. postProcessBeforeInstantiation: 实例化前
  2. postProcessAfterInstantiation: 实例化后
  3. postProcessBeforeInitialization: 初始化前
  4. postProcessAfterInitialization: 初始化后

注意:实现MergedBeanDefinitionPostProcessor, 主要是为了调整当前 BeanPostProcessor的执行顺序到最后, 具体参考BeanPostProcessor注册流程

org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors
org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors

,详见
在这里插入图片描述

源码

首先是一个 Bean。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
class Statistics {private String beanName;private long beforeInstantiationTime;private long afterInstantiationTime;private long beforeInitializationTime;private long afterInitializationTime;public long calculateTotalCostTime() {return calculateInstantiationCostTime() + calculateInitializationCostTime();}public long calculateInstantiationCostTime() {return afterInstantiationTime - beforeInstantiationTime;}public long calculateInitializationCostTime() {return afterInitializationTime - beforeInitializationTime;}public String toConsoleString() {return "\t" + getBeanName() + "\t" + calculateTotalCostTime() + "\t\n";}
}

StartupTimeMetric源码:

import com.ajaxjs.util.logger.LogHelper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.PriorityOrdered;import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;/*** 用于调优的处理器*/
public class StartupTimeMetric implements InstantiationAwareBeanPostProcessor, PriorityOrdered, ApplicationListener<ContextRefreshedEvent>, MergedBeanDefinitionPostProcessor {private static final LogHelper LOGGER = LogHelper.getLog(StartupTimeMetric.class);private final Map<String, Statistics> statisticsMap = new TreeMap<>();/*** InstantiationAwareBeanPostProcessor 中自定义的方法 在方法实例化之前执行 Bean 对象还没有*/@Overridepublic Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {String beanClassName = beanClass.getName();Statistics s = Statistics.builder().beanName(beanClassName).build();s.setBeforeInstantiationTime(System.currentTimeMillis());statisticsMap.put(beanClassName, s);return null;}/*** InstantiationAwareBeanPostProcessor 中自定义的方法 在方法实例化之后执行 Bean 对象已经创建出来了*/@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {String beanClassName = bean.getClass().getName();Statistics s = statisticsMap.get(beanClassName);if (s != null)s.setAfterInstantiationTime(System.currentTimeMillis());return true;}/*** BeanPostProcessor 接口中的方法 在 Bean 的自定义初始化方法之前执行 Bean 对象已经存在了*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {String beanClassName = bean.getClass().getName();Statistics s = statisticsMap.getOrDefault(beanClassName, Statistics.builder().beanName(beanClassName).build());s.setBeforeInitializationTime(System.currentTimeMillis());statisticsMap.putIfAbsent(beanClassName, s);return bean;}/*** BeanPostProcessor 接口中的方法 在 Bean 的自定义初始化方法执行完成之后执行 Bean 对象已经存在了*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {String beanClassName = bean.getClass().getName();Statistics s = statisticsMap.get(beanClassName);if (s != null)s.setAfterInitializationTime(System.currentTimeMillis());return bean;}@Overridepublic int getOrder() {return PriorityOrdered.HIGHEST_PRECEDENCE;}private static final AtomicBoolean START_LOCK = new AtomicBoolean(false);@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {LOGGER.info("Spring 容器启动完成");if (START_LOCK.compareAndSet(false, true)) {List<Statistics> sList = statisticsMap.values().stream().sorted(Comparator.comparing(Statistics::calculateTotalCostTime).reversed()).collect(Collectors.toList());StringBuilder sb = new StringBuilder();sList.forEach(_s -> sb.append(_s.toConsoleString()));LOGGER.info("ApplicationStartupTimeMetric:\n" + sb);}}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {}
}

参见《应用启动加速-并发初始化spring bean》

http://www.lryc.cn/news/137619.html

相关文章:

  • 1. 优化算法学习
  • 再获荣誉丨通付盾WAAP解决方案获“金鼎奖”优秀金融科技解决方案
  • 【腾讯云 TDSQL-C Serverless 产品测评】“橡皮筋“一样的数据库『MySQL高压篇』
  • python http文件上传
  • Android学习之路(9) Intent
  • vue项目配置git提交规范
  • 影响交叉导轨运行速度的因素有哪些?
  • List转Map
  • ES:一次分片设计问题导致的故障
  • vue 简单实验 自定义组件 综合应用 传参数 循环
  • 【OpenCV实战】2.OpenCV基本数据类型实战
  • MyBatis进阶:告别SQL注入!MyBatis分页与特殊字符的正确使用方式
  • 安装Node(脚手架)
  • R语言10-R语言中的循环结构
  • 【Spring】一次性打包学透 Spring | 阿Q送书第五期
  • 第 7 章 排序算法(4)(插入排序)
  • JavsScript知识框架
  • el-input添加自定义指令只允许输入中文/英文/数字,兼容输入法事件
  • 0基础学习VR全景平台篇 第89篇:智慧眼-安放热点
  • java中用SXSSFWorkbook把多个list数据和单个实体dto导出到excel如何导出到多个sheet页详细实例?(亲测)
  • SpringBoot 01 如何创建 和pom的解析
  • axios详解
  • Docker分布式仓库
  • SQL注入之万能用户名
  • ubuntu20搭建环境使用的一下指令
  • GAN(生成对抗网络)
  • 实时同步ES技术选型:Mysql+Canal+Adapter+ES+Kibana
  • 禅道后台命令执行漏洞
  • 基于Spark+django的国漫推荐系统--计算机毕业设计项目
  • 向量数据库 Milvus:实现高效向量搜索的技术解析