SpringBoot Bean指定初始化顺序详解

转自:https://blog.csdn.net/zzhongcy/article/details/109504563

最近遇到SpringBoot容器外类初始化依赖容器内bean的问题,由于容器内bean初始化有一定顺序,网上查了查资料,这里记录一下。

  1. 前言

本文将介绍几种可行的方式来控制 bean 之间的加载顺序

  • @Order指明顺序
  • @AutoConfigureOrder
  • 构造方法依赖
  • @DependOn 注解
  • BeanPostProcessor 扩展

  • @Order和@AutoConfigureOrder说明

1.1. 错误姿势

下面我们会介绍两种典型注解的错误使用姿势,一个@Order,一个@AutoConfigureOrder

1.1.1 @Order

err.case1: 类上添加 Order 注解

结论:Sprintboot默认自动扫描,order 值的大小,与指定 bean 之间的初始化顺序无关。

一种常见的错误观点是在类上添加这个 Order 注解,就可以指定 bean 之间的初始化顺序,order 值越小,则优先级越高,接下来我们实际测试一下,是否如此

我们创建两个 DemoBean, 指定不同的 Order 顺序

1 @Order(4)
 2 @Component
 3 publicclass BaseDemo1 {
 4     private String name = "base demo 1";
 5
 6     public BaseDemo1() {
 7         System.out.println(name);
 8     }
 9 }
10
11 @Order(3)
12 @Component
13 publicclass BaseDemo2 {
14     private String name = "base demo 2";
15
16     public BaseDemo2() {
17         System.out.println(name);
18     }
19 }

根据前面的观点,order值小的优先级高,那么 BaseDemo2 应该先被初始化,实际测试一下,输出如下

SpringBoot Bean指定初始化顺序详解

err.case2: 配置类中 Bean 声明方法上添加@Order

结论:通过@Bean注解扫描,order 值的大小,与指定 bean 之间的初始化顺序无关。

Bean 除了上面的自动扫描之外,还有一种方式就是通过@Bean注解,下面我们演示一下在配置类中指定 bean 加载顺序的错误 case

同样我们新建两个测试 bean

1 publicclass BaseDemo3 {
 2     private String name = "base demo 3";
 3
 4     public BaseDemo3() {
 5         System.out.println(name);
 6     }
 7 }
 8
 9 publicclass BaseDemo4 {
10     private String name = "base demo 4";
11
12     public BaseDemo4() {
13         System.out.println(name);
14     }
15 }

接下来在配置类中定义 bean

1 @Configuration
 2 publicclass ErrorDemoAutoConf {
 3     @Order(2)
 4     @Bean
 5     public BaseDemo3 baseDemo3() {
 6         returnnew BaseDemo3();
 7     }
 8
 9     @Order(1)
10     @Bean
11     public BaseDemo4 baseDemo4() {
12         returnnew BaseDemo4();
13     }
14 }

同样的,如果 @Order注解有效,那么 BaseDemo4应该先被初始化

SpringBoot Bean指定初始化顺序详解

从上面的实际测试输出可以看出,@Order 注解在上面的方式中也不生效,如果有兴趣的同学可以试一下,将上面配置类中的两个方法的顺序颠倒一下,会发现BaseDemo4先加载

err.case3: @Order 注解修饰配置类

结论:通过@Order 注解是用来指定配置类的加载顺序,初始化顺序与@Order大小值无关。

这也是一种常见的错误 case,认为@Order 注解是用来指定配置类的加载顺序的,然而真的是这样么?

我们创建两个测试的配置类

1 @Order(1)
 2 @Configuration
 3 publicclass AConf {
 4     public AConf() {
 5         System.out.println("AConf init!");
 6     }
 7 }
 8
 9 @Order(0)
10 @Configuration
11 publicclass BConf {
12     public BConf() {
13         System.out.println("BConf init");
14     }
15 }

如果@Order 注解生效,那么 BConf 配置类会优先初始化,那么我们实测一下

SpringBoot Bean指定初始化顺序详解

从上面的结果可以看出,并不是 BConf 先被加载;当然这种使用姿势,实际上和第一种错误 case,并没有什么区别,配置类也是 bean,前面不生效,这里当然也不会生效。

