Spring Boot利用dag加速Spring beans初始化

1.什么是Dag?

有向无环图(Directed Acyclic Graph),简称DAG,是一种有向图,其中没有从节点出发经过若干条边后再回到该节点的路径。换句话说,DAG中不存在环路。这种数据结构常用于表示并解决具有依赖关系的问题。

20201229164751920

DAG的特性

  • 首先,DAG中的节点可以有入度和出度。节点的入度是指指向该节点的边的数量,而节点的出度是指由该节点指向其他节点的边的数量。在DAG中,节点的入度可以是0或正整数,而出度可以是0或正整数,但不能同时为负数。
  • DAG的另一个重要性质是存在一个或多个拓扑排序。拓扑排序是DAG中节点的线性排列,满足任意一条有向边的起点在排序中都位于终点之前。可以使用深度优先搜索(DFS)或宽度优先搜索(BFS)算法来生成拓扑排序。

 

DAG的应用

  1. 任务调度
  2. 编译器优化
  3. 数据流分析
  4. 电路设计

2.如何加速Spring Bean初始化?

在Spring框架中进行DAG(有向无环图)分析以实现并行初始化,可以有效提升应用程序启动的性能。通常情况下,Spring应用程序的Bean是按依赖顺序初始化的,而通过DAG分析可以识别出哪些Bean之间没有依赖关系,并行初始化这些Bean可以减少启动时间。以下是实现思路:

1. 识别依赖关系,构建DAG

首先需要识别Spring Bean之间的依赖关系。可以通过@DependsOn注解、构造器注入或@Autowired等方式获取Bean依赖。具体步骤:

  • 遍历Spring上下文中的所有Bean定义。
  • 根据Bean的依赖关系构建DAG,节点代表Bean,边表示依赖关系。

Spring的ApplicationContext提供了getBeanDefinitionNames()方法可以列出所有的Bean,通过BeanDefinition可以分析出依赖。

2. 拓扑排序

通过拓扑排序(Topological Sorting)对DAG进行排序,以确保Bean按依赖顺序初始化。拓扑排序可以确定哪些Bean可以并行初始化,哪些Bean必须在某些Bean之后初始化。 使用算法如Kahn’s Algorithm或DFS找到所有没有依赖的Bean(入度为0的节点),这些节点可以并行初始化。

3. 并行初始化Bean

在完成拓扑排序后,使用多线程来并行初始化可以同时启动的Bean。可以使用Java的ExecutorService或类似的线程池机制来管理并发的Bean初始化过程。 步骤:

  • 针对所有入度为0的节点,启动一个线程来初始化它们。
  • 当某个Bean初始化完成后,减少它所依赖的其他Bean的入度值。
  • 当某个Bean的入度为0时,可以在另一个线程中启动它的初始化。

4. Spring Integration

可以通过BeanFactoryPostProcessorApplicationContextInitializer来挂钩到Spring的初始化流程中,分析Bean之间的依赖关系,并将并行化初始化逻辑集成到Spring容器的启动过程中。 具体方法:

  • 实现BeanFactoryPostProcessor,在Bean初始化之前分析Bean的依赖并构建DAG。
  • 在Bean初始化阶段,使用多线程并行处理独立的Bean。

3.代码工程

整体的依赖关系如下:

layer

实验目标

实现自定义Bean并行化加载

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.1</version></parent><modelVersion>4.0.0</modelVersion><artifactId>dag</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.jgrapht</groupId><artifactId>jgrapht-core</artifactId><version>1.5.1</version></dependency></dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><fork>true</fork><failOnError>false</failOnError></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><forkCount>0</forkCount><failIfNoTests>false</failIfNoTests></configuration></plugin></plugins></pluginManagement></build>
</project>

Bean初始化

