详细分析Spring的动态代理机制

news2024/11/13 23:27:20

文章目录

  • 1. JDK动态代理和CGLIB动态代理的区别
    • 1.1 适用范围
    • 1.2 生成的代理类
    • 1.3 调用方式
  • 2. 问题引入
  • 3. 创建工程验证 Spring 默认采用的动态代理机制
    • 3.1 引入 Maven 依赖
    • 3.2 UserController.java
    • 3.3 UserService.java
    • 3.4 UserServiceImpl.java(save方法添加了@Transactional注解)
    • 3.5 User.java
    • 3.6 编写配置文件
  • 4. 测试结果及测试结果分析
    • 4.1 测试结果
    • 4.2 测试结果分析
    • 4.3 Spring Framework工程和SpringBoot工程中分别使用哪种动态代理
    • 4.4 源码分析
    • 4.5 手动指定 AOP 使用哪种动态代理
  • 5. 为什么SpringBoot默认会选用CGLIB动态代理(JDK动态代理的局限性)
    • 5.1 使用JDK动态代理时目标对象必须要实现接口
    • 5.2 使用JDK动态代理时必须要用接口来接收
    • 5.3 使用JDK动态代理时获取不到实现类方法上的注解
  • 6. 补充:调试时this指针指的是哪个对象
  • 7. 总结
  • 8. 参考资料

1. JDK动态代理和CGLIB动态代理的区别

1.1 适用范围

  • 如果要使用 JDK 动态代理,必须要保证目标类实现了接口
  • 如果要使用 CGLIB 动态代理,目标类不能使用 final 关键字进行修饰,也就是说,目标类是可继承的

1.2 生成的代理类

  • JDK 动态代理生成的代理对象会实现目标类实现的所有接口,这也是 JDK 动态代理为什么要保证目标类实现了接口的原因
  • CGLIB 动态代理生成的代理对象会继承目标类,所以说目标类不能使用 final 关键字进行修饰,因为一个类加了final 关键字就不能被继承了

1.3 调用方式

JDK 动态代理是采用反射的方式来调用目标方法,而 CGLIB 是通过子类直接调用父类的方式来调用目标方法

2. 问题引入

Spring 框架在创建代理对象时,默认情况下如果目标对象实现了至少一个接口,那么会使用 JDK 动态代理;如果目标对象没有实现任何接口,则会使用 CGLIB 动态代理

以上内容相信大家都非常熟悉,然而事实真的是这样吗,我们创建一个工程来验证一下是否真的如此

3. 创建工程验证 Spring 默认采用的动态代理机制

工程环境:SpringBoot 3.0.2 + JDK 17.0.7

3.1 引入 Maven 依赖

为了方便测试,我们引入以下依赖

  • Spring Web
  • MyBatis Framework
  • aspectjweaver
  • MySQL 连接驱动
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

我们创建几个简单的类,验证 Spring 默认采用的动态代理机制

3.2 UserController.java

import cn.edu.scau.pojo.User;
import cn.edu.scau.service.UserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/save")
    public void save(@RequestBody User user) {
        userService.save(user);
    }

}

3.3 UserService.java

import cn.edu.scau.pojo.User;

public interface UserService {

    void save(User user);
    
}

3.4 UserServiceImpl.java(save方法添加了@Transactional注解)

import cn.edu.scau.pojo.User;
import cn.edu.scau.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Transactional
    public void save(User user) {
        System.out.println("保存用户:" + user);
    }

}

3.5 User.java

public class User {

}

3.6 编写配置文件

application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

4. 测试结果及测试结果分析

4.1 测试结果

我们在 controller 层打上断点,以 debug 的方法访问接口

在这里插入图片描述

可以看到,UserService 的类名中出现了 CGLIB 关键字,说明采用的是 CGLIB 动态代理

在这里插入图片描述

怎么回事呢,明明我的 UserServiceImpl 是实现了 UserService 接口的,为什么会使用 CGLIB 动态代理呢

4.2 测试结果分析

在 Spring Framework 中,是使用 @EnableAspectJAutoProxy 注解来开启 Spring AOP 相关功能的

我们查看一下@EnableAspectJAutoProxy 注解类的源码

在这里插入图片描述