那么是不是我们的理解不对导致的呢,实际上这个@Order放在配置类上之后,是这个配置类中定义的 Bean 的优先于另一个配置类中定义的 Bean 呢?

同样的我们测试下这种 case,我们定义三个 bean,两个 conf

1 publicclass Demo1 {
 2     private String name = "conf demo bean 1";
 3
 4     public Demo1() {
 5         System.out.println(name);
 6     }
 7 }
 8
 9 publicclass Demo2 {
10     private String name = "conf demo bean 2";
11
12     public Demo2() {
13         System.out.println(name);
14     }
15 }
16
17 publicclass Demo3 {
18     private String name = "conf demo bean 3";
19
20     public Demo3() {
21         System.out.println(name);
22     }
23 }

然后我们将 Demo1, Demo3 放在一个配置中,Demo2 放在另外一个配置中

1 @Order(2)
 2 @Configuration
 3 publicclass AConf1 {
 4     @Bean
 5     public Demo1 demo1() {
 6         returnnew Demo1();
 7     }
 8
 9     @Bean
10     public Demo3 demo3() {
11         returnnew Demo3();
12     }
13 }
14
15 @Order(1)
16 @Configuration
17 publicclass BConf1 {
18
19     @Bean
20     public Demo2 demo2() {
21         returnnew Demo2();
22     }
23 }

如果@Order 注解实际上控制的是配置类中 Bean 的加载顺序,那么 BConf1 中的 Bean 应该优先加载,也就是说 Demo2 会优先于 Demo1, Demo3,实际测试一下,输出如

SpringBoot Bean指定初始化顺序详解

上面的输出结果和我们预期的并不一样,所以@Order注解来决定配置类的顺序也是不对的

1.1.2. @AutoConfigureOrder

从命名来看,这个注解是用来指定配置类的顺序的,然而对于这个注解的错误使用也是非常多的,而大多的错误使用在于没有真正的了解到它的使用场景

接下来我们来演示一下错误的使用 case

在工程内新建两个配置类,直接使用注解

1 @Configuration
 2 @AutoConfigureOrder(1)
 3 publicclass AConf2 {
 4     public AConf2() {
 5         System.out.println("A Conf2 init!");
 6     }
 7 }
 8
 9 @Configuration
10 @AutoConfigureOrder(-1)
11 publicclass BConf2 {
12     public BConf2() {
13         System.out.println("B conf2 init!");
14     }
15 }

当注解生效时,BConf 会优先级加载

SpringBoot Bean指定初始化顺序详解

从输出结果来看,和我们预期的不一样;那么这个注解是不是作用于配置类中的 Bean 的顺序,而不是配置类本身呢?

同样的我们设计一个 case 验证一下

1 publicclass DemoA {
 2     private String name = "conf demo bean A";
 3
 4     public DemoA() {
 5         System.out.println(name);
 6     }
 7 }
 8
 9 publicclass DemoB {
10     private String name = "conf demo bean B";
11
12     public DemoB() {
13         System.out.println(name);
14     }
15 }
16
17 publicclass DemoC {
18     private String name = "conf demo bean C";
19
20     public DemoC() {
21         System.out.println(name);
22     }
23 }

对应的配置类

1 @Configuration
 2 @AutoConfigureOrder(1)
 3 publicclass AConf3 {
 4     @Bean
 5     public DemoA demoA() {
 6         returnnew DemoA();
 7     }
 8
 9     @Bean
10     public DemoC demoC() {
11         returnnew DemoC();
12     }
13 }
14
15 @Configuration
16 @AutoConfigureOrder(-1)
17 publicclass BConf3 {
18
19     @Bean
20     public DemoB demoB() {
21         returnnew DemoB();
22     }
23 }

如果 DemoB 后被加载,则说明上面的观点是错误的,实测结果如下

SpringBoot Bean指定初始化顺序详解

所以问题来了,@AutoConfigureOrder这个注解并不能指定配置类的顺序,还叫这个名,干啥?存粹是误导人不是!!!

接下来我们看一下@Order和@AutoConfigureOrder的正确使用方式

