【学习笔记】手写一个简单的 Spring MVC

目录

一、什么是Spring MVC ?

Spring 和 Spring MVC 的区别?

Spring MVC 的运行流程?

二、实现步骤

1. DispatcherServlet

1. 创建一个中央分发器

拦截所有请求

测试

2. 接管 IOC 容器

1. 创建配置文件

2. 修改 web.xml 配置文件

3. 启动 IOC 容器

2. HandlerMapping

1. 添加映射

1. 创建 RequestMapping 注解

2. 创建映射Bean

3. 使用 RequestMapping 注解

4. 添加映射

2. 处理映射

1. 创建 ResponseBody 注解

2. 创建一个 HTML 文件

3. 使用 ResponseBody 注解

4. 处理映射

3. 测试


一、什么是Spring MVC ?

Spring MVC 是 Spring 的模块之一,Spring MVC 实现了MVC 的设计思想,并继承了 Servlet API 的WEB 框架。当用户在游览器地址栏上输入 url 后,Spring MVC就可以处理用户的请求

Spring 和 Spring MVC 的区别?

Spring 是一个框架,这个框架由不同的模块组成,其中一个模块 就是Spring MVC,Spring 核心是IOC 控制反转,IOC 容器负责对象的创建和依赖注入。

Spring MVC 是基于 MVC 设计来开发web 应用,Spring MVC 将前端发送的请求分发给适当的控制器 Controller,然后根据结果选择合适的视图进行渲染返回

Spring MVC 的运行流程?

  1. 用户发送HTTP请求
  2. 请求到达服务器后,Spring MVC 的中央分发器拦截请求
  3. 中央分发器根据 请求的路径找到对应的 HandlerMapping,确定由哪个 Controller 控制器处理
  4. HandlerMaping 根据请求信息映射到对应的 Controller ,然后返回给中央分发器
  5. 中央分发器把请求交给对应的Controller 控制器,Controller 是Spring MVC的一个组件,它负责处理请求以及响应结果
  6. Controller 控制器调用合适的业务层或 Mapper 层获取数据
  7. Controller 把数据封装成一个ModelAndView对象,然后返回给中央分发器
  8. 中央分发器把ModelAndView对象传给 ViewResolver
  9. ViewResolver 根据视图名称解析出一个 View 对象,然后返回给中央分发器
  10. 中央分发器把 view 对象渲染出来返回给客户端

在 手写 Spring IOC 的基础上再进行扩展,手写一个 Spring MVC

二、实现步骤

1. DispatcherServlet

1. 创建一个中央分发器

package com.shao.MVC;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class DispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("dispatcherServlet 初始化");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet 开始执行任务了");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet doPost");}}

拦截所有请求

在 Tomcat 的 web.xml 配置文件中配置自定义的中央分发器,拦截所有请求

<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app><display-name>Archetype Created Web Application</display-name><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>com.shao.MVC.DispatcherServlet</servlet-class></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

测试

在地址栏随便输入一个请求,中央分发器会进行拦截,然后执行初始化和相关方法,初始化只会执行一次

2. 接管 IOC 容器

在 手写 spring IOC 的时候,为了方便测试是在 Servlet 的 doGet 方法中初始化 IOC 容器,现在改为在中央分发器初始化的时候启动 IOC 容器

1. 创建配置文件

在配置文件中配置扫描包路径,然后启动中央分发器的时候把配置文件传过去

2. 修改 web.xml 配置文件

3. 启动 IOC 容器

在中央分发器的初始化方法中启动 IOC 容器

