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

news2024/11/20 9:32:36

Spring循环依赖问题

    • 什么是循环依赖问题
      • 示例
        • 项目结构
        • 项目代码
        • 运行结果
      • @Async注解导致的问题
      • 使用@Lazy注解解决@Async注解导致的问题
      • 开启Aop使用代理对象示例
        • 项目结构
        • 项目代码
        • 运行结果
    • Spring是如何解决循环依赖问题的
      • 原理
      • 源码解读
    • 什么情况下Spring无法解决循环依赖问题

什么是循环依赖问题

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

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

示例

项目结构

在这里插入图片描述

项目代码

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);
    }
}

运行结果

在这里插入图片描述

在这里插入图片描述

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

@Async注解导致的问题

在SpringConfig01类上加上@EnableAsync注解

在这里插入图片描述

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

在这里插入图片描述

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测试中...");
    }
}

运行结果

在这里插入图片描述

"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注解

在这里插入图片描述

运行结果

在这里插入图片描述

在这里插入图片描述

开启Aop使用代理对象示例

项目结构

在这里插入图片描述

项目代码

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中加上一个普通方法,如:

在这里插入图片描述

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 {
}

运行结果

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

AbstractAutowireCapableBeanFactory.java

在这里插入图片描述
在这里插入图片描述

ObjectFactory.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

在这里插入图片描述


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

AbstractBeanFactory.java

在这里插入图片描述

DefaultSingletonBeanRegistry.java

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

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

InstantiationAwareBeanPostProcessorAdapter.java

在这里插入图片描述

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

AbstractAutoProxyCreator.java

在这里插入图片描述


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

AbstractBeanFactory.java

在这里插入图片描述

DefaultSingletonBeanRegistry.java

在这里插入图片描述
在这里插入图片描述


获取得到B对象后,接着执行A对象的生命周期,执行完成后和B对象一样存入一级缓存中。


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

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

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

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

相关文章

nginx部署 vue配置代理服务器 解决跨域问题

