SpringBoot利用dag加速Spring beans初始化的方法示例
1.什么是Dag?
有向無環(huán)圖(Directed Acyclic Graph),簡稱DAG,是一種有向圖,其中沒有從節(jié)點出發(fā)經(jīng)過若干條邊后再回到該節(jié)點的路徑。換句話說,DAG中不存在環(huán)路。這種數(shù)據(jù)結(jié)構(gòu)常用于表示并解決具有依賴關(guān)系的問題。
DAG的特性
- 首先,DAG中的節(jié)點可以有入度和出度。節(jié)點的入度是指指向該節(jié)點的邊的數(shù)量,而節(jié)點的出度是指由該節(jié)點指向其他節(jié)點的邊的數(shù)量。在DAG中,節(jié)點的入度可以是0或正整數(shù),而出度可以是0或正整數(shù),但不能同時為負(fù)數(shù)。
- DAG的另一個重要性質(zhì)是存在一個或多個拓?fù)渑判颉M負(fù)渑判蚴荄AG中節(jié)點的線性排列,滿足任意一條有向邊的起點在排序中都位于終點之前??梢允褂蒙疃葍?yōu)先搜索(DFS)或?qū)挾葍?yōu)先搜索(BFS)算法來生成拓?fù)渑判颉?/li>
DAG的應(yīng)用
- 任務(wù)調(diào)度
- 編譯器優(yōu)化
- 數(shù)據(jù)流分析
- 電路設(shè)計
2.如何加速Spring Bean初始化?
在Spring框架中進(jìn)行DAG(有向無環(huán)圖)分析以實現(xiàn)并行初始化,可以有效提升應(yīng)用程序啟動的性能。通常情況下,Spring應(yīng)用程序的Bean是按依賴順序初始化的,而通過DAG分析可以識別出哪些Bean之間沒有依賴關(guān)系,并行初始化這些Bean可以減少啟動時間。以下是實現(xiàn)思路:
1. 識別依賴關(guān)系,構(gòu)建DAG
首先需要識別Spring Bean之間的依賴關(guān)系??梢酝ㄟ^@DependsOn
注解、構(gòu)造器注入或@Autowired
等方式獲取Bean依賴。具體步驟:
- 遍歷Spring上下文中的所有Bean定義。
- 根據(jù)Bean的依賴關(guān)系構(gòu)建DAG,節(jié)點代表Bean,邊表示依賴關(guān)系。
Spring的ApplicationContext
提供了getBeanDefinitionNames()
方法可以列出所有的Bean,通過BeanDefinition
可以分析出依賴。
2. 拓?fù)渑判?/h3>
通過拓?fù)渑判颍═opological Sorting)對DAG進(jìn)行排序,以確保Bean按依賴順序初始化。拓?fù)渑判蚩梢源_定哪些Bean可以并行初始化,哪些Bean必須在某些Bean之后初始化。 使用算法如Kahn’s Algorithm或DFS找到所有沒有依賴的Bean(入度為0的節(jié)點),這些節(jié)點可以并行初始化。
3. 并行初始化Bean
在完成拓?fù)渑判蚝螅褂枚嗑€程來并行初始化可以同時啟動的Bean。可以使用Java的ExecutorService
或類似的線程池機制來管理并發(fā)的Bean初始化過程。 步驟:
- 針對所有入度為0的節(jié)點,啟動一個線程來初始化它們。
- 當(dāng)某個Bean初始化完成后,減少它所依賴的其他Bean的入度值。
- 當(dāng)某個Bean的入度為0時,可以在另一個線程中啟動它的初始化。
4. Spring Integration
可以通過BeanFactoryPostProcessor
或ApplicationContextInitializer
來掛鉤到Spring的初始化流程中,分析Bean之間的依賴關(guān)系,并將并行化初始化邏輯集成到Spring容器的啟動過程中。 具體方法:
- 實現(xiàn)
BeanFactoryPostProcessor
,在Bean初始化之前分析Bean的依賴并構(gòu)建DAG。 - 在Bean初始化階段,使用多線程并行處理獨立的Bean。
3.代碼工程
整體的依賴關(guān)系如下:
實驗?zāi)繕?biāo)
實現(xiàn)自定義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); @Override public 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 DAG DirectedAcyclicGraph<String, DefaultEdge> dag = buildDAG(beanDefinitionMap,beanFactory); // bean layers List<Set<String>> layers = getBeansByLayer(dag); System.out.println("layers:"+layers); // init bean by layers initializeBeansInLayers(layers, beanFactory); } // DAG Bean private 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 dependencies Set<String> autowireDependencies = resolver.getAllDependencies(beanName); for (String autowireDependency : autowireDependencies) { // convert beanName String 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 degree for (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 everyLayers while (!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 layers for (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 node private Set<String> getSuccessors(DirectedAcyclicGraph<String, DefaultEdge> dag, String vertex) { // get outgoingEdges Set<DefaultEdge> outgoingEdges = dag.outgoingEdgesOf(vertex); // find the next node return outgoingEdges.stream() .map(edge -> dag.getEdgeTarget(edge)) .collect(Collectors.toSet()); } // init beans by layer private void initializeBeansInLayers(List<Set<String>> layers, ConfigurableListableBeanFactory beanFactory) { for (Set<String> layer : layers) { // Beans of the same layer can be initialized in parallel List<CompletableFuture<Void>> futures = new ArrayList<>(); for (String beanName : layer) { // only load beans that wrote by yourself if (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; @Autowired public DependencyResolver(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; } public Set<String> getAllDependencies(String beanName) { Set<String> dependencies = new HashSet<>(); // get Bean definite BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); // reflect try { 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 { @Autowired ServiceOne ServiceOne; @Autowired ServiceTwo 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 { @Autowired ServiceThree serviceThree; private void sayhi(){ System.out.println("this is service two sayhi"); } }
只是一些關(guān)鍵代碼
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
以上就是SpringBoot利用dag加速Spring beans初始化的方法示例的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot dag加速beans初始化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
子類繼承父類時構(gòu)造函數(shù)相關(guān)問題解析
這篇文章主要介紹了子類繼承父類時構(gòu)造函數(shù)相關(guān)問題解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11springboot項目Redis統(tǒng)計在線用戶的實現(xiàn)示例
最近做個項目需要統(tǒng)計在線用戶,本文主要介紹了springboot項目Redis統(tǒng)計在線用戶的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2024-06-06使用spring的websocket創(chuàng)建通信服務(wù)的示例代碼
這篇文章主要介紹了使用spring的websocket創(chuàng)建通信服務(wù)的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11RocketMQ整合SpringBoot實現(xiàn)生產(chǎn)級二次封裝
本文主要介紹了RocketMQ整合SpringBoot實現(xiàn)生產(chǎn)級二次封裝,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06使用JPA中@Query 注解實現(xiàn)update 操作方法(必看)
下面小編就為大家?guī)硪黄褂肑PA中@Query 注解實現(xiàn)update 操作方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06java統(tǒng)計漢字字?jǐn)?shù)的方法示例
這篇文章主要介紹了java統(tǒng)計漢字字?jǐn)?shù)的方法,結(jié)合實例形式分析了java正則判定、字符串遍歷及統(tǒng)計相關(guān)操作技巧,需要的朋友可以參考下2017-05-05Spring?Cloud?Feign?使用對象參數(shù)的操作
這篇文章主要介紹了Spring?Cloud?Feign?如何使用對象參數(shù)的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02