1.2. 使用说明

1.2.1. @Order

先看一下这个注解的官方注释

{@code @Order} defines the sort order for an annotated component. Since Spring 4.0, annotation-based ordering is supported for many kinds of components in Spring, even for collection injection where the order values of the target components are taken into account (either from their target class or from their {@code @Bean} method). While such order values may influence priorities at injection points, please be aware that they do not influence singleton startup order which is an orthogonal concern determined by dependency relationships and {@code @DependsOn} declarations (influencing a runtime-determined dependency graph).

最开始 Order 注解用于切面的优先级指定;在 4.0 之后对它的功能进行了增强, @Order 支持集合的注入时,指定集合中 bean 的顺序。

并且特别指出了,它对于单实例的 bean 之间的顺序,没有任何影响;这句话根据我们上面的测试也可以验证。

接下来我们需要看一下通过@Order 注解来注入集合时,指定顺序的场景。

首先我们定义两个 Bean 实现同一个接口,并添加上@Order注解。

1 publicinterface IBean {
 2 }
 3
 4 @Order(2)
 5 @Component
 6 publicclass AnoBean1 implements IBean {
 7
 8     private String name = "ano order bean 1";
 9
10     public AnoBean1() {
11         System.out.println(name);
12     }
13 }
14
15 @Order(1)
16 @Component
17 publicclass AnoBean2 implements IBean {
18
19     private String name = "ano order bean 2";
20
21     public AnoBean2() {
22         System.out.println(name);
23     }
24 }

然后在一个测试 bean 中,注入 IBean的列表,我们需要测试这个列表中的 Bean 的顺序是否和我们定义的 @Order规则一致

1 @Component
2 publicclass AnoTestBean {
3
4     public AnoTestBean(List anoBeanList) {
5         for (IBean bean : anoBeanList) {
6             System.out.println("in ano testBean: " + bean.getClass().getName());
7         }
8     }
9 }

根据我们的预期, anoBeanList 集合中,anoBean2 应该在前面

SpringBoot Bean指定初始化顺序详解

根据上面的输出,也可以看出列表中的顺序和我们预期的一致,并且 AnoOrderBean1与 AnoOrderBean2 的加载顺序和注解没有关系

1.2.2. @AutoConfigureOrder

这个注解用来指定配置文件的加载顺序,然而前面的测试中并没有生效,那么正确的使用姿势是怎样的呢?

@AutoConfigureOrder适用于外部依赖的包中 AutoConfig 的顺序,而不能用来指定本包内的顺序

为了验证上面的说法,我们再次新建两个工程,并指定自动配置类的顺序

工程一配置如下:

1 @AutoConfigureOrder(1)
2 @Configuration
3 @ComponentScan(value = {"com.git.hui.boot.order.addition"})
4 publicclass AdditionOrderConf {
5     public AdditionOrderConf() {
6         System.out.println("additionOrderConf init!!!");
7     }
8 }

注意自动配置类如要被正确加载,需要在工程的 /META-INF/spring.factories文件中定义

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.order.addition.AdditionOrderConf

工程二的配置如下:

1 @Configuration
2 @AutoConfigureOrder(-1)
3 @ComponentScan("com.git.hui.boot.order.addition2")
4 publicclass AdditionOrderConf2 {
5
6     public AdditionOrderConf2() {
7         System.out.println("additionOrderConf2 init!!!");
8     }
9 }

然后我们在项目内部添加一个配置

1 @AutoConfigureOrder(10)
2 @Configuration
3 publicclass OrderConf {
4     public OrderConf() {
5         System.out.println("inner order conf init!!!");
6     }
7 }

因为注解适用于外部依赖包中的自动配置类的顺序,所以上面三个配置类中,正确的话 AdditionOrderConf2 在 AdditionOrderConf1 之前;

而 OrderConf 并不会收到注解的影响,默认环境下,内部定义的配置类会优于外部依赖,从下面的输出也可以佐证我们说明(当然为了验证确实如此,还应该调整下两个外部工程配置类的顺序,并观察下加载顺序是否随之改变,我们这里省略掉了)

SpringBoot Bean指定初始化顺序详解

1.3 . 小结