package com.et.config;import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedAcyclicGraph;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;@Component
public class DAGBeanInitializer implements BeanFactoryPostProcessor {private final ExecutorService executorService = Executors.newFixedThreadPool(10);@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);beanDefinitionMap.put(beanName, beanDefinition);}// build DAGDirectedAcyclicGraph<String, DefaultEdge> dag = buildDAG(beanDefinitionMap,beanFactory);// bean layersList<Set<String>> layers = getBeansByLayer(dag);System.out.println("layers:"+layers);// init bean by layersinitializeBeansInLayers(layers, beanFactory);}// DAG Beanprivate DirectedAcyclicGraph<String, DefaultEdge> buildDAG(Map<String, BeanDefinition> beanDefinitionMap, ConfigurableListableBeanFactory beanFactory) {DependencyResolver resolver = new DependencyResolver(beanFactory);DirectedAcyclicGraph<String, DefaultEdge> dag = new DirectedAcyclicGraph<>(DefaultEdge.class);for (String beanName : beanDefinitionMap.keySet()) {if(shouldLoadBean(beanName)) {dag.addVertex(beanName);String[] dependencies = beanDefinitionMap.get(beanName).getDependsOn();if (dependencies != null) {for (String dependency : dependencies) {dag.addEdge(dependency, beanName); }}// get @Autowired dependenciesSet<String> autowireDependencies = resolver.getAllDependencies(beanName);for (String autowireDependency : autowireDependencies) {// convert beanNameString autowireBeanName = convertToBeanName(autowireDependency);dag.addVertex(autowireBeanName);dag.addEdge(autowireBeanName, beanName);}}}return dag;}private String convertToBeanName(String className) {String simpleName = className.substring(className.lastIndexOf('.') + 1);return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);}private List<Set<String>> getBeansByLayer(DirectedAcyclicGraph<String,DefaultEdge> dag) {List<Set<String>> layers = new ArrayList<>();Map<String, Integer> inDegree = new HashMap<>();Queue<String> queue = new LinkedList<>();// init all nodes degreefor (String vertex : dag) {int degree = dag.inDegreeOf(vertex);inDegree.put(vertex, degree);if (degree == 0) {queue.offer(vertex);  //zero degree as the first layer}}// BFS process everyLayerswhile (!queue.isEmpty()) {Set<String> currentLayer = new HashSet<>();int size = queue.size();for (int i = 0; i < size; i++) {String currentBean = queue.poll();currentLayer.add(currentBean);// iterator layersfor (String successor : getSuccessors(dag,currentBean)) {inDegree.put(successor, inDegree.get(successor) - 1);if (inDegree.get(successor) == 0) {queue.offer(successor);  // add next layer when the degress is zero}}}layers.add(currentLayer);}return layers;}// get next nodeprivate Set<String> getSuccessors(DirectedAcyclicGraph<String, DefaultEdge> dag, String vertex) {// get outgoingEdgesSet<DefaultEdge> outgoingEdges = dag.outgoingEdgesOf(vertex);// find the next nodereturn outgoingEdges.stream().map(edge -> dag.getEdgeTarget(edge)).collect(Collectors.toSet());}// init beans by layerprivate void initializeBeansInLayers(List<Set<String>> layers, ConfigurableListableBeanFactory beanFactory) {for (Set<String> layer : layers) {// Beans of the same layer can be initialized in parallelList<CompletableFuture<Void>> futures = new ArrayList<>();for (String beanName : layer) {// only load beans that  wrote by yourselfif (shouldLoadBean(beanName)) {CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {beanFactory.getBean(beanName);  // init Bean} catch (Exception e) {System.err.println("Failed to initialize bean: " + beanName);e.printStackTrace();}}, executorService);futures.add(future);}}//Wait for all beans in the current layer to be initialized before initializing the next layer.CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));allOf.join();  // make sure to be done on current layer}}private boolean shouldLoadBean(String beanName) {return beanName.startsWith("helloWorldController")||beanName.startsWith("serviceOne")||beanName.startsWith("serviceTwo")||beanName.startsWith("serviceThree");}
}

获取bean@Autowired依赖

package com.et.config;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;@Component
public class DependencyResolver {private final ConfigurableListableBeanFactory beanFactory;@Autowiredpublic DependencyResolver(ConfigurableListableBeanFactory beanFactory) {this.beanFactory = beanFactory;}public Set<String> getAllDependencies(String beanName) {Set<String> dependencies = new HashSet<>();// get Bean definiteBeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);// reflecttry {Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());Field[] fields = beanClass.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {dependencies.add(field.getType().getName()); }}} catch (ClassNotFoundException e) {e.printStackTrace();}return dependencies;}
}

controller

package com.et.controller;import com.et.service.ServiceTwo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.et.service.*;
import java.util.HashMap;
import java.util.Map;@RestController
public class HelloWorldController {@AutowiredServiceOne ServiceOne;@AutowiredServiceTwo ServiceTwo;@RequestMapping("/hello")public Map<String, Object> showHelloWorld(){Map<String, Object> map = new HashMap<>();map.put("msg", "HelloWorld");return map;}
}

service

package com.et.service;import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceOne {private   void  sayhi(){System.out.println("this is service one sayhi");}
}
package com.et.service;import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceThree {private   void  sayhi(){System.out.println("this is service three sayhi");}
}
package com.et.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceTwo {@AutowiredServiceThree serviceThree;private   void  sayhi(){System.out.println("this is service two sayhi");}
}

只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • https://github.com/Harries/springboot-demo(dag)

4.测试

启动Spring Boot工程,查看bean加载顺序如下

2024-09-20T15:51:27.081+08:00 INFO 33188 --- [ main] com.et.DemoApplication : Starting DemoApplication using Java 17.0.9 with PID 33188 (D:\IdeaProjects\ETFramework\dag\target\classes started by Dell in D:\IdeaProjects\ETFramework)
2024-09-20T15:51:27.085+08:00 INFO 33188 --- [ main] com.et.DemoApplication : No active profile set, falling back to 1 default profile: "default"
layers:[[serviceOne, serviceThree], [serviceTwo], [helloWorldController]]
2024-09-20T15:51:28.286+08:00 INFO 33188 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8088 (http)
2024-09-20T15:51:28.297+08:00 INFO 33188 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-09-20T15:51:28.297+08:00 INFO 33188 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2024-09-20T15:51:28.373+08:00 INFO 33188 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-09-20T15:51:28.374+08:00 INFO 33188 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1198 ms
2024-09-20T15:51:28.725+08:00 INFO 33188 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8088 (http) with context path ''
2024-09-20T15:51:28.732+08:00 INFO 33188 --- [ main] com.et.DemoApplication

5.引用

