Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题

这篇具有很好参考价值的文章主要介绍了Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是循环依赖问题

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于C、C依赖于A

通常来说,如果问Spring容器内部如何解决循环依赖问题,一定是指默认的单例Bean中,属性相互引用的场景。也就是说,Spring的循环依赖,是Spring容器注入时出现的问题。

示例

项目结构

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

项目代码

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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com</groupId>
    <artifactId>spring-circular-dependency</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.bundles</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8_2</version>
        </dependency>
    </dependencies>

</project>

Bean01.java

package com.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author honey
 * @date 2023-08-23 17:50:53
 */
@Component
public class Bean01 {

    @Autowired
    private Bean02 bean02;
}

Bean02.java

package com.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author honey
 * @date 2023-08-23 17:52:55
 */
@Component
public class Bean02 {

    @Autowired
    private Bean01 bean01;
}

SpringConfig01.java

package com.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author honey
 * @date 2023-08-23 17:59:37
 */
@Configuration
@ComponentScan(value = {"com.spring.bean"})
public class SpringConfig01 {
}

SpringTest01.java

package com.spring.test;

import com.spring.bean.Bean01;
import com.spring.bean.Bean02;
import com.spring.config.SpringConfig01;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author honey
 * @date 2023-08-23 18:00:24
 */
public class SpringTest01 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig01.class);
        Bean01 bean01 = applicationContext.getBean("bean01", Bean01.class);
        Bean02 bean02 = applicationContext.getBean("bean02", Bean02.class);
        System.out.println(bean01);
        System.out.println(bean02);
    }
}
运行结果

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Spring默认情况下已经解决了循环依赖问题(单例Bean)

@Async注解导致的问题

在SpringConfig01类上加上@EnableAsync注解

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

在Bean01类中使用@Async注解修饰的异步方法

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Bean01.java

package com.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * @author honey
 * @date 2023-08-23 17:50:53
 */
@Component
public class Bean01 {

    @Autowired
    private Bean02 bean02;

    @Async
    public void test() {
        System.out.println(Thread.currentThread().getName() + "Bean01测试中...");
    }
}

运行结果

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\lib\idea_rt.jar=57456:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\知识点资料(第四年)\Spring源码解读\代码\spring-circular-dependency\target\classes;D:\maven_jar\org\springframework\spring-core\5.2.1.RELEASE\spring-core-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-jcl\5.2.1.RELEASE\spring-jcl-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-beans\5.2.1.RELEASE\spring-beans-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-context\5.2.1.RELEASE\spring-context-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-aop\5.2.1.RELEASE\spring-aop-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-expression\5.2.1.RELEASE\spring-expression-5.2.1.RELEASE.jar;D:\maven_jar\org\aspectj\aspectjrt\1.8.9\aspectjrt-1.8.9.jar;D:\maven_jar\org\apache\geronimo\bundles\aspectjweaver\1.6.8_2\aspectjweaver-1.6.8_2.jar" com.spring.test.SpringTest01
八月 23, 2023 7:07:05 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean01': Bean with name 'bean01' has been injected into other beans [bean02] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean01': Bean with name 'bean01' has been injected into other beans [bean02] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
	at com.spring.test.SpringTest01.main(SpringTest01.java:15)

Process finished with exit code 1

Spring在扫描bean发现某个类方法被@Async修饰时,会通过后置处理器AsyncAnnotationBeanPostProcessor生成代理对象,而该后置处理器的顺序比处理AOP的后置处理器还靠后,因此会导致Spring处理不了循环依赖。

使用@Lazy注解解决@Async注解导致的问题

在Bean01类中循环依赖的属性上使用@Lazy注解

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

运行结果

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

开启Aop使用代理对象示例

项目结构

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

项目代码

AspectAop.java

package com.spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author honey
 * @date 2023-08-23 18:26:33
 */
@Component
@Aspect
public class AspectAop {

    @Pointcut("execution (* com.spring.bean.*.*(..))")
    public void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        System.out.println("doAround advice start");
        Object result = point.proceed();
        System.out.println("doAround advice end");
        return result;
    }
}

