Spring系列文章3:基于注解方式依赖注入

news2024/11/25 10:53:40

和XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作,本质上所有操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行

一、负责声明bean的注解

1、5个声明bean的注解

Spring中使用以下5个注解声明一个bean

@Controller、@Service、@Repository、@Component、@Configuration

查看@Controller、@Service、@Repository三个注解源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    String value() default "";
}

可以看到只是在@Component注解的基础上起了三个新的名字,对于Spring使用IOC容器管理这些组件来说没有区别。Controller、Service、Repository三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

虽然本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。 

  • @Controller:将类标识为控制层组件即用于对 Controller 实现类进行注解
  • @Service:将类标 识为业务层组件即对service层进行注解
  • @Repository:将类标识为持久层组件即用于对 DAO 实现类进行注解
  • @Component:将类标识为普通组件,当前对象不是持久层、业务层、控制层的时候最好使用component
  • @Configuration:如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?使用@Configuration注解定义一个类,并在方法上标记一个@Bean注解

在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识便于在其他地方引用。

使用注解后每个组件仍然应该有一个唯一标识

默认情况类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。

可通过标识组件的注解的value属性设置自定义的bean的id @Service("userService")


2、声明注解如何使用 

加⼊aop的依

我们可以看到当加⼊spring-context依赖之后会关联加⼊aop的依赖

第⼆步在配置⽂件中context命名空间

<?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:context="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 http://www.springframework.org/schema/context/spring-context.xsd">
    
</beans>

在配置⽂件中定要描的包 

 扫描指定包下所有被注解标注的组件

<?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:context="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 http://www.springframework.org/schema/context/spring-context.xsd">
    <!--方式1:只指定具体的包-->
    <context:component-scan base-package="com.springcode.example.controller"></context:component-scan>
    <!--方式2:也可以多个包,逗号隔开-->
    <context:component-scan base-package="com.springcode.example.controller,com.springcode.example.entity"></context:component-scan>
    <!--方式3:指定共同的父包-->
    <context:component-scan base-package="com.springcode.example"></context:component-scan>
</beans>

 排除指定组件

<context:component-scan base-package="com.demo">
<!-- context:exclude-filter标签:指定排除规则 -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:exclude-filter type="annotation"
        expression="org.springframework.stereotype.Controller"/>
    <!--<context:exclude-filter type="assignable"
    expression="com.demo.controller.UserController"/>-->
</context:component-scan>

 仅扫描指定组件

<context:component-scan base-package="com.demo" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable"
expression="com.demo.controller.UserController"/>-->
</context:component-scan>

Bean使⽤注解 

@Controller
public class UserController {
}

 编写试程序

public class SpringTest {
    @Test
    public void testFirst() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        UserController user   = applicationContext.getBean("userController", UserController.class);
        System.out.println(user);
    }
}

3、方法注解

 可以通过上面注解声明一个类作为一个bean,也可以声明一个方法作为一个bean,如下

@Configuration //也可以用@Component
public class MyBean {
    //使用bean注解声明一个方法返回值作为一个bean,bean的名字是方法名
    @Bean
    public User userInfo(){
        return new User();
    }
}

测试

public class SpringTest {
    @Test
    public void testFirst() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        User user   = applicationContext.getBean("userInfo", User.class);
        System.out.println(user);
    }
}

4、 @Configuration和@Component区别

如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?使用@Configuration注解定义一个类,并在方法上标记一个@Bean注解:

@Configuration
public class AppConfig {
    // 创建一个Bean:
    @Bean
    ZoneId createZoneId() {
        return ZoneId.of("Z");
    }
}

查看@Configuration源码本质是使用@Component进行元注解,因此 <context:component-scan/> 或者 @ComponentScan 都能处理@Configuration 注解的类。

Spring @Configuration 和 @Component 区别,一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

@Configuration
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}