  • JGraphX User Manual
  • Spring Boot利用dag加速Spring beans初始化 | Harries Blog™

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/879824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度解读MySQL意向锁的工作原理机制与应用场景

意向锁 意向锁的概念 意向锁是InnoDB自动添加的一种锁&#xff0c;不需要用户去干预。 是数据库中的一种表级锁&#xff0c;一个事务要给一个资源加锁时&#xff0c;必须要先获取到对应类型的意向锁之后&#xff0c;才可以给这个资源加上自己想要的共享锁或者排他锁&#xff0…

STM32F407单片机编程入门(十) IWDG独立看门狗详解及实战含源码

文章目录 一.概要二.独立看门狗介绍1.独立看门狗基本介绍2.独立看门狗功能描述3.独立看门狗复位时间 三.CubeMX配置一个独立看门狗IWDG例程四.CubeMX工程源代码下载五.小结 一.概要 什么是单片机看门狗 看门狗&#xff08;WDT&#xff09;是一个定时器&#xff0c;开启看门狗定…

C++ 常用设计模式

1、单例模式 一个类只有一个实例&#xff0c;提供一个全局访问点来访问这个实例。 分为懒汉模式和饿汉模式&#xff1a; 懒汉模式就是 只有用到这个实例才会初始化对象并返回 &#xff08;调用了对外的接口才实例化对象&#xff09;饿汉模式就是 不管用不用得到&#xff0c;都…

Unity3D 小案例 像素贪吃蛇 02 蛇的觅食

Unity3D 小案例 像素贪吃蛇 第二期 蛇的觅食 像素贪吃蛇 食物生成 在场景中创建一个 2D 正方形&#xff0c;调整颜色&#xff0c;添加 Tag 并修改为 Food。 然后拖拽到 Assets 文件夹中变成预制体。 创建食物管理器 FoodManager.cs&#xff0c;添加单例&#xff0c;可以设置…

【VitualBox】VitualBox的网络模式+网络配置

VirtualBox 1. 简介 VirtualBox 是一款开源虚拟机软件&#xff0c;使用者可以在VirtualBox上安装并且执行Solaris、Windows、DOS、Linux、OS/2 Warp、BSD等系统作为客户端操作系统。 2. 六种网络接入模式 VirtualBox提供了多种网络接入模式&#xff0c;他们各有优缺点&#xf…

YOLOv8改进 | 自定义数据集训练 | AirNet助力YOLOv8检测

目录 一、本文介绍 二、AirNet原理介绍 2.1 对比基降解编码器&#xff08;CBDE&#xff09; 2.2 降解引导修复网络&#xff08;DGRN&#xff09; 三、yolov8与AirNet结合修改教程 3.1 核心代码文件的创建与添加 3.1.1 AirNet.py文件添加 3.1.2 __init__.py文件添加 3…

【qt】一个WPS项目了解qt界面设计的基本套路

项目功能演示: 放心食用!最后有完整代码. 超级详细,期待您的一个点赞❥(^_-) 一览全局: WPS项目目录 一.创建项目二.导入资源三.ui设计四.字号选择框初始化五.滚动条初始化六.添加自定义文本类七.初始化action状态八.新建文档九.打开文件十.保存与另存为十一.打印/打印预览十…

vue 入门一

参考&#xff1a;丁丁的哔哩哔哩 1.使用vue 1.1 使用CDN的方式使用Vue mount和<div id"counter">关联起来 1.2 vue中的createApp import { createApp } from "vue"; import App from "./App.vue"; createApp(App).mount("#app&qu…

PyTorch使用------自动微分模块

目录 &#x1f354; 梯度基本计算 1.1 单标量梯度的计算 1.2 单向量梯度的计算 1.3 多标量梯度计算 1.4 多向量梯度计算 1.5 运行结果&#x1f4af; &#x1f354; 控制梯度计算 2.1 控制不计算梯度 2.2 注意: 累计梯度 2.3 梯度下降优化最优解 2.4 运行结果&#x1…

数字工厂管理系统与MES系统在实际应用中有哪些区别

随着制造业的数字化转型步伐加快&#xff0c;数字工厂管理系统与制造执行MES系统作为两大关键工具&#xff0c;在实际应用中展现出了明显的差异。本文将从实际应用的角度&#xff0c;详细探讨这两种系统之间的主要区别。 数字工厂管理系统的实际应用 数字工厂管理系统侧重于对…

掌握Spring Boot数据库集成:用JPA和Hibernate构建高效数据交互与版本控制

在现代应用开发中&#xff0c;数据库操作是核心环节之一。Spring Boot提供了简化数据库集成的强大工具&#xff0c;而JPA&#xff08;Java Persistence API&#xff09;和Hibernate是两种非常流行的ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;可以帮助我们将对象…

如何删除EXCELL文件中的空行?

1&#xff0c;选择某一列 2&#xff0c;点击《开始》《查找和选择》>《定位条件》&#xff0c;调出《定位条件》的选择框&#xff1b; 3&#xff0c;在定位条件选项框&#xff0c;选择《空值》&#xff1b; 4&#xff0c;找到变灰被选中的某一行&#xff0c;右击《删除》 5&…

GitLab权限及设置

之前很少关注这些&#xff0c;项目的权限&#xff0c;一般由专门的管理人员设置。 但自己创建的项目自己可以设置权限。下面是一些笔记。 GitLab中用户权限_gitlab 权限-CSDN博客 开发中遇到要将自己这块的代码上传到Git&#xff0c;由其他组的同事拉取后继续开发。上传代码后…

JavaScript 笔记汇总

JavaScript 笔记汇总 引入方式 内部方式 通过 script 标签包裹 JavaScript 代码。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JavaScript 基础 - 引入方式</title> </head> <…

SpringBoot Kafka发送消息与接收消息实例

前言 Kafka的基本工作原理 我们将消息的发布&#xff08;publish&#xff09;称作 producer(生产者)&#xff0c;将消息的订阅&#xff08;subscribe&#xff09;表述为 consumer&#xff08;消费者&#xff09;&#xff0c;将中间的存储阵列称作 broker(代理)&#xff0c;这…

QT打开摄像头采集

QT打开摄像头采集 今天好不容易把opencv的环境装好&#xff0c;然后想学习一下人脸识别的功能&#xff0c;但是在图书馆坐了4个多小时了&#xff0c;屁股疼就先写个摄像头采集的功能&#xff0c;明天继续学习吧&#xff0c;废话不多&#xff0c;嚼个奶片开始发车&#xff01;&…

JVM java主流的追踪式垃圾收集器

目录 前言 分代垃圾收集理论 标记清除算法 标记复制算法 标记整理法 前言 从对象消亡的角度出发, 垃圾回收器可以分为引用计数式垃圾收集和追踪式垃圾收集两大类, 但是java主流的一般是追踪式的垃圾收集器, 因此我们重点讲解. 分代垃圾收集理论 分代收集这种理…

Linux Vim编辑器常用命令

目录 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令 注&#xff1a;本章内容全部基于Centos7进行操作&#xff0c;查阅本章节内容前请确保您当前所在的Linux系统版本&#xff0c;且具有足够的权限执行操作。 一、命令模式快捷键 二…

企业专用智能云盘 | 帮助企业便捷管控企业文档 | 天锐绿盘云文档安全管理系统

由于当前多数企业内部的办公文件普遍散落于各员工电脑中&#xff0c;导致存在诸多潜在的文档使用风险。为优化团队协作效率&#xff0c;天 锐 绿盘是一款集文档统一管理、高效协同于一体的企业云盘&#xff0c;帮助企业解决文档管理中的诸多难题。 【地址&#xff1a;点击了解天…

【2023工业异常检测文献】SimpleNet

SimpleNet:ASimpleNetworkforImageAnomalyDetectionandLocalization 1、Background 图像异常检测和定位主要任务是识别并定位图像中异常区域。 工业异常检测最大的难题在于异常样本少&#xff0c;一般采用无监督方法&#xff0c;在训练过程中只使用正常样本。 解决工业异常检…