本篇主要介绍了网上对@Order和@AutoConfigureOrder常见的错误使用姿势,并给出了正确的使用 case。

下面用简单的几句话介绍一下正确的姿势

  • @Order注解不能指定 bean 的加载顺序,它适用于 AOP 的优先级,以及将多个 Bean 注入到集合时,这些 bean 在集合中的顺序。
  • @AutoConfigureOrder指定外部依赖的 AutoConfig 的加载顺序(即定义在/META-INF/spring.factories文件中的配置 bean 优先级),在当前工程中使用这个注解并没有什么鸟用。
  • 同样的 @AutoConfigureBefore和 @AutoConfigureAfter这两个注解的适用范围和@AutoConfigureOrder一样。

  • 初始化顺序指定

2.1. 构造方法依赖

这种可以说是最简单也是最常见的使用姿势,但是在使用时,需要注意循环依赖等问题

我们知道 bean 的注入方式之中,有一个就是通过构造方法来注入,借助这种方式,我们可以解决有优先级要求的 bean 之间的初始化顺序

比如我们创建两个 Bean,要求 CDemo2 在 CDemo1 之前被初始化,那么我们的可用方式

1 @Component
 2 public class CDemo1 {
 3
 4     private String name = "cdemo 1";
 5
 6     public CDemo1(CDemo2 cDemo2) {
 7         System.out.println(name);
 8     }
 9 }
10
11 @Component
12 public class CDemo2 {
13
14     private String name = "cdemo 1";
15
16     public CDemo2() {
17         System.out.println(name);
18     }
19 }

实测输出结果如下,和我们预期一致

SpringBoot Bean指定初始化顺序详解

虽然这种方式比较直观简单,但是有几个限制

  • 需要有注入关系,如 CDemo2 通过构造方法注入到 CDemo1 中,如果需要指定两个没有注入关系的 bean 之间优先级,则不太合适(比如我希望某个 bean 在所有其他的 Bean 初始化之前执行)
  • 循环依赖问题,如过上面的 CDemo2 的构造方法有一个 CDemo1 参数,那么循环依赖产生,应用无法启动

另外一个需要注意的点是,在构造方法中,不应有复杂耗时的逻辑,会拖慢应用的启动时间

2.2. @DependOn 注解

这是一个专用于解决 bean 的依赖问题,当一个 bean 需要在另一个 bean 初始化之后再初始化时,可以使用这个注解

使用方式也比较简单了,下面是一个简单的实例 case

1 @DependsOn("rightDemo2")
 2 @Component
 3 public class RightDemo1 {
 4     private String name = "right demo 1";
 5
 6     public RightDemo1() {
 7         System.out.println(name);
 8     }
 9 }
10
11 @Component
12 public class RightDemo2 {
13     private String name = "right demo 2";
14
15     public RightDemo2() {
16         System.out.println(name);
17     }
18 }

上面的注解放在 RightDemo1 上,表示 RightDemo1的初始化依赖于 rightDemo2这个 bean

SpringBoot Bean指定初始化顺序详解

在使用这个注解的时候,有一点需要特别注意,它能控制 bean 的实例化顺序,但是 bean 的初始化操作(如构造 bean 实例之后,调用 @PostConstruct注解的初始化方法)顺序则不能保证,比如我们下面的一个实例,可以说明这个问题

1 @DependsOn("rightDemo2")
 2 @Component
 3 public class RightDemo1 {
 4     private String name = "right demo 1";
 5
 6     @Autowired
 7     private RightDemo2 rightDemo2;
 8
 9     public RightDemo1() {
10         System.out.println(name);
11     }
12
13     @PostConstruct
14     public void init() {
15         System.out.println(name + " _init");
16     }
17 }
18
19 @Component
20 public class RightDemo2 {
21     private String name = "right demo 2";
22
23     @Autowired
24     private RightDemo1 rightDemo1;
25
26     public RightDemo2() {
27         System.out.println(name);
28     }
29
30     @PostConstruct
31     public void init() {
32         System.out.println(name + " _init");
33     }
34 }

注意上面的代码,虽然说有循环依赖,但是通过 @Autowired注解方式注入的,所以不会导致应用启动失败,我们先看一下输出结果