相信大多数人第一次看到上面 userInfo() 中调用 country() 时,会认为这里的 Country 和上面 @Bean 方法返回的 Country 可能不是同一个对象,因此可能会通过下面的方式来替代这种方式:

@Autowired
private Country country;

实际上不需要这么做(后面会给出需要这样做的场景),直接调用 country() 方法返回的是同一个实例。
但是@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,因此像下面这样配置时,就是两个不同的 country。

@Component
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}

二、负责注入的注解

上面注解声明后,如何给Bean的属性赋值?Bean属性赋需要⽤到

@Value@Autowired@Qualifier@Resource

1、@value

对于简单属性注入可以使用@value赋值

@Component
public class User {
    @Value(value = "tom")
    private String name;
    @Value("18")
    private int age;
    
    public User(){
        System.out.println("无参构造被调用");
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试

public class SpringTest {
    @Test
    public void testFirst() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        User user   = applicationContext.getBean("user", User.class);
        System.out.println(user);//User{name='tom', age=18}
    }
}

 通过代码可以发现我们并有给属性提供setter但仍然可以完成属性赋值。 如果提供settersetter@Value可以完成⼊吗

@Component
public class User {

    private String name;

    private int age;
    @Value(value = "tom")
    public void setName(String name) {
        this.name = name;
    }
    @Value("18")
    public void setAge(int age) {
        this.age = age;
    }

    public User(){
        System.out.println("无参构造被调用");
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通过测试可以得知@Value解可以直接使⽤属性上,也可以使⽤setter是可以的。都可以完成属性的赋值。

为了简化代码以后我们提供setter直接属性使⽤@Value解完成属性赋值。

是否能够通过完成 

@Component
public class User {
    private String name;
    private int age;

    public User(@Value("tome") String name,@Value("18") int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通过测试得知@Value解可以出现属性setter以及构的形参可⻅Spring给我们提供了多样化的 

2、@Autowired与@Qualifier

使用@Autowired完成引用类型装配,单独使⽤@Autowired注解,默认根据类型装配。【默认是byType】

源码如下

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETE
R, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}

 源码理解1、该注解可以标注在哪⾥?

  • 构造⽅法上
  • ⽅法上
  • 形参上
  • 属性上
  • 注解上

源码理解2、该注解有⼀个required属性,默认值是true,表示在注⼊的时候要求被注⼊的Bean必须是 存在的,如果不存在则报错。如果required属性设置为false,表示注⼊的Bean存在或者不存在都没 关系,存在的话就注⼊,不存在的话,也不报错

1、在属性上使⽤@Autowired注解

在成员变量上直接标记@Autowired注解即可完成自动装配,如在controller中装配service层bean

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    
    public void saveUser(){
        userService.saveUser();
    }
}

2、可以标记在set方法上

@Service
public class UserService {
     private UserDao userDao;
     @Autowired
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
     userDao.insert();
     }
}

 

3、@Autowired注解还可以标记在构造器

@Controller
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}

4、标注在构造⽅法的形参上 

@Service
public class UserService {
     private UserDao userDao;
     public UserService(@Autowired UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
         userDao.insert();
     }
}

并且当有参数的构造⽅法只有⼀个时,@Autowired注解可以省略

@Service
public class UserService {
     private UserDao userDao;
     public UserService(UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
         userDao.insert();
     }
}

 如果有多个构造⽅法,@Autowired肯定是不能省略的。如下测试会报错

@Service
public class UserService {
     private UserDao userDao;
     public UserService(UserDao userDao) {
         this.userDao = userDao;
     }
 
