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.rhkb.cn/news/428087.html

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

相关文章

elasticsearch同步mysql方案

文章目录 1、1. 使用数据库触发器2. 使用定时任务3. 监听MySQL二进制日志&#xff08;binlog&#xff09;4. 使用数据管道5. 使用第三方工具或服务6. 编写自定义脚本注意事项 2、1. 使用Logstash步骤&#xff1a;示例配置&#xff1a; 2. 使用Debezium步骤&#xff1a; 3. 自定…

【Redis入门到精通三】Redis核心数据类型(List,Set)详解

目录 Redis数据类型 ​编辑 1.List类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 2.Set类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 Redis数据类型 查阅Redis官方文档可知&#xff0c;Redis提供给用户的核…

JavaScript - Document文档操作

1. 前言 ​​​​​​​ 编写网页时&#xff0c;我们需要时刻操作文档进而完成我们想要的效果。这就是通过文档对象模型实现&#xff0c;使用Document对象控制HTML以及样式信息的API 2. Document的树结构 在了解Document文档对象模型之前&#xff0c;我们先了解Dom的树结构 …

使用scp命令从本地往服务器传输文件失败

解决办法&#xff1a; 找到这个文件&#xff0c;打开&#xff0c;将里面的服务器ip对应的一行数据删掉即可。

(c语言+数据结构链表)项目:贪吃蛇

目录 1.项目背景 2.游戏效果演⽰ 3. ⽬标 4. 技术要点 5. Win32 API介绍 5.1 Win32 API 5.2 控制台程序 5.3 控制台屏幕上的坐标COORD 5.4 GetStdHandle 5.5 GetConsoleCursorInfo 5.5.1 CONSOLE_CURSOR_INFO 5.6 SetConsoleCursorInfo 5.7 SetConsoleCursorPositi…

d3dcompiler47dll丢失怎么解决,详细介绍6种解决方案

在电脑使用过程中&#xff0c;我们可能会遇到各种问题&#xff0c;其中之一就是系统提示某个文件缺失。其中&#xff0c;d3dcompiler_47.dll是许多用户经常遇到的问题之一。这个文件是DirectX组件的一部分&#xff0c;如果缺失&#xff0c;可能会导致游戏或应用程序无法正常运行…

Qt/C++ 多线程同步机制详解及应用

在多线程编程中&#xff0c;线程之间共享资源可能会导致数据竞争和不一致的问题。因此&#xff0c;采用同步机制确保线程安全至关重要。在Qt/C中&#xff0c;常见的同步机制有&#xff1a;互斥锁&#xff08;QMutex、std::mutex&#xff09;、信号量&#xff08;QSemaphore&…

Ansbile-变量

文章目录 一、Ansible的常量&#xff08;内置的变量&#xff09;有哪些&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1…

vulnhub(11):derpnstink(hydra爆破用户名和密码、验证的文件上传)

端口 nmap主机发现 nmap -sn 192.168.159.120/24 ​ Nmap scan report for 192.168.159.120 Host is up (0.00020s latency). ​ 120是新出现的机器&#xff0c;他就是靶机 nmap端口扫描 nmap -Pn 192.168.159.120 -p- --min-rate 10000 -oA nmap/scan 扫描开放端口保存到 nma…

【论文笔记】Are Large Kernels Better Teacheres than Transformers for ConvNets

Abstract 本文提出蒸馏中小核ConvNet做学生时&#xff0c;与Transformer相比&#xff0c;大核ConvNet因其高效的卷积操作和紧凑的权重共享&#xff0c;使得其做教师效果更好&#xff0c;更适合资源受限的应用。 用蒸馏从Transformers蒸到小核ConvNet的效果并不好&#xff0c;原…

图的应用(关键路径)

基于你设计的带权有向无环图&#xff0c;写出所有合法的关键路径&#xff0c;并算出关键路径总长度 文字描述&#xff1a;关键路径总长度的现实意义是什么&#xff1f; 1.关键路径 总长度454316 2.现实意义 从源点到汇点的所有路径中&#xff0c;具有最大路径长度的路径称…

好的头戴式降噪耳机一定很贵吗?四款热门头戴耳机盘点及推荐!

在快节奏的现代生活中&#xff0c;噪音无处不在&#xff0c;它常常干扰着我们的工作、学习与休闲时光。而一款高性价比的降噪蓝牙耳机&#xff0c;就如同一个贴心的伙伴&#xff0c;能为我们营造出一片宁静的听觉空间。如今&#xff0c;耳机市场蓬勃发展&#xff0c;想要好的头…

Broadcast:Android中实现组件及进程间通信

目录 一&#xff0c;Broadcast和BroadcastReceiver 1&#xff0c;简介 2&#xff0c;广播使用 二&#xff0c;静态注册和动态注册 三&#xff0c;无序广播和有序广播 1&#xff0c;有序广播的使用 2&#xff0c;有序广播的截断 3&#xff0c;有序广播的信息传递 四&am…

如何在GitHub上克隆仓库:HTTPS、SSH和GitHub CLI的区别

GitHub是开发者的天堂&#xff0c;提供了丰富的工具和功能来管理代码和项目。在克隆GitHub仓库时&#xff0c;你可能会遇到三种常见的方法&#xff1a;HTTPS、SSH和GitHub CLI。每种方法都有其独特的优势和适用场景。本文将深入探讨这三种克隆方式的区别&#xff0c;帮助你选择…

C++--类的实例化

一、实例化的概念 用类类型在屋里内存中创建对象的过程&#xff0c;称为类实例化出对象 类是对对象进行一种抽象描述&#xff0c;是一个模型一样的东西&#xff0c;限定了类有哪些成员变量&#xff0c;这些成员变量只是声明&#xff0c;没有分配空间&#xff0c;用类实例化出…

java项目之编程训练系统源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的编程训练系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 编程训练系统的主要使用者管…

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)

文章目录 前言一、ArkTS基本介绍1、 ArkTS组成2、组件参数和属性2.1、区分参数和属性的含义2.2、父子组件嵌套 二、装饰器语法1.State2.Prop3.Link4.Watch5.Provide和Consume6.Observed和ObjectLink代码示例&#xff1a;示例1&#xff1a;&#xff08;不使用Observed和ObjectLi…

未来通信抢先看!遨游通讯2024年中国国际信息通信展亮点剧透

2024年中国国际信息通信展览会将于9月25日-27日在北京国家会议中心举行&#xff0c;本届展会以“推动数实深度融合&#xff0c;共筑新质生产力”为主题。在通信技术日新月异的今天&#xff0c;卫星通信、人工智能、低碳节能等技术理念正引领着通信行业迈向新的高度。遨游通讯作…

【漏洞复现】Casbin casdoor static 任意文件读取漏洞

漏洞描述 Casdoor 是 Casbin 开源社区推出的基于 OAuth 2.0 / OIDC 的中心化的单点登录(SSO)身份验证平台。 Casdoor static 存在任意文件读取漏洞,攻击者通过发送特殊的请求包可以获取服务器中的敏感文件。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵…

Linux C# DAY3

作业 1、 #!/bin/bash mkdir -p ~/dir/dir1 mkdir ~/dir/dir2 cp ./* ~/dir/dir1 cp ./*.sh ~/dir/dir2 cd ~/dir/ tar -cvJf dir2.tar.xz ./dir2 mv dir2.tar.xz ~/dir/dir1/ cd ~/dir/dir1/ tar -xvf dir2.tar.xz 2、 #!/bin/bash head -5 /etc/group | tail -1 sudo mkdi…