Spring使用注解存储Bean对象

news2024/10/5 7:03:53

文章目录

  • 一. 配置扫描路径
  • 二. 使用注解储存Bean对象
    • 1. 使用五大类注解储存Bean
    • 2. 为什么要有五大类注解?
    • 3.4有关获取Bean参数的命名规则
  • 三. 使用方法注解储存Bean对象
    • 1. 方法注解储存对象的用法
    • 2. @Bean的重命名

在前一篇博客中( Spring项目创建与Bean的存储与读取(DL))介绍的是通过配置文件注册对象从而存储到 Spring 中,这种方式其实还是挺繁琐的。

实际上,在使用学习使用 Spring过程中,当我们要实现一个功能的时候,先应该考虑的是有没有相应的注解是实现对应功能的,Spring 中很多功能的配置都是可以依靠注解实现的,而本篇中介绍的是使用注解来存储 Bean 对象。

一. 配置扫描路径

首先还是要创建 Spring 项目,这里有问题还是去看我上一篇博客。当创建好项目后,我们的第一步就是配置扫描路径,这一步骤非常关键的,这里错了,之后的的操作就都不会生效了。

我们在resources目录下创建一个spring-config.xml配置文件,用来设置扫描的路径,在配置文件中添加如下内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <content:component-scan base-package=""></content:component-scan>
</beans>

其中<content:component-scan base-package=""></content:component-scan>里面 base-package的值设置为你需要扫描对象的根路径,这个路径从java目录开始,比如我在如图中的com.tr.demo目录下创建类:
img
那么这个配置文件中根路径就为com.tr.demo,所以我们将base-package的值设置为com.tr.demo

<content:component-scan base-package="com.tr.demo"></content:component-scan>

二. 使用注解储存Bean对象

想要使用注解,那得先知道能使用哪些注解,在 Spring 中有五大类注解和方法注解,分别为:

  1. 五大类注解:@Controller(控制器)、@Service(服务)、@Repository(仓库)、@Component(组件)、@Configuration(配置)。
  2. 方法注解:@Bean。

1. 使用五大类注解储存Bean

首先,我们来了解如何使用五大类注解来储存对象,先以@Controller注解为例,我们有如下的代码:

package com.tr.demo;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public void sayHi() {
        System.out.println("Hi, UserController~");
    }
}

像这样在扫描路径下创建类,并在类上加上@Controller注解就将Bean存储到容器当中了。

接下来就要从 Spring 中读取出我们的对象,这里还是先使用依赖查找的方式来获取 Bean,使用五大类注解,默认情况下,Bean 的名字就是原类名首字母小写(小驼峰)。

import com.tr.demo.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //获取对象时使用类名的小驼峰形式作为 name 参数
        UserController userController =  context.getBean("userController", UserController.class);
        userController.sayHi();
    }
}

运行结果:
img
要注意,是使用了五大类注解创建的类且类必须要在前面我们配置的扫描路径下(包括子包)才能将 Bean 存储到 Spring 当中,否则是无效的,所以这个扫描路径也叫做根路径。

设置根路径其实也是为了提高程序的性能,因为如果不设置根路径,Spring 就会扫描项目文件中所有的目录,但并不是所有类都需要储存到 Spring当中,这样性能就会比较低,设置了根路径,Spring 就只扫描该根路径下所有的目录就可以了,提高了程序的性能。

上面只使用了 @Controller,那么我们再来验证一下其他四个注解可不可以达到同样的目的,同时为了验证上面的结论,我们在com.tr.demo目录下再创建一个inner目录,在根路径外在创建一个类Student使用类注解。

img

package com.tr.demo.inner;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void sayHi() {
        System.out.println("Hi, UserComponent~");
    }
}

package com.tr.demo.inner;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfiguration {
    public void sayHi() {
        System.out.println("Hi, UserConfiguration~");
    }
}

package com.tr.demo.inner;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void sayHi() {
        System.out.println("Hi, UserRepository~");
    }
}

package com.tr.demo.inner;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void sayHi() {
        System.out.println("Hi, UserService~");
    }
}