SpringBoot Bean指定初始化顺序详解

有意思的地方来了,我们通过@DependsOn注解来确保在创建RightDemo1之前,先得创建RightDemo2;

所以从构造方法的输出可以知道,先实例 RightDemo2, 然后实例 RightDemo1;

然后从初始化方法的输出可以知道,在上面这个场景中,虽然 RightDemo2 这个 bean 创建了,但是它的初始化代码在后面执行

题外话:
有兴趣的同学可以试一下把上面测试代码中的 @Autowired的依赖注入删除,即两个 bean 没有相互注入依赖,再执行时,会发现输出顺序又不一样

2.3. BeanPostProcessor

最后再介绍一种非典型的使用方式,如非必要,请不要用这种方式来控制 bean 的加载顺序

先创建两个测试 bean

1 @Component
 2 public class HDemo1 {
 3     private String name = "h demo 1";
 4
 5     public HDemo1() {
 6         System.out.println(name);
 7     }
 8 }
 9
10 @Component
11 public class HDemo2 {
12     private String name = "h demo 2";
13
14     public HDemo2() {
15         System.out.println(name);
16     }
17 }

我们希望 HDemo2 在 HDemo1 之前被加载,借助 BeanPostProcessor,我们可以按照下面的方式来实现

1 @Component
 2 public class DemoBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
 3     private ConfigurableListableBeanFactory beanFactory;
 4     @Override
 5     public void setBeanFactory(BeanFactory beanFactory) {
 6         if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
 7             throw new IllegalArgumentException(
 8                     "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
 9         }
10         this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
11     }
12
13     @Override
14     @Nullable
15     public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {
16         // 在bean实例化之前做某些操作
17         if ("HDemo1".equals(beanName)) {
18             HDemo2 demo2 = beanFactory.getBean(HDemo2.class);
19         }
20         return null;
21     }
22 }

请将目标集中在 postProcessBeforeInstantiation,这个方法在某个 bean 的实例化之前,会被调用,这就给了我们控制 bean 加载顺序的机会

SpringBoot Bean指定初始化顺序详解

看到这种骚操作,是不是有点蠢蠢欲动,比如我有个 bean,希望在应用启动之后,其他的 bean 实例化之前就被加载,用这种方式是不是也可以实现呢?

下面是一个简单的实例 demo,重写DemoBeanPostProcessor的postProcessAfterInstantiation方法,在 application 创建之后,就加载我们的 FDemo 这个 bean

1 @Override
 2 public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
 3     if ("application".equals(beanName)) {
 4         beanFactory.getBean(FDemo.class);
 5     }
 6
 7     return true;
 8 }
 9
10
11 @DependsOn("HDemo")
12 @Component
13 public class FDemo {
14     private String name = "F demo";
15
16     public FDemo() {
17         System.out.println(name);
18     }
19 }
20
21 @Component
22 public class HDemo {
23     private String name = "H demo";
24
25     public HDemo() {
26         System.out.println(name);
27     }
28 }

从下图输出可以看出, HDemo, FDemo的实例化顺序放在了最前面了

SpringBoot Bean指定初始化顺序详解

2.4. 小结
在小结之前,先指出一下,一个完整的 bean 创建,在本文中区分了两块顺序

实例化 (调用构造方法)
初始化 (注入依赖属性,调用@PostConstruct方法)
本文主要介绍了三种方式来控制 bean 的加载顺序,分别是

通过构造方法依赖的方式,来控制有依赖关系的 bean 之间初始化顺序,但是需要注意循环依赖的问题
@DependsOn注解,来控制 bean 之间的实例顺序,需要注意的是 bean 的初始化方法调用顺序无法保证
BeanPostProcessor 方式,来手动控制 bean 的加载顺序