     public UserService(){
 
     }
     public void save(){
         userDao.insert();
     }
}

@Autowired注解默认是byType进⾏注⼊的,也就是说根据类型注⼊的,如果以上程序中,UserDao接⼝ 还有另外⼀个实现类,会出现问题吗?

可以测试是不能装配的,提示UserDao这个Bean的数量⼤于1. 怎么解决这个问题呢?当然要byName,根据名称进⾏装配了。 @Autowired注解和@Qualifier注解联合起来才可以根据名称进⾏装配,在@Qualifier注解中指定Bean名 称。

@Service
public class UserService {
     private UserDao userDao;
     @Autowired
     @Qualifier("userDaoTwo") // 这个是bean的名字。
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
         userDao.insert();
     }
}

 @Autowired工作流程

首先根据所需要的组件类型到IOC容器中查找,唯一直接返回,存在多个

        没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行 匹配 能够找到:执行装配 找不到:装配失败

        使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配 能够找到:执行装配 找不到:装配失败 

@Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装 配失败 可以将属性required的值设置为true,则表示能装就装,装不上就不装,此时自动装配的属性为 默认值

3、@Resource

@Resource注解也可以完成⾮简单类型注⼊。那它和@Autowired注解有什么区别?

@Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具 有通⽤性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)

  • @Autowired注解是Spring框架⾃⼰的。
  • @Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过name 找不到的话会⾃动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起 ⽤。
  • @Resource注解⽤在属性上、setter⽅法上。
  • @Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。
  • @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引⼊以下依赖:【如果是JDK8的话不需 要额外引⼊依赖。⾼于JDK11或低于JDK8需要引⼊以下依赖。】
<dependency>
 <groupId>jakarta.annotation</groupId>
 <artifactId>jakarta.annotation-api</artifactId>
 <version>2.1.1</version>
</dependency>

注意:如果你⽤Spring6,要知道Spring6不再⽀持JavaEE,它⽀持的是JakartaEE9。(Oracle 把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,⼤家之前所接触的所有的 javax.* 包名统⼀修改为 jakarta.*包名了。)

如果你是spring5-版本请使⽤这个依赖

<dependency>
 <groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
 <version>1.3.2</version>
</dependency>

 @Resource注解的源码如下:

使用

@Service
public class UserService {
     @Resource
     private UserDao userDao;
     public void save(){
         userDao.insert();
     }
}

 @Resource注解也可以使⽤在setter⽅法上

@Service
public class UserService {
     private UserDao userDao;
     @Resource
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
         userDao.insert();
     }
}

注意这个setter⽅法的⽅法名,setUserDao去掉set之后,将⾸字⺟变⼩写userDao,userDao就是name

当然,也可以指定name:

@Service
public class UserService {
     private UserDao userDao;
     @Resource(name = "userDaoForMySQL")
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
     public void save(){
         userDao.insert();
     }
}

 ⼀句话总结@Resource注解:默认byName注⼊,没有指定name时把属性名当做name,根据name找不 到时,才会byType注⼊。byType注⼊时,某种类型的Bean只能有⼀个。

三、全注解开发

所谓的全注解开发就是不再使⽤spring配置⽂件了。写⼀个配置类来代替配置⽂件。

@Configuration
@ComponentScan({"com.spring6demo.spring6.dao", "com.spring6demo.service"})
public class Spring6Configuration {
}

编写测试程序:不再new ClassPathXmlApplicationContext()对象了。

@Test
public void testNoXml(){
 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
 UserService userService = applicationContext.getBean("userService", Use
rService.class);
 userService.save();
}

四、注解和xml的对比

实际开发中以注解为主, xml为辅

五、IOC(依赖注入)使用总结

IOC解决的是业务逻辑对象之间的耦合关系,也就是service和dao之家的解耦合

spring容器适合管理对象

  • service层、dao层、controller对象、工具类对象

不适合管理管理对象

  • 实体类对象
  • servle、listener、filter等web中的对象,他们是tomcat创建和管理的

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

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

相关文章

windows安装Oracle19c安装

windows安装Oracle19c安装 百度云安装包地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/11XvBN8Zqb7jbhugP88IHRw 提取码&#xff1a;ybsy 官网下载地址&#xff1a;https://www.oracle.com/database/technologies/oracle-database-software-downloads.html 下载…