通过源码的注释我们可以了解到:在 Spring Framework 中,proxyTargetClass 的默认取值是 false(false 表示不启用 CGLIB 动态代理),也就是说,Spring Framework 默认采用的还是使用 JDK 动态代理

难道说,Spring 源码的注释写错了???


为了进一步进行测试,我们在项目的启动类上手动指定使用 JDK 动态代理

@EnableAspectJAutoProxy(proxyTargetClass = false)

在这里插入图片描述

可以发现,Spring 默认使用的就是 JDK 动态代理

打断点后再次进行测试,发现使用的还是 CGLIB 动态代理

在这里插入图片描述

难道 @EnableAspectJAutoProxy 的 proxyTargetClass 属性无效了?

4.3 Spring Framework工程和SpringBoot工程中分别使用哪种动态代理

等一等,我们是不是遗漏了什么?

我们的测试工程使用的 SpringBoot 环境,那如果不用 SpringBoot,只用 Spring Framework 会怎么样呢


测试过程与上述测试相同,就不再赘述了

测试结果表明,在 Spring Framework 中,如果类实现了接口,默认还是使用 JDK 动态代理

4.4 源码分析

结果上面的分析,很有可能是 SpringBoot 修改了 Spring AOP 的相关配置,那我们就来一波源码分析,看一下 SpringBoot 内部到底做了什么

源码分析,找对入口很重要。那这次的入口在哪里呢?

@SpringBootApplication 是一个组合注解,该注解中使用 @EnableAutoConfiguration 实现了大量的自动装配

EnableAutoConfiguration 也是一个组合注解,在该注解上被标志了 @Import

在这里插入图片描述

在这里插入图片描述

AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口

在这里插入图片描述

DeferredImportSelector 接口中有一个 getImportGroup 方法,AutoConfigurationImportSelector 类重写了该方法

在这里插入图片描述

在这里插入图片描述

getImportGroup 方法返回了AutoConfigurationGroup 类,AutoConfigurationGroup 是 AutoConfigurationImportSelector 中的一个私有内部类,实现了 DeferredImportSelector.Group 接口

在这里插入图片描述

SpringBoot 就是通过 AutoConfigurationImportSelector.AutoConfigurationGroup 类的 process 方法来导入自动配置类的

我们在 process 方法打上断点,重新启动 SpringBoot 测试工程

在这里插入图片描述

通过调试信息,我们可以看到与 AOP 相关的自动配置是通过 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration 类来进行配置的

在这里插入图片描述

我们查看 AopAutoConfiguration 类的源码,发现如果我们没有在配置文件中指定使用哪种代理,默认就会使用 CGLIB 动态代理

在这里插入图片描述

4.5 手动指定 AOP 使用哪种动态代理

通过源码我们也就可以知道,在 SpringBoot 中如果需要指定 AOP 使用哪种动态代理,需要通过 spring.aop.proxy-target-class 这个配置项来修改

在 application.yml 文件中通过 spring.aop.proxy-target-class 属性来配置

在编写文件时我们也可以看到,proxy-target-class 属性默认为 true

在这里插入图片描述

我们改为 false 之后再次启动测试工程,发现已经使用了 JDK 动态代理

在这里插入图片描述

5. 为什么SpringBoot默认会选用CGLIB动态代理(JDK动态代理的局限性)

为什么 SpringBoot 默认会选用 CGLIB 动态代理呢,当然是因为 JDK 动态代理存在一定的局限性

5.1 使用JDK动态代理时目标对象必须要实现接口

如果要使用 JDK 动态代理,必须要保证目标类实现了接口,这是由 JDK 动态代理的实现原理决定的

5.2 使用JDK动态代理时必须要用接口来接收

我们现在有 UserServiceImpl 和 UserService 两个类,此时需要在 UserContoller 中注入 UserService 类

我们通常都习惯这样写代码

@Autowired
private UserService userService;

在这种情况下,无论是使用 JDK 动态代理,还是 CGLIB 动态代理都不会出现问题


但是,如果代码是这样的呢

@Autowired
private UserServiceImpl userService;

这个时候,如果我们是使用 JDK 动态代理,那在启动时就会报错:

在这里插入图片描述

The bean is of type ‘jdk.proxy2.$Proxy59’ and implements:
cn.edu.scau.service.UserService
org.springframework.aop.SpringProxy
org.springframework.aop.framework.Advised
org.springframework.core.DecoratingProxy