import com.tr.demo.UserController;
import com.tr.demo.inner.UserComponent;
import com.tr.demo.inner.UserConfiguration;
import com.tr.demo.inner.UserRepository;
import com.tr.demo.inner.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //获取对象时使用类名的小驼峰形式作为 name 参数
        UserController userController =  context.getBean("userController", UserController.class);
        userController.sayHi();
        UserService service =  context.getBean("userService", UserService.class);
        service.sayHi();
        UserConfiguration configuration =  context.getBean("userConfiguration", UserConfiguration.class);
        configuration.sayHi();
        UserComponent component =  context.getBean("userComponent", UserComponent.class);
        component.sayHi();
        UserRepository repository =  context.getBean("userRepository", UserRepository.class);
        repository.sayHi();
    }
}

运行结果:

五大类注解效果都是一样的,而不在根路径下的Student是无效的。
img

还需要知道的是使用注解存储的 Bean 和使用XML存储的的 Bean 是可以一同使用的,比如我们将将刚刚有问题的Student重新通过XML的方式进行存储。

img

运行结果:

img

2. 为什么要有五大类注解?

既然都五大类完成的是同样的工作,那为什么要有五大类注解呢?

其实五大类注解主要是为了规范 Java 项目的代码,Java 项目的标准分层如下:

  1. 控制层(Controller)
  2. 服务层(Service)
  3. 数据持久层(Dao)

而五大类注解便是对应着不同的层级别使用的,让程序猿看到某一个注解就可以明确这个了类是做什么的。

  • @Controller:控制器,校验用户请求数据的正确性(安保系统);直接和前端打交道,校验前端发来请求是参数和合法性。

  • @Service:服务,编排和调度具体执行方法的(客服中心);不会直接操作数据库,根据请求判断具体调用哪个方法。

  • @Repository:数据持久层,直接和数据库交互(实际业务的执行),也叫DAO层(data access object)。

  • @Component:组件(工具类层),为整个项目存放一些需要使用的组件,但又和其他层没有什么实际交互。

  • @Configuration 配置项(项目中的一些配置)。

img

包括企业中也是按照这样的结构来将项目分层的,典型的比如阿里,它只是在标准分层在服务层(Service)做了一个扩展,划分的更加细致详细了。

img

五大类注解主要起到的是“见名知意”的作用,代码层面上来看,作用是类似的,我们去查看五大类类注解的源码看一看。

img

img

img

img

img
可以看到五大类的源码中除了 @Component 以外,其他四大类注解中都包含了 @Component 注解的功能,这四大类注解都是基于 @Component 实现的,是 @Component 拓展。

3.4有关获取Bean参数的命名规则

上文中在使用依赖查找的方式获取Bean时,getBean方法的BeanName是使用类名的小驼峰形式(即类名的首字母小写),这是因为使用注解储存对象时,默认会将类名的小驼峰形式设置为 Bean 的名字,但并不是完全依照这个规则的,是有特殊情况的。

比如,我们创建一个类,将它的前两个字母大写,如UConfig,此时来看使用类名的小驼峰形式还能不能获取到 Bean。

package com.tr.demo;

import org.springframework.stereotype.Repository;

@Repository
public class UConfig {
    public void sayHi(){
        System.out.println("Hi, UConfig~");
    }
}

启动类

import com.tr.demo.UConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        UConfig uConfig=  context.getBean("uConfig", UConfig.class);
        uConfig.sayHi();
    }
}

运行结果:
img
可以看到程序报错了,说没有找到beanNameuConfig的对象,那此时的beanName是什么呢?

此时再来试一下原本的类名(大驼峰),看能不能获取:

UConfig uConfig=  context.getBean("UConfig", UConfig.class);

运行结果:
img
此时就获取到了,好像多少有点玄学在里面,我们来翻一翻源码,看一看这是什么原因。

双击Shift进行全局搜索,上面是根于对象名称来找到对象的,所以我们输入beanName,试着搜索一下:

img

我们会发现有AnnotationBeanNameGenerator类与BeanNameGenerator接口,那我们就试着点到AnnotationBeanNameGenerator类源码看一看。

正常点开后看到的应该是 IDEA 将.class文件反编译出来的代码,缺少注释和明确的变量命名。

img

我们点击Download Sources将 Spring 源码下载下来即可,此时我们在在源码中就能看到下面的方法,看名字也知道,用来建立默认的BeanName的。

img

返回值是Introspector.decapitalize方法的返回值,再点进去看看这个方法。

img