需要在Bean01和Bean02中加上一个普通方法,如:

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

package com.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author honey
 * @date 2023-08-23 17:52:55
 */
@Component
public class Bean02 {

    @Autowired
    private Bean01 bean01;

    public void add() {

    }
}

SpringConfig01.java

package com.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author honey
 * @date 2023-08-23 17:59:37
 */
@Configuration
@ComponentScan(value = {"com.spring.bean", "com.spring.aop"})
@EnableAspectJAutoProxy
public class SpringConfig01 {
}
运行结果

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Spring是如何解决循环依赖问题的

Spring底层通过三级缓存解决了循环依赖问题。

一级缓存:singletonObjects,也叫作单例池,存放已经经历了完整生命周期的Bean对象;

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

二级缓存:earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

三级缓存:singletonFactories,存放可以生成Bean的工厂;

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

原理

假设有两个对象分别是A和B,其中A依赖于B,B依赖于A

  1. 在创建A对象时需要B对象,于是将A对象存入三级缓存,再去创建B对象;
  2. 在创建B对象时发现需要A对象,于是先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A对象,然后把三级缓存里面的A对象存入二级缓存,并删除三级缓存里面的A对象;
  3. B对象创建完成,将B对象存入一级缓存(此时B对象依赖的A对象依然是创建中状态),获取到B对象后回来接着创建A对象,直到创建完成,并将A对象存入一级缓存中(如果其它对象依赖于A对象和B对象,可以直接从一级缓存里面获取);

源码解读


在创建A对象时,A对象完成实例化后,会将A对象封装成ObjectFactory存入三级缓存。

AbstractBeanFactory.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

AbstractAutowireCapableBeanFactory.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java
Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

ObjectFactory.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

如果该对象是单例对象且开启了循环依赖且该对象正在创建中,则会将该对象封装成ObjectFactory对象存入三级缓存。其中ObjectFactory是一个函数接口,在调用getObject()方法时,才会真正去执行lambda语句调用getEarlyBeanReference()方法。

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

DefaultSingletonBeanRegistry.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java


在为A对象设置属性时,发现A对象依赖于B对象,则会去尝试获取B对象,由于此时B对象还未被创建,所以B对象也会走和A对象一样的创建逻辑,不同的是在为B对象设置属性时,发现B对象依赖于A对象,可以从三级缓存中获取得到A对象。

AbstractBeanFactory.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

DefaultSingletonBeanRegistry.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

此时存入二级缓存的是不完整对象,调用singletonFactory.getObject()方法实际上是在调用getEarlyBeanReference(beanName, mbd, bean)方法。

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

如果是正常情况下,返回原始对象

InstantiationAwareBeanPostProcessorAdapter.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

如果是开启了Aop的情况下,返回Aop代理对象

AbstractAutoProxyCreator.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java


将获取到的A对象设置为B对象的属性并执行完B对象的完整生命周期后,将B对象存入一级缓存中并从二级缓存、三级缓存中移除。此处也使用了ObjectFactory这个函数接口。

AbstractBeanFactory.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java

DefaultSingletonBeanRegistry.java

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java
Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题,Spring,mr,spring,java


获取得到B对象后,接着执行A对象的生命周期,执行完成后和B对象一样存入一级缓存中。文章来源地址https://www.toymoban.com/news/detail-668023.html


什么情况下Spring无法解决循环依赖问题

  1. 构造器注入的循环依赖问题;
  2. 非单例对象的循环依赖问题;