Expected a bean of type ‘cn.edu.scau.service.impl.UserServiceImpl’ which implements:
cn.edu.scau.service.UserService

Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.


因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量

而 CGLIB 就不存在这个问题,因为 CGLIB 是通过生成目标对象子类来实现的,代理对象无论是赋值给接口还是实现类,两者都是代理对象的父类,使用 CGLIB 动态代理可以避免类型转换导致的错误

5.3 使用JDK动态代理时获取不到实现类方法上的注解

在这里插入图片描述

我们在 UserServiceImpl 类的 save 方法上添加一个自定义的 Log 注解

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    /**
     * 操作名称
     */
    String name() default "";

}

同时对方法上带有 Log 注解的类进行增强

import cn.edu.scau.annotation.Log;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 切面类
 */
@Component
@Aspect
public class SystemAspect {

    @Pointcut("@annotation(cn.edu.scau.annotation.Log)")
    private void pointcut() {

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 通过解析 session 或 token 获取用户名

        // 获取被增强类和方法的信息
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;

        // 获取被增强的方法对象
        Method method = methodSignature.getMethod();

        // 从方法中解析注解
        if (method != null) {
            Log logAnnotation = method.getAnnotation(Log.class);
            System.out.println("模块名称:" + logAnnotation.name());
        }

        // 方法名字
        String name = null;
        if (method != null) {
            name = method.getName();
        }
        System.out.println("方法名:" + name);

        // 通过工具类获取Request对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = null;
        if (servletRequestAttributes != null) {
            request = servletRequestAttributes.getRequest();
        }

        // 访问的URL
        String url = null;
        if (request != null) {
            url = request.getRequestURI();
        }
        System.out.println("访问的URL:" + url);

        // 请求方式
        String methodName = null;
        if (request != null) {
            methodName = request.getMethod();
        }
        System.out.println("请求方式:" + methodName);

        // 登录IP
        String ipAddress = null;
        if (request != null) {
            ipAddress = getIpAddress(request);
        }
        System.out.println("登录IP:" + ipAddress);

        // 操作时间
        System.out.println("操作时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        // 将操作日志保存到数据库

        return joinPoint.proceed();
    }

    /**
     * 获取 IP 地址
     *
     * @param request HttpServletRequest
     * @return String
     */
    public String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");

        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

}

在这里插入图片描述

以 debug 的方式启动项目,在切面中发现获取到的注解数量为 0,获取不到任何注解,因为 UserService 接口上并没有注解(当然,我们也可以在接口上添加注解,但是这不符合我们的编码习惯)

method.getAnnotations()

在这里插入图片描述


改用 CGLIB 动态代理后,再次以 debug 的方式启动项目,在切面中发现获取到的注解数量为 2,获取到了方法上的所有注解

在这里插入图片描述

6. 补充:调试时this指针指的是哪个对象

哪个对象调用了this所在的代码块,this 指针就指向哪个对象

如果我们在打断点调试时,输出 this,会发现 this 指针指向的不是代理对象,为什么呢

在这里插入图片描述

在这里插入图片描述

因为代理对象只是对目标对象进行了增强,真正调用目标方法还是需要目标对象

以 CGLIB 动态代理为例,代理对象会保留目标对象的引用,代理对象的简单结构如下

在这里插入图片描述

我们记住,哪个对象调用了 this 所在的代码块,this 指针就指向哪个对象

在进行断点调试时,this 所在的代码块指向的对象就是目标对象,而不是代理对象

7. 总结

  • 在 Spring Framework 工程中 AOP 默认使用 JDK 动态代理
  • 为了解决使用 JDK 动态代理可能导致的类型转化异常问题,SpringBoot 默认使用 CGLIB 动态代理
  • 在 SpringBoot 中,如果需要默认使用 JDK 动态代理,可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,在启动类上通过 @EnableAspectJAutoProxy(proxyTargetClass = false) 配置已无效

8. 参考资料