此时我们就能分析得出结论,如果类名长度大于1并且满足第一个与第二个字母为大写,则构造的BeanName就为原类名,其他正常情况为类名的小驼峰形式,这就解释了UConfig类的BeanName为什么是原类名了。
而且我们会发现这个方法所在类是来自于jdk的。
img

所以,BeanName的规范命名规则并不是 Spring 独创的,而依照 Java 标准库的规则进行的。

  1. 如果类名不存在或类名为空字符串,BeanName为原类名。
  2. 如果类名字长度大于1,且第一个与第二个字符为大写,BeanName为原类名。
  3. 其他情况,BeanName为原类名的小驼峰形式。

三. 使用方法注解储存Bean对象

1. 方法注解储存对象的用法

五大类注解是添加到某个类上的,而方法注解是放到方法上的,当一个方法返回的是一个具体的实例对象时,我们就可以使用方法注解@Bean来将对象储存到 Spring,但是单单使用一个@Bean是不能够成功储存对象的,还需要在方法所在类上使用五大类注解才行,比如搭配一 个@Component 注解,方法注解是不能够单独使用的,@Bean注解必须要搭配五大类注解一起使用(Spring为了提升性能所做的规定,毕竟造方法的成本太低了,不能去扫描整个项目的方法吧)。

还是要注意使用必须是在根路径下。

比如我们有一个普通文章的实体类ArticleInfo

package com.tr.demo.model;

import java.time.LocalDateTime;

/**
 * 普通的文章实体类
 */
public class ArticleInfo {
    private int aid;
    private LocalDateTime createtime;
    private String title;
    private String author;
    private String content;
    
    public void setAid(int aid) {
        this.aid = aid;
    }
    public void setCreatetime(LocalDateTime createtime) {
        this.createtime = createtime;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "ArticleInfo{" +
                "aid=" + aid +
                ", createtime=" + createtime + "\n" +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' + "\n" +
                ", content='" + content + '\'' +
                '}';
    }
}

下面演示使用@Bean方法注解储存对象

package com.tr.demo;

import com.tr.demo.model.ArticleInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import java.time.LocalDateTime;

@Controller
public class Articles {
    @Bean// 将当前方法返回的对象存储到 IoC 容器
    public ArticleInfo getArt(){
        // 伪代码(实际上这里的 Bean 不是 new 出来的)
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setAid(1);
        articleInfo.setCreatetime(LocalDateTime.now());
        articleInfo.setTitle("夏日绝句");
        articleInfo.setAuthor("李清照");
        articleInfo.setContent("生当做人杰,死亦为鬼雄。至今思项羽,不肯过江东。");
        return articleInfo;
    }

    public void sayHi(){
        System.out.println("Hi, Articles~");
    }
}

获取方法注解储存的对象时,传入的BeanName参数值默认值就是方法名,我上面的代码中方法名为getArt,所以获取时,就使用getArt作为参数来进行获取。

import com.tr.demo.model.ArticleInfo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP3 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        ArticleInfo article =  context.getBean("getArt", ArticleInfo.class);

        System.out.println(article);
    }
}

运行结果:
img

2. @Bean的重命名

获取方法注解储存的对象时,传入的BeanName参数值默值为方法名,但像上面那样返回对象的方法名称往往是getXXX这样式取名的,虽然在语法与实现上是没有问题的,但实际开发写出这样的代码,看起来还是比较别扭的。

实际上注解 @Bean 是可以加参数的,给储存的对象起别名,像下面这个样子。

@Controller
public class Articles {
    @Bean("article")// 将当前方法返回的对象存储到 IoC 容器
    public ArticleInfo getArt(){
        // 伪代码(实际上这里的 Bean 不是 new 出来的)
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setAid(1);
        articleInfo.setCreatetime(LocalDateTime.now());
        articleInfo.setTitle("夏日绝句");
        articleInfo.setAuthor("李清照");
        articleInfo.setContent("生当做人杰,死亦为鬼雄。至今思项羽,不肯过江东。");
        return articleInfo;
    }

    public void sayHi(){
        System.out.println("Hi, Articles~");
    }
}

也可以给 Bean 设置多个别名,总结起来有如下几种方式:

//方式一(省略参数名的情况下默认是name)
@Bean("article1")
//方式二
@Bean(name = "article2")
//方式三
@Bean(value = "article3")
//起多个别名
@Bean(name = {"article4", "article5"})
@Bean(value = {"article6", "article7"})
@Bean({"article8", "article9", "article10"})