Original: https://www.cnblogs.com/fnlingnzb-learner/p/16419884.html
Author: Boblim
Title: SpringBoot Bean指定初始化顺序详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/540901/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 基于LSM的Key-Value数据库实现初篇

    前篇文章对LSM的基本原理,算法流程做了简单的介绍,这篇文章将实现一个简单的 基于LSM算法的 迷你Key-Value数据库,结合上篇文章的理论与本篇文章的实践使之对LSM算法有更…

    Java 2023年6月16日
    094
  • 二叉树的遍历

    二叉树的遍历应用实例 前序遍历,中序遍历,后序遍历步骤 前序遍历 1.先输出当前节点2.如果当前节点的左子节点不为空,则递归前序遍历3.如果当前节点的右子节点不为空,则递归前序遍历…

    Java 2023年6月15日
    074
  • 线程池

    为什么要使用线程池?什么是线程池?字面意思(他是一种基于池化思想管理和使用线程的机制,他将多个线程预先存储在一个池子中) 池化思想应用: 内存池:预先申请内存,提升申请内存速度,减…

    Java 2023年6月9日
    082
  • 面试官:什么是脚手架?为什么需要脚手架?常用的脚手架有哪些?

    微服务本身是一种架构风格,也是指导组织构建软件的一系列最佳实践集合。然而,业务团队在拆分应用后,会产生更多细粒度服务,并面临这些服务在分布式网络环境中的复杂性。如何专心实现业务逻辑…

    Java 2023年5月29日
    074
  • GBase数据库对象相关操作

    create table 表名(列名 数据&am…

    Java 2023年6月9日
    081
  • java 二维码生成(可带图片)springboot版

    本文(2019年6月29日 飞快的蜗牛博客) 有时候,男人和女人是两个完全不同的世界,男人的玩笑和女人的玩笑也完全是两码事,爱的人完全不了解你,你也不要指望一个女人了解你,所以男的…

    Java 2023年6月16日
    099
  • 人生苦短,我用python之三

    HTTP协议及Requests库的方法 requests库的主要方法:requests.request()构造一个请求 requests.get()获取HTML网页的主要方法,对应…

    Java 2023年6月7日
    083
  • 动力节点-王妈妈Springboot教程(八)打包war

    *视频观看地址 8.1 打包war 1.创建了一个jsp应用 2.修改pom.xml 1)指定打包后的文件名称 2)指定jsp编译目录 3)执行打包是war 4)主启动类继承Spr…

    Java 2023年6月7日
    095
  • 从零玩转人脸识别之RGB人脸活体检测

    从零玩转RGB人脸活体检测 前言 本期教程人脸识别第三方平台为虹软科技,本文章讲解的是人脸识别RGB活体追踪技术,免费的功能很多可以自行搭配,希望在你看完本章课程有所收获。 Arc…

    Java 2023年6月9日
    072
  • [spring]spring的bean自动装配机制

    是spring满足bean依赖的一种方式 spring会在上下文中自动寻找,并自动给bean装配属性 spring的装配方式: (1)手动装配 在people类中依赖了cat和do…

    Java 2023年6月6日
    080
  • MySQL采用B+树作为索引的原因

    MySQL采用B+树作为索引的原因 1、MySQL的索引结构是如何查询的 在MySQL中,存储的数据记录都是持久化到磁盘中的,数据包含索引和记录,当MySQL查询数据时,由于索引也…

    Java 2023年6月8日
    0121
  • 获取类的子类

    原理: 1、扫描指定路劲下的JAVA文件 2、利用反射 package com.util; import java.nio.file.Files; import java.nio….

    Java 2023年6月6日
    078
  • OpenLDAP 服务端配置(一): 基本配置

    拷贝默认配置文件并修改权限 生成配置文件中 rootpw 项的密码(注意:密码改为自己想设的密码) 修改配置文件 /etc/openldap/slapd.conf 并按如下说明修改…

    Java 2023年5月30日
    060
  • 服务端高并发分布式架构演进之路

    1. 概述 本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了…

    Java 2023年5月30日
    077
  • 关于C语言的学习 01

    include void main() //定义主函数 { //main 函数体开始 函数声明部分 C语言的各种语句 } //main函数结束 代码输出HelloWorld! 例子…

    Java 2023年6月5日
    0100
  • nginx转发https协议

    内网需要访问github.com,并且是按照https://github.com这样的访问方式进行访问。 因为使用了npm install git+https://github.c…

    Java 2023年5月30日
    073
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球