到了这里,关于Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Mr. Cappuccino的第69杯咖啡——Oracle之存储过程

    概念 PLSQL是Oracle对sql语言的过程化扩展,指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力。 程序结构 PL/SQL可以分为三个部分:声明部分、可执行部分、异常处理部分。 其中DECLARE部分用来声明变量或游标(结果集类型变量),如果程序

    2024年01月17日
    浏览(48)
  • Mr. Cappuccino的第59杯咖啡——简单手写SpringIOC框架

    基于XML方式 项目结构 项目代码 pom.xml UserBean.java spring.xml SpringTest01.java 运行结果 基于注解方式 项目结构 项目代码 ScanBean.java SpringConfig.java SpringTest02.java 运行结果 核心原理 底层使用map集合管理对象,key=beanId,value=实例对象 基于XML方式 原理 基于反射+工厂模式+DOM技术 使用

    2024年02月13日
    浏览(41)
  • Mr. Cappuccino的第57杯咖啡——简单手写Mybatis大致原理

    底层基于JDK动态代理技术实现 pom.xml config.properties UserEntity.java UserMapper.java Insert.java JdbcUtils.java MapperProxy.java SqlSession.java MybatisTest.java 运行MybatisTest类 SqlSession.java MapperProxy.java MybatisTest.java MapperProxy.java MybatisTest.java 运行结果

    2024年02月14日
    浏览(66)
  • Mr. Cappuccino的第55杯咖啡——Mybatis一级缓存&二级缓存

    缓存越小,查询速度越快,缓存数据越少 缓存越大,查询速度越慢,缓存数据越多 在多级缓存中,一般常见的是先查询一级缓存,再查询二级缓存,但在Mybatis中是先查询二级缓存,再查询一级缓存。 在Mybatis中,BaseExecutor属于一级缓存执行器,CachingExecutor属于二级缓存执行

    2024年02月14日
    浏览(45)
  • Mr. Cappuccino的第68杯咖啡——基于Docker安装Oracle11g

    拉取镜像 以持久化的方式启动容器 关于持久化,source=oracle_vol指的是容器中的数据卷路径,target指的是容器外需要被挂载的目录路径。 查看volume的具体位置 修改配置文件 使用I键进入编辑模式,添加以下配置信息,再使用Esc键退出编辑模式,输入:wq保存配置信息。 检查配置

    2024年01月17日
    浏览(58)
  • Mr. Cappuccino的第58杯咖啡——MacOS配置Maven和Java环境

    如果使用的是bash,则使用以下命令 因为我这里使用的是zsh,所以使用以下命令 下载Maven Maven下载地址 配置前准备 使用command+shift+G进入/usr/local/目录 创建maven文件夹 将下载好的Maven压缩包解压 把解压后的文件复制到maven文件夹下面,并创建repo文件夹用来存放拉取的maven依赖

    2024年02月14日
    浏览(41)
  • (循环依赖问题)学习spring的第九天

     Bean实例的属性填充  Spring在属性注入时 , 分为如下几种情况 : 注入单向对象引用 : 如usersevice里注入userdao , userdao里没有注入其他属性     注入双向对象引用 : 如usersevice里注入userdao , userdao也注入usersevice属性  (搞清原理即可) 问题提出 : 形成死循环 解决问题 : 三级缓存解决

    2024年01月22日
    浏览(37)
  • Spring解决循环依赖问题

    例如,就是A对象依赖了B对象,B对象依赖了A对象。(下面的代码属于 属性的循环依赖 ,也就是初始化阶段的循环依赖,区别与底下 构造器的循环依赖 ) 问题来了: A Bean创建 —— 依赖了 B 属性 ——  触发 B Bean创建 ——  B 依赖了 A 属性 ——  需要 A Bean(但A Bean还在创建

    2024年02月12日
    浏览(45)
  • Spring如何解决循环依赖问题

    循环依赖问题在Spring中主要有三种情况: (1)通过构造方法进行依赖注入时产生的循环依赖问题。 (2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。 (3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。 在Spring中,只有第

    2024年02月06日
    浏览(46)
  • Spring怎么解决循环依赖问题?

    循环依赖是指 一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用 , 举个例子 : A 依赖B , B依赖C , C依赖A , 这样就形成了循环依赖;   ①构造器的循环依赖:这种依赖spring是处理不了的,直接拋出BeanCurrentlyInCreationException异常。 ②单例模式下的se

    2024年02月08日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包