我们按照第 9 行的方式设置,此时获取方法注解储存的对象就能够使用别名来进行获取。

img

import com.tr.demo.model.ArticleInfo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP4 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        ArticleInfo article1 =  context.getBean("article6", ArticleInfo.class);
        System.out.println(article1);
        System.out.println("-----------------------------------------------------");
        ArticleInfo article2 =  context.getBean("article7", ArticleInfo.class);
        System.out.println(article2);
        System.out.println("-----------------------------------------------------");
    }
}

运行结果:
img

再想一下,当一个 Bean 有别名了,那使用之前那个方法名还能够获取到对象吗?尝试一下:

import com.tr.demo.model.ArticleInfo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class APP5 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        ArticleInfo article =  context.getBean("getArt", ArticleInfo.class);
        System.out.println(article);
    }
}

运行结果:

此时就能发现是获取不到的
img
所以使用 @Bean 存储对象的beanName命名规则是,当没有设置name/value属性时,此时 Bean 的默认名字就是方法名,一旦添加了别名name/value属性后,就只能通过重命名的别名来获取 Bean 了,默认的使用方法名获取 Bean 对象就不能使用了。

还要简单注意一下,@Bean 使用时,同一类如果多个 Bean 使用相同的名称,此时程序执行是不会报错的,他会根据类加载顺序和类中代码从上至下的的顺序,将第一个 Bean 存放到 Spring 中,但第一个之后的对象就不会被存放到容器中了,也就是只有在第一次创建 Bean 的时候会将对象和 Bean 名称关联起来,后续再有相同名称的Bean存储时候,容器会自动忽略。

还可以通过类注解 @Order 注解控制类加载顺序(值越小,优先级越高),进而影响 Bean 的存放的先后顺序,这些也比较简单,就不做演示了。

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

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

相关文章

Excel中Vlookup

VLOOKUP($A:$A,Sheet3!$A:$D,COLUMN(Sheet3!B1),FALSE) ps: 1.按F4&#xff0c;锁定第一个和第二个参数 2.第二个参数&#xff0c;要选择全部范围(包括被查找列&#xff0c;以及查找内容) 2.第三个参数用column&#xff08;&#xff09;函数&#xff0c;第三列不要锁定 3.…

【Linux】自动化构建工具-make/Makefile详解

前言 大家好吖&#xff0c;欢迎来到 YY 滴 Linux系列 &#xff0c;热烈欢迎&#xff01;本章主要内容面向接触过Linux的老铁&#xff0c;主要内容含 欢迎订阅 YY 滴Linux专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 订阅专栏阅读&#xff1a;YY的《…

Alpha-GO打败⼈类的秘籍- 强化学习(Reinforcement Learning)

为了深⼊理解强化学习&#xff08;Reinforcement Learning&#xff0c;简称RL&#xff09;这⼀核⼼概念&#xff0c;我们从⼀个⽇常游戏的例⼦出发。在“贪吃蛇”这个经典游戏中&#xff0c;玩家需要掌控⼀条蛇&#xff0c;引导它吞吃屏幕上出现的各种果实。每次成功捕获果实&a…

CSS层叠

声明冲突 同一个样式&#xff0c;多次应用到同一个元素。 此时可以看到我们的a元素的样式和浏览器的默认样式发生了冲突 层叠 解决声明冲突的过程&#xff0c;浏览器会自动处理&#xff08;权重计算&#xff09;&#xff0c;权重大的获胜 1.比较重要性 重要性从高到低 1&…

Docker 教程

Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&a…

省电液晶驱动IC,VK2C22G,COG片高抗干扰抗噪系列LCD段码驱动芯片,I2C通信接口

型号:VK2C22G DICE(邦定COB)/COG&#xff08;绑定玻璃用&#xff09; VK2C22G概述&#xff1a; VK2C22G是一个点阵式存储映射的LCD驱动器&#xff0c;可支持最大176点&#xff08;44SEGx4COM&#xff09;的LCD屏。单片机可通过I2C接口配置显示参数和读写显示数据&#…

C# 接口2.0 (028 课程)

参考视频教程&#xff1a;B站 刘铁猛 028 接口 依赖反转 单元测试 接口是一种供方和需求方都遵守的契约 — 即规则。 文章目录 原始代码利用用接口简化函数重载紧耦合程序实体松耦合代码实体设计一个紧耦合 -- 接口松耦合 -- 单元测试紧耦合程序实例松耦合程序实体 -- 引入接…

