【问题标题】:Getting spring beans initialization times获取spring bean初始化时间
【发布时间】:2015-09-06 10:00:28
【问题描述】:

我有一个带有大型 spring 上下文的应用程序,它加载了许多开发人员编写的大量 bean。
一些 bean 可能会对其初始化代码进行一些重要的处理,这可能需要很长时间。
我正在寻找一种简单的方法来获取每个 bean 的加载时间。
由于该软件在大量客户的机器上运行,我需要一种方法来轻松地在日志中找到瓶颈 bean。
如果我可以注册诸如“加载 bean 之前”和之后的事件,那就太好了。
因此,如果我可以有问题地获取这些数据,我可以编写如下内容:

if (beanLoadingTime > 2 seconds) 
    print bean details and loading time to log file

这就是为什么启用日志记录或分析是不够的。

【问题讨论】:

    标签: java spring


    【解决方案1】:

    不知道我的解决方案是否会对您有所帮助,但这就是我所做的,因为我需要类似的东西。

    首先,我们需要记录两件事,实例化时间和初始化时间。首先,我只为包“org.springframework.beans.factory”启用日志记录,使用 %d{mm:ss,SSS} %m%n 作为模式(仅限时间和消息)。 Spring 记录消息,例如:正在创建 bean 实例...和已完成创建 bean 实例...对于第二件事,我按照 this answer 中的建议创建了一个 LoggerBeanPostProcessor。代码是:

    public class LoggerBeanPostProcessor implements BeanPostProcessor, Ordered {
    
        protected Log logger = LogFactory.getLog("org.springframework.beans.factory.LoggerBeanPostProcessor");
        private Map<String, Long> start;
        private Map<String, Long> end;
    
        public LoggerBeanPostProcessor() {
            start = new HashMap<>();
            end = new HashMap<>();
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            start.put(beanName, System.currentTimeMillis());
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            end.put(beanName, System.currentTimeMillis());
            logger.debug("Init time for " + beanName + ": " + initializationTime(beanName));
            return bean;
        }
    
        @Override
        public int getOrder() {
            return Integer.MAX_VALUE;
        }
    
        // this method returns initialization time of the bean.
        public long initializationTime(String beanName) {
            return end.get(beanName) - start.get(beanName);
        }
    }
    

    我在 log4j 配置中使用文件附加程序。然后我编写了一个简单的代码来解析该信息并获取每件事的毫秒数并将它们相加:

    public static void main(String[] argumentos) throws Exception{
            File file = new File("C:\\app\\daily.log");
            List<String> lines = FileUtils.readLines(file);
    
            Map<String,Long> start = new HashMap();
            Map<String,Long> end = new HashMap();
            Map<String,Long> init = new HashMap();
            List<String> beans = new ArrayList();
            int max = 0;
    
            for(String line :  lines) {
                String time = StringUtils.substring(line, 0, 9);
                String msg = StringUtils.substring(line, 10);
    
                if(msg.startsWith("Creating instance")) {
                    int fi = StringUtils.indexOf(msg, '\'') + 1;
                    int li = StringUtils.lastIndexOf(msg, '\'');
                    String bean = StringUtils.substring(msg, fi, li);
                    if(start.containsKey(bean)) {
                        continue;
                    }
                    start.put(bean, parseTime(time));
                    beans.add(bean);
                    max = Math.max(max, bean.length());
    
                } else if(msg.startsWith("Finished creating")) {
                    int fi = StringUtils.indexOf(msg, '\'') + 1;
                    int li = StringUtils.lastIndexOf(msg, '\'');
                    String bean = StringUtils.substring(msg, fi, li);
                    if(end.containsKey(bean)) {
                        continue;
                    }
                    end.put(bean, parseTime(time));
    
                } else if(msg.startsWith("Init time for")) {
                    int li = StringUtils.lastIndexOf(msg, ':');
                    String bean = StringUtils.substring(msg, 14, li);
                    if(init.containsKey(bean)) {
                        continue;
                    }
                    init.put(bean, Long.parseLong(StringUtils.substring(msg, li+2)));
                }
            }
    
            for(String bean : beans) {
                long s = start.get(bean);
                long e = end.get(bean);
                long i = init.containsKey(bean) ? init.get(bean) : -1;
                System.out.println(StringUtils.leftPad(bean, max) + ": " + StringUtils.leftPad(Long.toString((e-s)+i), 6, ' '));
            }
        }
    

    导致:

                                               splashScreen:    172
    org.springframework.aop.config.internalAutoProxyCreator:     31
                                    loggerBeanPostProcessor:   1137
                                                 appContext:   1122
    

    希望这对你有帮助,就像它对我有帮助一样。

    【讨论】:

    • 看到postProcessAfterInitialization 方法可以为同一个bean 调用多次。
    【解决方案2】:

    要查找 Java 代码中的性能瓶颈,请使用 profiler。

    分析器将测量被分析的每个方法所花费的时间,包括方法本身以及方法的总和加上它所做的每次调用。通常,在类或包级别启用它的分析,例如如果您的代码在 com.example 包或子包中,您指定它,并且分析器将监控您的代码,而不会浪费时间监控 Spring 代码和 Java 运行时库。

    根据您的 IDE,可能已经内置,也可能作为扩展/插件提供。

    更新

    要挂钩到 Spring 容器 bean 实例化过程,BeanPostProcessor 可能是解决方案。引用的描述包括以下内容:

    [...] 对于容器创建的每个 bean 实例,后处理器从容器中获取回调 before 容器初始化方法(例如 InitializingBean 的 afterPropertiesSet() 和任何声明的 init 方法)以及 在任何 bean 初始化回调之后调用。后处理器可以对 bean 实例执行任何操作,包括完全忽略回调。

    【讨论】:

    • 感谢您的回答,但分析无法解决我的问题(请参阅更新后的问题)
    • 所以您是说您没有可以分析的 QA 环境?由于您在谈论初始化时间,因此您不需要应用程序的实际负载,因此来自 QA 环境的值将类似于实际生产环境,当然 CPU 性能除外,但相对而言您可以识别有问题的bean。
    • 由于 bean 执行特定于设置的初始化,因此它们的性能可能会根据不同的设置而改变。当然,我们正在使用分析来识别许多问题,但我正在寻找一种额外的工具来识别问题。
    • 我想这样做,但将实例化时间作为在我们的构建管道期间运行的自动化测试包括在内。使用本机代码解决方案,而不是针对分析器运行会更合适。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-02
    • 2016-12-06
    • 1970-01-01
    • 1970-01-01
    • 2020-03-31
    • 2016-08-08
    • 1970-01-01
    相关资源
    最近更新 更多