基于vue3和element-plus的省市区级联组件

git地址&#xff1a;https://github.com/ht-sauce/elui-china-area-dht 使用:npm i elui-china-area-dht 默认使用 使用方法 <template><div class"app"><!--默认使用--><elui-china-area-dht change"onChange"></elui-china…

【android12-linux-5.1】【ST芯片】【RK3588】【LSM6DSR】HAL源码分析

一、环境介绍 RK3588主板搭载Android12操作系统,内核是Linux5.10,使用ST的六轴传感器LSM6DSR芯片。 二、芯片介绍 LSM6DSR是一款加速度和角速度(陀螺仪)六轴传感器,还内置了一个温度传感器。该芯片可以选择I2C,SPI通讯,还有可编程终端,可以后置摄像头等设备,功能是很…

MySQL阻塞与死锁

MySQL阻塞与死锁 阻塞 因为不同锁之间的兼容性关系&#xff0c;在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源&#xff0c;这就是阻塞。 # 查看等待时间 show variables like innodb_lock_wait_timeout; SETinnodb_lock_wait_timeout60; # 是否在等待…

深度学习面试八股文(2023.9.06持续更新)

一、优化器 1、SGD是什么&#xff1f; 批梯度下降&#xff08;Batch gradient descent&#xff09;&#xff1a;遍历全部数据集算一次损失函数&#xff0c;计算量开销大&#xff0c;计算速度慢&#xff0c;不支持在线学习。随机梯度下降&#xff08;Stochastic gradient desc…

【HTML/CSS】入门导学篇

本文属于HTML/CSS专栏文章&#xff0c;适合WEB前端开发入门学习&#xff0c;如果有所帮助请一键三连支持&#xff0c;对博主系列文章感兴趣点击下方专栏了解详细。 本文内容出自B站pink老师的前端入门教程&#xff0c;感谢pink老师&#xff01;&#xff01;&#xff01; 视频链…

【ARM CoreLink 系列 1 -- CoreLink 系列 产品介绍】

文章目录 ARM CoreLink 介绍ARM CoreLink InterconnectARM CoreLink 处理器外设ARM CoreLink Memory Controllers ARM CoreLink 介绍 ARM的CoreLink系列产品是一套能够进行高效互联的组件和工具&#xff0c;它们用于构建高性能、低功耗的嵌入式和消费电子设备。CoreLink产品系…

Mac MySQL初始登录root报错access denied解决方法

如图&#xff0c;当在mac m2上首次安装mysql后尝试登录root用户时&#xff0c;无论输入什么样的密码&#xff0c;或者直接回车键&#xff0c;都会显示access denied for user rootlocalhost。同时win和ubuntu也出现了一模一样的问题&#xff0c;先记录一下mac的解决方法。 参考…

Sigrity仿真报错:找不到电容的S参数?

很多电子工程师会选择使用Sigrity软件进行高速PCB信号仿真&#xff0c;但在使用过程可能会遇见报错情况&#xff0c;其中之一是提示找不到电容的S参数&#xff0c;那么如何解决这个问题&#xff1f; 一般来说&#xff0c;S参数的全称为Scatter参数&#xff0c;即散射参数&#…

STM32单片机OLED贪吃蛇游戏记分计时

实践制作DIY- GC00165---OLED贪吃蛇游戏 一、功能说明&#xff1a; 基于STM32单片机设计---OLED贪吃蛇游戏 二、功能说明&#xff1a; STM32F103C系列最小系统板0.96寸OLED显示器上、下、左、右4个按键 1.通过OLED配合按键实现贪吃蛇游戏 2.可以上下左右移动。 3.可以统计显…

JavaScript-----DOM元素