为了演示方便使用的是windows部署 1.首先将vue打包 2.打包好的vue放入到nginx-1.24.0\html\下,这里我创建一个big-data文件夹所以放入到big-data方便多项目管理 3.打开nginx.conf的配置文件修改 server {listen 8081;server_name localhost;location /{alias html/big-data…

锐捷校园网使用指南

Linux ubantu linux客户端下载 进入到下载目录解压 进入解压后的文件目录&#xff0c;修改文件权限 开始使用&#xff0c;了解一些命令&#xff0c;查看帮助 连接有线网络 sudo ./rjsupplicant -u 你的校园网账号 -p 你的密码 -d 1 然后输入当前linux ip账户的密码&#xff0c…

19.图,图的两种存储结构

目录 一. 一些基本概念 二. 图的抽象数据类型定义 三. 图的存储结构 &#xff08;1&#xff09;数组表示法&#xff08;邻接矩阵表示法&#xff09; &#xff08;a&#xff09;邻接矩阵 &#xff08;b&#xff09;存储表示 &#xff08;c&#xff09;优缺点分析 &#x…

无限计算力:探索云计算的无限可能性

这里写目录标题 前言云计算介绍服务模型&#xff1a; 应用领域&#xff1a;云计算主要体现在生活中的地方云计算未来发展的方向 前言 云计算是一种基于互联网的计算模型&#xff0c;通过它可以实现资源的共享、存储、管理和处理。它已经成为许多个人、企业和组织的重要技术基础…

变压器绝缘油色谱分析试验

试验目的 分析油中溶解气体的组分和含量是监视充油设备安全运行的最有效措施之一。 该 方法适用于充有矿物质绝缘油和以纸或层压板为绝缘材料的电气设备。 对判断充油电 气设备内部故障有价值的气体包括: 氢气 (H2 )、 甲烷 (CH4 )、 乙烷 (C2 H6 )、 乙烯 (C2H4 )、 乙炔 (C2…

mybatis讲解(2)之动态SQL的运用

目录 经典面试题&#xff1a; 1.mybatis动态sql 2.模糊查询&#xff08;3种方式&#xff09; 3.查询返回结果集 总结&#xff1a; 前言&#xff1a;在我上篇已经学习了Mybatis简介以及如何去连接数据库&#xff0c;具有增删改查的方法。那么我们今天来学习Mybatis的第二节关…

苹果叶病害识别(Python代码,pyTorch框架,预训练好的VGG16模型,也很容易替换为其它模型,带有GUI识别界面)

代码运行要求&#xff1a;Torch>1.13.1即可 1.数据集介绍&#xff1a; Apple Scab类文件夹图片 Black Rot类文件夹图片 Cedar Apple Rust文件夹 healthy文件夹 2.整个项目 data文件夹存放的是未被划分训练集和测试集的原始照片 picture文件夹存放的是经hf.py对data文件夹…

docker 02(docker 命令)

一、docker服务命令 systemctl start docker 启动docker服务 [参考] systemctl status docker 状态 systemctl stop docker 停止docker服务 systemctl restart docker 重启动docker服务 systemctl enable docker 开机自启动docker服务 &#xff0c;无需手动 二、docke…

远程调试环境配置

目录 一、准备工作 二、ssh连接和xdebug配置 1.ssh连接 2.xdebug配置 三、xdebug调试&#xff0c;访问 一、准备工作 1.安装vscode里面的两个扩展 2.安装对应PHP版本的xdebug 去xdebug官方&#xff0c;复制自己的phpinfo源码到方框里&#xff0c;再点击Analyse Xdebug: …

【UML】软件工程中常用图:类图、部署图、时序图、状态图

前言&#xff1a; UML中的很多东西平时都听过、用过&#xff0c;诸如类图、时序图等&#xff0c;本文将详细详细讲一下UML中常用的几类图&#xff0c;并且会引入一个完整的例子来讲解&#xff0c;UML在工程上到底该怎么合理使用。 目录 1.概述 1.1.什么是UML&#xff1f; …

C++入门---vector常用函数介绍及使用

vector的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它的大…

第1章:计算机网络体系结构

文章目录 1.1 计算机网络 概述1.概念2.组成3.功能4.分类5.性能指标1.2 计算机网络 体系结构&参考模型1.分层结构2.协议、接口、服务3.ISO/OSI模型4.TCP/IP模型1.1 计算机网络 概述 1.概念 2.组成 1.组成部分&

docker搭建redis三主三从集群,及其常见问题解决

目录结构 redis.conf主要参数 每个配置文件都要修改对应的端口 bind 0.0.0.0 protected-mode no #每个配置文件都要修改端口 port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 supervised no loglevel notice #日志文件路径 #logfile "/mydata/master_redis/log/…

逻辑回归原理,最大化似然函数和最小化损失函数

目录 逻辑回归原理 最大化似然函数和最小化损失函数 一、逻辑回归基本概念 1. 什么是逻辑回归 2. 逻辑回归的优缺点 3. 逻辑回归和多重线性回归的区别 Poisson分布 泊松分布的特点&#xff1a; 泊松分布用途 4. 逻辑回归用途 5. Regression 常规步骤 逻辑回归原理 …

代码随想录 (五)栈和队列

1栈与队列基础知识待看 2.用栈实现队列 题意有说操作是有效的&#xff0c;不用去判断非法的情况 class MyQueue { public:stack<int> stIn;stack<int> stOut; MyQueue() {}void push(int x) {stIn.push(x);}//出队并返回该元素 int pop() {if (stOut.empty()) {…

使用 Nacos 作为 Spring Boot 配置中心

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

数据并行(DP)、张量模型并行(TP)、流水线并行(PP)

数据并行 数据集分为n块&#xff0c;每块随机分配到m个设备(worker)中&#xff0c;相当于m个batch并行训练n/m轮&#xff0c;模型也被复制为n块&#xff0c;每块模型均在每块数据上进行训练&#xff0c;各自完成前向和后向的计算得到梯度&#xff0c;对梯度进行更新&#xff0…

【TypeScript】声明文件

在 TypeScript 中&#xff0c;声明文件&#xff08;Declaration Files&#xff09;用于描述已有 JavaScript 代码库的类型信息&#xff0c;以便在 TypeScript 项目中使用这些代码库时获得类型支持。 当你在 TypeScript 项目中引用外部 JavaScript 模块或库时&#xff0c;可能会…

iPhone 15 Pro与三星Galaxy Z Flip 5对决:谁将成为旗舰手机的佼佼者?

如果你打算买那些钱能买到的最好的手机,你经常会看到1000美元左右的东西。在这一点上,苹果即将推出的旗舰机并不遥远,这就是为什么值得一看iPhone 15 Pro与三星Galaxy Z Flip 5的对决。这两款旗舰手机将以大致相同的价格竞争,但它们的差异远比你想象的要大。 三星的Galaxy…

C语言之扫雷游戏实现篇

目录 主函数test.c 菜单函数 选择循环 扫雷游戏实现分析 整体思路 问题1 问题2 问题3 问题4 游戏函数&#xff08;函数调用&#xff09; 创建游戏盘数组mine 创建游戏盘数组show 初始化游戏盘数组InitBoard 展示游戏盘DisplayBoard 游戏盘置雷SetMine 游戏…