package com.shao.MVC;import com.shao.IOC.ApplicationContext;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Properties;public class DispatcherServlet extends HttpServlet {// 存储 IOC 容器private ApplicationContext applicationContext;private Properties Prop = new Properties();/*** 初始化*/@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("dispatcherServlet 初始化");// 获取传过来的配置文件String configLocation = config.getInitParameter("contextConfigLocation");String fileName = configLocation.replace("classpath:", "");// 调用 loadConfig 方法,传入配置文件名,返回扫描包路径String packagePath = loadConfig(fileName);try {// 启动 IOC 容器applicationContext = new ApplicationContext(packagePath);} catch (UnsupportedEncodingException e) {e.printStackTrace();}}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet 开始执行任务了");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet doPost");}/*** 加载配置文件,解析配置文件,返回扫描包路径*/public String loadConfig(String path) {// 以流的方式加载配置文件InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);String basePackage = "";try {// 解析配置文件中的属性,以键值对的方式存储到 Prop 中Prop.load(resourceAsStream);basePackage = Prop.getProperty("basePackage");} catch (IOException e) {e.printStackTrace();}return basePackage;}}

2. HandlerMapping

HandlerMapping 根据注解的值映射到对应的 Controller 和方法

将 url 和 controller 里面的方法进行映射,存到 HashMap 中,key 是 url ,value 是一个对象,这个对象有 url 对应的 controller 对象和对应的方法

1. 添加映射

1. 创建 RequestMapping 注解

2. 创建映射Bean

package com.shao.MVC;import java.lang.reflect.Method;public class RequestMappingBean {/*** controller 对象*/private Object controller;/*** controller 的方法*/private Method method;public RequestMappingBean(Object controller, Method method) {this.controller = controller;this.method = method;}/*** 获取** @return controller*/public Object getController() {return controller;}/*** 设置** @param controller*/public void setController(Object controller) {this.controller = controller;}/*** 获取** @return method*/public Method getMethod() {return method;}/*** 设置** @param method*/public void setMethod(Method method) {this.method = method;}public String toString() {return "RequestMappingBean{controller = " + controller + ", method = " + method + "}";}
}

3. 使用 RequestMapping 注解

4. 添加映射
package com.shao.MVC;import com.shao.Annotation.Controller;
import com.shao.Annotation.RequestMapping;
import com.shao.IOC.ApplicationContext;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Properties;public class DispatcherServlet extends HttpServlet {// 存储 IOC 容器private ApplicationContext applicationContext;private Properties Prop = new Properties();private HashMap<String, RequestMappingBean> mappingMap = new HashMap<>();/*** 初始化*/@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("dispatcherServlet 初始化");// 获取传过来的配置文件String configLocation = config.getInitParameter("contextConfigLocation");String fileName = configLocation.replace("classpath:", "");// 调用 loadConfig 方法,传入配置文件名,返回扫描包路径String packagePath = loadConfig(fileName);try {// 启动 IOC 容器applicationContext = new ApplicationContext(packagePath);} catch (UnsupportedEncodingException e) {e.printStackTrace();}// 添加映射AddRequestMapping();}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet 开始执行任务了");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet doPost");}/*** 加载配置文件,解析配置文件,返回扫描包路径*/public String loadConfig(String path) {// 以流的方式加载配置文件InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);String basePackage = "";try {// 解析配置文件中的属性,以键值对的方式存储到 Prop 中Prop.load(resourceAsStream);basePackage = Prop.getProperty("basePackage");} catch (IOException e) {e.printStackTrace();}return basePackage;}/*** 添加映射* 1. 从 IOC 容器中获取所有带 RequestMapping 注解的 Controller 对象* 2. 获取 Controller 对象中带 RequestMapping 注解的方法* 3. 将 Controller 对象和方法封装为 RequestMappingBean 对象* 4. 构建映射关系,key 是 url,value 是 映射Bean 对象,包括 Controller 对象和方法*/public void AddRequestMapping() {// 获取 IOC 容器的 Bean MapHashMap<String, Object> beanMap = applicationContext.getBeanMap();for (Object bean : beanMap.values()) {// 获取 bean 的 Class 对象Class<?> aClass = bean.getClass();// 判断是否有 @Controller 注解if (!aClass.isAnnotationPresent(Controller.class)) {continue;}// 判断是否有 @RequestMapping 注解if (!aClass.isAnnotationPresent(RequestMapping.class)) {continue;}// 获取类的 @RequestMapping 注解的值String basePath = aClass.getAnnotation(RequestMapping.class).value();// 获取 Controller 对象中的所有方法Method[] methods = aClass.getDeclaredMethods();for (Method method : methods) {// 判断方法上有没有带 @RequestMapping 注解if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}String path = method.getAnnotation(RequestMapping.class).value();// 封装为 映射Bean 对象RequestMappingBean mappingBean = new RequestMappingBean(bean, method);// 构建映射,添加到 Map 中mappingMap.put(basePath + path, mappingBean);}}System.out.println("映射添加完成");System.out.println(mappingMap);}}

2. 处理映射

1. 创建 ResponseBody 注解

2. 创建一个 HTML 文件

3. 使用 ResponseBody 注解

4. 处理映射

为了方便测试,只处理了 GET 方法的映射

package com.shao.MVC;import com.alibaba.fastjson2.JSON;
import com.shao.Annotation.Controller;
import com.shao.Annotation.RequestMapping;
import com.shao.Annotation.ResponseBody;
import com.shao.IOC.ApplicationContext;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Properties;public class DispatcherServlet extends HttpServlet {// 存储 IOC 容器private ApplicationContext applicationContext;private Properties Prop = new Properties();private HashMap<String, RequestMappingBean> mappingMap = new HashMap<>();/*** 初始化*/@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("dispatcherServlet 初始化");// 获取传过来的配置文件String configLocation = config.getInitParameter("contextConfigLocation");String fileName = configLocation.replace("classpath:", "");// 调用 loadConfig 方法,传入配置文件名,返回扫描包路径String packagePath = loadConfig(fileName);try {// 启动 IOC 容器applicationContext = new ApplicationContext(packagePath);} catch (UnsupportedEncodingException e) {e.printStackTrace();}// 添加映射AddRequestMapping();}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet 开始执行任务了");// 处理请求HandlerMapping(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("dispatcherServlet doPost");}/*** 加载配置文件,解析配置文件,返回扫描包路径*/public String loadConfig(String path) {// 以流的方式加载配置文件InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);String basePackage = "";try {// 解析配置文件中的属性,以键值对的方式存储到 Prop 中Prop.load(resourceAsStream);basePackage = Prop.getProperty("basePackage");} catch (IOException e) {e.printStackTrace();}return basePackage;}/*** 添加映射* 1. 从 IOC 容器中获取所有带 RequestMapping 注解的 Controller 对象* 2. 获取 Controller 对象中带 RequestMapping 注解的方法* 3. 将 Controller 对象和方法封装为 RequestMappingBean 对象* 4. 构建映射关系,key 是 url,value 是 映射Bean 对象,包括 Controller 对象和方法*/public void AddRequestMapping() {// 获取 IOC 容器的 Bean MapHashMap<String, Object> beanMap = applicationContext.getBeanMap();for (Object bean : beanMap.values()) {// 获取 bean 的 Class 对象Class<?> aClass = bean.getClass();// 判断是否有 @Controller 注解if (!aClass.isAnnotationPresent(Controller.class)) {continue;}// 判断是否有 @RequestMapping 注解if (!aClass.isAnnotationPresent(RequestMapping.class)) {continue;}// 获取类的 @RequestMapping 注解的值String basePath = aClass.getAnnotation(RequestMapping.class).value();// 获取 Controller 对象中的所有方法Method[] methods = aClass.getDeclaredMethods();for (Method method : methods) {// 判断方法上有没有带 @RequestMapping 注解if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}String path = method.getAnnotation(RequestMapping.class).value();// 封装为 映射Bean 对象RequestMappingBean mappingBean = new RequestMappingBean(bean, method);// 构建映射,添加到 Map 中mappingMap.put(basePath + path, mappingBean);}}System.out.println("映射添加完成");System.out.println(mappingMap);}/*** 处理请求,根据 url 找到对应的映射对象,调用对应的方法,返回结果*/public void HandlerMapping(HttpServletRequest req, HttpServletResponse resp) throws IOException {// 获取请求的路径,这个请求路径中有项目名称String requestURI = req.getRequestURI();// 获取项目名String contextPath = req.getContextPath();// 去掉项目名String url = requestURI.replace(contextPath, "");// 获取 url 对应的映射对象RequestMappingBean mappingBean = mappingMap.get(url);Object controller = mappingBean.getController();Method method = mappingBean.getMethod();Object res = null;try {// 调用映射对象中的方法res = method.invoke(controller);} catch (Exception e) {e.printStackTrace();}// 判断方法是否有返回内容if (res == null) {return;}// 判断方法是否有 @ResponseBody 注解if (method.isAnnotationPresent(ResponseBody.class)) {resp.setContentType("application/json;charset=utf-8");// 响应数据resp.getWriter().write(JSON.toJSONString(res));} else {// 获取编译后的项目根目录String path = this.getClass().getClassLoader().getResource("../../").getPath();// 路径前面有一个 /,比如: /D:/xxx,需要去掉,然后拼接静态资源名称String filePath = path.substring(1) + res;try {// 解码,如果路径有空格或者中文,会出现 16 进制的字符filePath = URLDecoder.decode(filePath, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}// 获取静态资源内容byte[] fileContents = StaticResourceHandler.getFileContents(filePath);// 获取文件媒体类型String mimeType = StaticResourceHandler.getFileMimeType(filePath);resp.setContentType(mimeType + ";charset=utf-8");// 响应内容resp.getWriter().write(new String(fileContents));}}
}

3. 测试

如果显示乱码可以添加以下命令试一下

-Dfile.encoding=UTF-8

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

输电线路悬垂线夹检测无人机航拍图像数据集,总共1600左右图片,悬垂线夹识别,标注为voc格式

输电线路悬垂线夹检测无人机航拍图像数据集&#xff0c;总共1600左右图片&#xff0c;悬垂线夹识别&#xff0c;标注为voc格式 输电线路悬垂线夹检测无人机航拍图像数据集介绍 数据集名称 输电线路悬垂线夹检测数据集 (Transmission Line Fittings Detection Dataset) 数据集…

在mac中通过ip连接打印机并实现双面打印

首先需要找到电脑自带的打印。添加打印机。 填写好打印机的ip地址&#xff0c;然后添加。 填写好ip地址后&#xff0c;直接添加就行 添加完打印机后其实就可以打印了。但是有些功能可能实现不了&#xff0c;比如说双面打印。为了实现双面打印的功能&#xff0c;需要再进行设置…

代码随想录算法训练营第五十四天|LeetCode42 接雨水、LeetCode84 柱状图中最大的矩形

LeetCode42 接雨水 代码随想录题目链接/文章讲解/视频讲解&#xff1a; 代码随想录代码随想录PDF&#xff0c;代码随想录网站&#xff0c;代码随想录百度网盘&#xff0c;代码随想录知识星球&#xff0c;代码随想录八股文PDF&#xff0c;代码随想录刷题路线&#xff0c;代码随…

GEE开发之Modis_NDWI数据分析和获取

GEE开发之Modis_NDWI数据分析和获取 0 数据介绍NDWI介绍MOD09GA介绍 1 NDWI天数据下载2 NDWI月数据下载3 NDWI年数据下载 前言&#xff1a;本文主要介绍Modis下的NDWI数据集的获取。归一化差异水指数 (NDWI) 对植被冠层液态水含量的变化很敏感。它来自近红外波段和第二个红外波…

PMP--冲刺题--解题--21-30

文章目录 11.风险管理--数据分析--成本效益分析--如果能够把单个项目风险的影响进行货币量化&#xff0c;那么就可以通过成本收益分析来确定备选风险应对策略的成本有效性。 特性要取消&#xff0c;要想继续做的话&#xff0c;就得看能不能给组织带来收益。21、 [单选] 在迭代审…

【NoSQL】portswigger NoSQL注入 labs 全解

目录 NoSQL NoSQL 数据库模型 NoSQL 注入的类型 NoSQL 语法注入 检测 MongoDB 中的语法注入 lab1:检测 NoSQL 注入 NoSQL 运算符注入 提交查询运算符 检测 MongoDB 中的运算符注入 lab2:利用 NoSQL 运算符注入绕过身份验证 利用语法注入来提取数据 MongoDB 中的数据…

Golang | Leetcode Golang题解之第446题等差数列划分II-子序列

题目&#xff1a; 题解&#xff1a; func numberOfArithmeticSlices(nums []int) (ans int) {f : make([]map[int]int, len(nums))for i, x : range nums {f[i] map[int]int{}for j, y : range nums[:i] {d : x - ycnt : f[j][d]ans cntf[i][d] cnt 1}}return }

Ubuntu 搭建 GitLab

1. 安装依赖&#xff1a; sudo apt update sudo apt install -y curl openssh-server ca-certificates2. 添加 GitLab 包仓库&#xff1a; curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash3. 安装 GitLab&#xff1a; s…

UE5数字人制作平台使用及3D模型生成

第10章 数字人制作平台使用及3D模型生成 在数字娱乐、虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;等领域&#xff0c;高质量的3D模型是数字内容创作的核心。本章将引导你了解如何使用UE5&#xff08;Unreal Engine 5&#xff09;虚幻引擎这一强大…

多模态大语言模型(MLLM)-Blip2深度解读

前言 Blip2是一个多模态大语言模型&#xff0c;因其提出时间较早&#xff08;2023年&#xff09;&#xff0c;且效果较好&#xff0c;很快成为一个标杆性工作。Blip2中提出的Q-former也成为衔接多模态和文本的重要桥梁。 Blip2发表时间是2023年&#xff0c;现在引用已经3288了…

【AIGC】ChatGPT是如何思考的:探索CoT思维链技术的奥秘

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;什么是CoT思维链CoT思维链的背景与技术发展需求 &#x1f4af;CoT思维链的工作原理&#x1f4af;CoT思维链的应用领域&#x1f4af;CoT思维链的优势&#x1f4af;CoT思维…

【JavaEE】【多线程】进程与线程的概念

目录 进程系统管理进程系统操作进程进程控制块PCB关键属性cpu对进程的操作进程调度 线程线程与进程线程资源分配线程调度 线程与进程区别线程简单操作代码创建线程查看线程 进程 进程是操作系统对一个正在运行的程序的一种抽象&#xff0c;可以把进程看做程序的一次运行过程&a…

【EXCEL数据处理】000014 案例 EXCEL分类汇总、定位和创建组。附多个操作案例。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000014 案例 EXCEL分类汇总、定位和创建组。附多个操…

SpringBoot MyBatis连接数据库设置了encoding=utf-8还是不能用中文来查询

properties的MySQL连接时已经指定了字符编码格式&#xff1a; url: jdbc:mysql://localhost:3306/sky_take_out?useUnicodetrue&characterEncodingutf-8使用MyBatis查询&#xff0c;带有中文参数&#xff0c;查询出的内容为空。 执行的语句为&#xff1a; <select id&…

LeetCode讲解篇之139. 单词拆分

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用一个数组记录字符串s在[0, i)区间能否使用wordDict组成 我们使用左右指针遍历字符串s的子串&#xff0c;左指针 j 为子串的左端点下标&#xff0c;右指针 i 为右端点下标的下一个 遍历过程中如果字符串s…

怎么成为年薪53万的AI产品经理?我分析了200份大厂的招聘要求

我在 BOSS 直聘搜索AI产品经理&#xff0c;筛选了公司规模在10000人以上的公司&#xff0c;清洗整理后得到 229 个岗位信息&#xff0c;分析得到如下信息&#xff1a; 按最低薪资算&#xff0c;平均年薪 40.2 万&#xff1b;取薪资范围均值&#xff0c;平均年薪 52.9 万;只有 …

(PyTorch) 深度学习框架-介绍篇

前言 在当今科技飞速发展的时代&#xff0c;人工智能尤其是深度学习领域正以惊人的速度改变着我们的世界。从图像识别、语音处理到自然语言处理&#xff0c;深度学习技术在各个领域都取得了显著的成就&#xff0c;为解决复杂的现实问题提供了强大的工具和方法。 PyTorch 是一个…

消费者Rebalance机制

优质博文&#xff1a;IT-BLOG-CN 一、消费者Rebalance机制 在Apache Kafka中&#xff0c;消费者组 Consumer Group会在以下几种情况下发生重新平衡Rebalance&#xff1a; 【1】消费者加入或离开消费者组&#xff1a; 当一个新的消费者加入消费者组或一个现有的消费者离开消费…

人机协作:科技与人类智慧的融合

随着科技的飞速发展&#xff0c;越来越多的领域开始借助人工智能&#xff08;AI&#xff09;和自动化技术来提升工作效率。人机协作&#xff08;Human-Machine Collaboration&#xff09;这一概念逐渐成为现代技术进步的核心。它不仅改变了我们的工作方式&#xff0c;也在重新定…

智能家居有哪些产品?生活中常见的人工智能有哪些?

智能家居有哪些产品? 1、智能照明设备类&#xff1a;智能开关、智能插座、灯控模块、智能空开、智能灯、无线开关。 2、家庭安防类&#xff1a;智能门锁、智能摄像机、智能猫眼、智能门铃。 3、智能传感器类&#xff1a;烟雾传感器、可燃气体传感器、水浸传感器、声光报警器…