  • 小米二面:为什么SpringBoot使用cglib作为默认动态代理 ?AOP使用jdk动态代理会有什么问题 ?_哔哩哔哩_bilibili
  • 什么鬼?弃用JDK动态代理,Spring5 默认使用 CGLIB 了?|jdk|aop|boot|proxy|spring|framework_网易订阅 (163.com)
  • 腾讯二面:Spring AOP底层实现原理是怎么样的?JDK和CGlib动态代理有什么区别 ?_哔哩哔哩_bilibili

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

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

相关文章

【Python】探索 TensorFlow:构建强大的机器学习模型

TensorFlow 是一个开源的深度学习框架&#xff0c;由 Google 开发&#xff0c;广泛应用于机器学习和人工智能领域。自从 2015 年推出以来&#xff0c;它已成为研究人员、开发者和数据科学家们不可或缺的工具。TensorFlow 提供了灵活、高效的工具集&#xff0c;可以帮助我们构建…

API接口在金融科技领域的创新应用

导语&#xff1a; 随着互联网的发展和技术的进步&#xff0c;API接口在金融科技领域的创新应用越来越受到关注。本文将介绍API接口的基本概念&#xff0c;以及它在金融科技领域的应用案例。 第一部分&#xff1a;API接口简介及原理 API是Application Programming Interface&…

2021-03-03人工智能应用的就业效应

【摘要】文章从人工智能的概念出发&#xff0c;在总结已有研究方法的基础上&#xff0c;回顾了人工智能对就业的产业分布、岗位、工资等方面影响的理论与实证研究。文章发现&#xff0c;人工智能技术在替代部分岗位、促使劳动力在不同产业间流动的同时&#xff0c;还会加快劳动…

java 获取集合a比集合b多出来的对象元素

public class OrderListEntity {/*** deprecated 对象集合的处理* param aData 集合a* param bData 集合b* return 返回集合a比集合b多出来的部分, 通过id判断*/public static List<OrderListEntity> AHasMoreThanBData(List<OrderListEntity> aData, List<Ord…

LEAN 赋型唯一性(Unique Typing)之 Church-Rosser 定理 (Church-Rosser Theorem)及 赋型唯一性的证明

有了并行K简化的概念及其属性&#xff0c;以及其在LEAN类型理论中的相关证明&#xff0c;就可以证明&#xff0c;在K简化下的Church-Rosser 定理。即&#xff1a; 其过程如下&#xff1a; 证明如下&#xff1a; 其中的 lemma 4.9 和 4.10 &#xff0c;及 4.8 是 这整个证明过程…

华为云centos7.9按装ambari 2.7.5 hostname 踩坑记录

华为云centos7.9按装ambari 2.7.5踩坑记录 前言升华总结 前言 一般都是废话&#xff0c;本人专业写bug业余运维。起初找了三台不废弃的台式机&#xff0c;开始重装centos系统&#xff0c;开始了HDP3.1.5Ambari2.7.5安装。 推荐一波好文&#xff0c;一路长绿。跑了一段时间没啥…

2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点

今天带来的是来自尚硅谷禹神2024年8月最新的TS课程的学习笔记&#xff0c;不得不说禹神讲的是真的超级棒&#xff01; 文章目录 TS入门JS中的困扰静态类型检查编译TS命令行编译自动化编译 类型检查变量和函数类型检查字面量类型检查 类型推断类型声明声明对象类型声明函数类型…

深度学习02-pytorch-08-自动微分模块

​​​​​​​ 其实自动微分模块&#xff0c;就是求相当于机器学习中的线性回归损失函数的导数。就是求梯度。 反向传播的目的&#xff1a; 更新参数&#xff0c; 所以会使用到自动微分模块。 神经网络传输的数据都是 float32 类型。 案例1: 代码功能概述&#xff1a; 该…

【Python篇】深入机器学习核心:XGBoost 从入门到实战

文章目录 XGBoost 完整学习指南&#xff1a;从零开始掌握梯度提升1. 前言2. 什么是XGBoost&#xff1f;2.1 梯度提升简介 3. 安装 XGBoost4. 数据准备4.1 加载数据4.2 数据集划分 5. XGBoost 基础操作5.1 转换为 DMatrix 格式5.2 设置参数5.3 模型训练5.4 预测 6. 模型评估7. 超…

重生之我们在ES顶端相遇第14 章 - ES 节点类型

文章目录 前言Coordinating nodeMaster-eligible nodeData nodeCoordinating only nodeRemote-eligible nodeMachine learning node 前言 通过前面的学习&#xff0c;我们已经初步的掌握了 ES 的大部分用法。 后面的篇章会介绍 ES 集群相关的内容。 本文着重介绍 ES 节点类型&…

华为HarmonyOS地图服务 3 - 如何开启和展示“我的位置”?

一. 场景介绍 本章节将向您介绍如何开启和展示“我的位置”功能&#xff0c;“我的位置”指的是进入地图后点击“我的位置”显示当前位置点的功能。效果如下&#xff1a; 二. 接口说明 “我的位置”功能主要由MapComponentController的方法实现&#xff0c;更多接口及使用方法…

软考高级:逻辑地址和物理地址转换 AI解读

一、题目 设某进程的段表如下所示&#xff0c;逻辑地址&#xff08; &#xff09;可以转换为对应的物理地址。 A. &#xff08;0&#xff0c;1597&#xff09;、&#xff08;1&#xff0c;30&#xff09;和&#xff08;3&#xff0c;1390&#xff09; B. &#xff08;0&…

Vue3 中组件传递 + css 变量的组合

文章目录 需求效果如下图所示代码逻辑代码参考 需求 开发一个箭头组件&#xff0c;根据父组件传递的 props 来修改 css 的颜色 效果如下图所示 代码逻辑 代码 父组件&#xff1a; <Arrow color"red" />子组件&#xff1a; <template><div class&…

3DMAX乐高积木插件LegoBlocks使用方法

3DMAX乐高积木插件LegoBlocks&#xff0c;用户可以通过控件调整和自定义每个乐高积木的外观和大小。 【适用版本】 3dMax2009或更高版本&#xff08;不仅限于此范围&#xff09; 【安装方法】 3DMAX乐高积木插件无需安装&#xff0c;使用时直接拖动插件脚本文件到3dMax视口中…

TS 运行环境

1、TS Playground&#xff08;在线&#xff09; TS Playground 是一个在线 TypeScript 编辑器&#xff0c;它允许你编写、共享和学习 TypeScript 代码。 2、Stackblitz&#xff08;在线&#xff09; StackBlitz 是面向web开发人员的基于浏览器的协作IDE。StackBlitz消除了耗时…

握手传输 状态机序列检测(记忆科技笔试题)_2024年9月2日

发送模块循环发送0-7&#xff0c;在每个数据传输完成后&#xff0c;间隔5个clk&#xff0c;发送下一个 插入寄存器打拍处理&#xff0c;可以在不同的时钟周期内对信号进行同步&#xff0c;从而减少亚稳态的风险。 记忆科技笔试题&#xff1a;检测出11011在下一个时钟周期输出…

Python | 读取.dat 文件

写在前面 使用matlab可以输出为 .dat 或者 .mat 形式的文件&#xff0c;之前介绍过读取 .mat 后缀文件&#xff0c;今天正好把 .dat 的读取也记录一下。 读取方法 这里可以使用pandas库将其作为一个dataframe的形式读取进python&#xff0c;数据内容格式如下&#xff0c;根据…

VulnHub-Narak靶机笔记

Narak靶机笔记 概述 Narak是一台Vulnhub的靶机&#xff0c;其中有简单的tftp和webdav的利用&#xff0c;以及motd文件的一些知识 靶机地址&#xff1a; https://pan.baidu.com/s/1PbPrGJQHxsvGYrAN1k1New?pwda7kv 提取码: a7kv 当然你也可以去Vulnhub官网下载 一、nmap扫…

zabbix7.0容器化部署测试--(1)准备容器镜像

本文为zabbix7.0容器化部署测试系统文档之一&#xff0c;准备容器镜像。拟测试数据库后台为PostgreSQL16并启用timescaledb插件。 一、准备数据库容器镜像 因为不确定zabbix7.0对数据库timescaledb插件的版本要求&#xff0c;准备了现个镜像版本 1、准备timescaledb-2.14.2插…

linux 基础(一)mkdir、ls、vi、ifconfig

1、linux简介 linux是一个操作系统&#xff08;os: operating system&#xff09; 中国有没有自己的操作系统&#xff08;华为鸿蒙HarmonyOS&#xff0c;阿里龙蜥(Anolis) OS 8、百度DuerOS都有&#xff09; 计算机组的组成&#xff1a;硬件软件 硬件&#xff1a;运算器&am…