目录 前言&#xff1a; 1. DOM介绍 2. 获取节点 3. 操作HTML内容 4. 监听事件 案例 5. 操作节点的标签属性 6. 操作样式 7. 创建、添加、删除节点 前言&#xff1a; 在此之前我们要想去操作网页元素一般是去通过CSS选择器实现的&#xff0c;今天我们就学习JavaScript里…

bit、bin 、mcs文件区别

FPGA里面的可执行文件都涉及到 *.bit&#xff0c; *.mcs&#xff0c; *.bin 和 *.elf。 bit文件 bit 文件一般用于JTAG在线进行调试的时候&#xff0c;是把bit文件是烧写到FPGA中进行在线调试。 bin文件 bin 文件是二进制文件&#xff0c;按顺序只包含原始字节流&#xff0c…

74HC595

简介 74HC595 是一个 8 位串行输入、并行输出的位移缓存器&#xff0c;其中并行输出为三 态输出&#xff08;即高电平、低电平和高阻抗&#xff09; 15 和 1 到 7 脚 QA--QH&#xff1a;并行数据输出 9 脚 QH 非&#xff1a;串行数据输出 10 脚 SCLK 非&#xff08; MR&#…

【UE 材质】制作飘动的旗帜

效果 步骤 1. 首先在建模软件中创建一个平面&#xff0c;注意分段数一定要多 2. 在UE中创建一个材质&#xff0c;这里命名为“Mat_Flag” 打开“Mat_Flag”&#xff0c;先将旗帜纹理连接到基础颜色 先选中导入的模型然后点击根据选中的模型预览材质 创建如下节点可以看到此时模…

本地开发环境大小写不敏感引发的问题

推荐阅读 大小写敏感知多少[1] 文件大小写与文件系统有关&#xff0c;与操作系统无关。 血泪教训--拿到新移动硬盘该做的第一件事[2] MacOS 默认的文件系统APFS&#xff0c;如果没有手动更改&#xff0c;默认不区分大小写(可以在初始化时设置为大小写敏感)。 而公司测试/生产环…

AOSP和AAOS,向左走还是向右走?

Android Automotive OS&#xff08;AAOS&#xff09;和Android Open-Source Project&#xff08;AOSP&#xff09;之间存在很多混乱。这两个Android版本经常被混淆使用&#xff0c;但它们代表不同的平台和方法。为汽车In-Vehicle Infotainment&#xff08;IVI&#xff09;项目选…

2023CSP-S初赛复习整理

目录 逻辑运算常见运算运算规则运算优先级例题 进制转换十进制转二进制、八进制、十六进制二进制、八进制、十六进制转十进制例题 主定理编程语言概况具体分类 逻辑运算 常见运算 非&#xff1a; n o t not not 即 与&#xff1a; a n d and and 即 ∧ ∧ ∧ 或&#xf…

依赖项的处理与层的创建与注册

依赖项的处理与层的创建与注册 依赖项的处理与层的创建与注册 新问题什么是 layer?layer 的创建与注册 与函数同时创建和绑定单独上传 layer 再绑定函数(推荐) 真正的运行时依赖 注册包的约定与平台强关联的运行时 1. 云端安装依赖2. 本地构建 Amazon Linux 2 容器环境3. 利用…

Nginx__高级进阶篇

目录 Nginx Web服务器 Nginx Proxy服务器 Nginx 邮 件 Nginx Web服务器 Nginx Proxy服务器 代理原理 正向代理&#xff1a;内网客户机通过代理访问互联网。通常要设置代理服务器地址和端口。 反向代理&#xff1a;外网用户通过代理访问内网服务器。内…

解决VSCode下载速度特别慢

背景: 我们在vscode官网下载vocode的时候速度慢得离谱,而且下载会断开,这时候我们需要将下载镜像切换到国内&#xff01; 解决方法 我是用chrome浏览器&#xff0c;点击右上角的三个点按钮&#xff0c;点击下载内容。 如果你是用其他浏览器&#xff0c;可能使用其他方式跳…