latex子图无法重新编号问题

问题 想让后四个子图&#xff0c;从a开始编号 解决方法 如上原因是由于全局自动编号机制&#xff0c;在 \begin{figure*}[h] \centering下面添加&#xff1a; \setcounter{subfigure}{0}

SpringBoot+ShardingSphere+Mybatis实现Mysql8读写分离

场景 CentOS7安装Mysql8并进行主从复制配置&#xff1a; CentOS7安装Mysql8并进行主从复制配置_霸道流氓气质的博客-CSDN博客 在上面搭建起来Mysql之间的主从复制的基础上&#xff0c;在SpringBoot项目中实现Mysql数据的 读写分离&#xff0c;即写入操作一个库&#xff0c;…

为什么重写 equals 方法要重写 hashCode 方法

前言 看了挺多关于这个问题相关的文章&#xff0c;发现回答缺少因果逻辑。直到看了一篇文章提到 《Effective Java》中有关于这个问题的答案。于是找了电子书看了一下&#xff0c;查看相关源码理清了因果逻辑&#xff0c;得出怎样回答这个问题比较好。 1 hashCode 的通用约定…

k8s 如何升级应用

如何升级应用 在之前的分享中&#xff0c;我们知道一个程序如何放到容器中&#xff0c;一个镜像如何生成 pod&#xff0c; pod 的创建&#xff0c;运行&#xff0c;管理&#xff0c;删除过程&#xff0c;挂载等等 那么我们有没有想过&#xff0c;在真正的生产环境中&#xff…

golang 验证器库go-playground/validator实践

当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时&#xff0c;此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。 reflect.Value 类型有很多方法&#xff08;Package reflect - The …

【字符流】编码解码问题

字符流中编码解码问题 字符流抽象基类&#xff1a; Reader&#xff1a;字符输入流的抽象类Writer&#xff1a;字符输出流的抽象类 字符流中和编码和解码问题相关的两个类&#xff1a; InputStreamReader&#xff1a;是从字节流到字符流的桥梁&#xff0c;它读取字节并使用指…

Jupyter Notebook 后台启动

进入用户目录 cd 指定目录 激活环境 conda activate 环境名 Note&#xff1a; 注意&#xff1a;无法导入依赖包&#xff0c;查看是否激活对应环境。 后台启动 jupyter notebook&#xff0c;或者服务器上启动 可通过浏览器访问 nohup jupyter notebook --allow-root& …

C语言——通讯录的实现

前面的文章介绍了C语言中的 指针、自定义类型等模块&#xff0c;这篇文章将通过编写实现通讯录的代码对这些模块进行应用和进一步加深理解&#xff1a; 目录 1. 通讯录主要功能设计&#xff1a; 2. 通讯录的实现——主页面&#xff1a; 3. 通讯录的实现——保存个人信息&am…

CS 144 Lab Three-- the TCP sender

CS 144 Lab Three -- the TCP sender TCPSender 功能如何检测丢包TCPSender 要求TCPSender 状态转换图TCPSender 实现测试 对应课程视频: 【计算机网络】 斯坦福大学CS144课程 Lab Three 对应的PDF: Lab Checkpoint 3: the TCP sender TCPSender 功能 TCP Sender 负责将数据以…

27 Deep Belief Network

文章目录 27 Deep Belief Network——深度信念网络27.1 DBN是什么&#xff1f;27.2 为什么要使用DBN27.2.1 DBN的思想是怎么来的&#xff1f;27.2.2 RBM的叠加可以提高ELBO 27.3 训练方式 27 Deep Belief Network——深度信念网络 27.1 DBN是什么&#xff1f; DBN(Deep Belie…

【机器学习】分类算法 - KNN算法(K-近邻算法)KNeighborsClassifier

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;零基础快速入门人工智能《机器学习入门到精通》 K-近邻算法 1、什么是K-近邻算法&#xff1f;2、K-近邻算法API3、…

FastDFS与Springboot集成

&#x1f680; FastDFS与Springboot集成 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风…

139、仿真-基于51单片机一氧化碳(CO)气体检测仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、设计功能 二、Proteus仿真图​编辑 三、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&#xff1…