Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Spring注解-@Autowired注解使用

Spring注解-@Autowired注解使用

作者头像
SerMs
发布于 2022-04-11 07:09:58
发布于 2022-04-11 07:09:58
1K00
代码可运行
举报
文章被收录于专栏:SerMsBlogSerMsBlog
运行总次数:0
代码可运行

写在前面得话

学习@Autowired之前建议先学会使用byType和byName

Spring的自动装配

https://hgm.vercel.app/post/63755f3a/

@Autowired详解

首先要知道另一个东西,default-autowire,它是在xml文件中进行配置的,可以设置为byName、byType、constructor和autodetect;比如byName,不用显式的在bean中写出依赖的对象,它会自动的匹配其它bean中id名与本bean的set**相同的,并自动装载。 @Autowired是用在JavaBean中的注解,通过byType形式,用来给指定的字段或方法注入所需的外部资源。 两者的功能是一样的,就是能减少或者消除属性或构造器参数的设置,只是配置地方不一样而已。 autowire四种模式的区别

先看一下bean实例化和@Autowired装配过程: 一切都是从bean工厂的getBean方法开始的,一旦该方法调用总会返回一个bean实例,无论当前是否存在,不存在就实例化一个并装配,否则直接返回。(Spring MVC是在什么时候开始执行bean的实例化过程的呢?其实就在组件扫描完成之后)

实例化和装配过程中会多次递归调用getBean方法来解决类之间的依赖。

Spring几乎考虑了所有可能性,所以方法特别复杂但完整有条理。

@Autowired最终是根据类型来查找和装配元素的,但是我们设置了后会影响最终的类型匹配查找。因为在前面有根据BeanDefinition的autowire类型设置PropertyValue值得一步,其中会有新实例的创建和注册。就是那个autowireByName方法。

下面通过@Autowired来说明一下用法

Setter 方法中的 @Autowired

你可以在 JavaBean中的 setter 方法中使用 @Autowired 注解。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注解,它会在方法中执行 byType 自动装配。 这里是 TextEditor.java 文件的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
    private SpellChecker spellChecker;
    @Autowired
    public void setSpellChecker( SpellChecker spellChecker ){
        this.spellChecker = spellChecker;
    }
    public SpellChecker getSpellChecker( ) {
        return spellChecker;
    }
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}

下面是另一个依赖的类文件 SpellChecker.java 的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
public class SpellChecker {
    public SpellChecker(){
        System.out.println("Inside SpellChecker constructor." );
    }
    public void checkSpelling(){
        System.out.println("Inside checkSpelling." );
    }  
}

下面是 MainApp.java 文件的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor) context.getBean("textEditor");
        te.spellCheck();
    }
}

下面是配置文件 Beans.xml:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>

    <!-- Definition for textEditor bean without constructor-arg  -->
    <bean id="textEditor" class="com.tutorialspoint.TextEditor">
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
    </bean>

</beans>

一旦你已经完成的创建了源文件和 bean 配置文件,让我们运行一下应用程序。如果你的应用程序一切都正常的话,这将会输出以下消息:

Inside SpellChecker constructor. Inside checkSpelling.

属性中的 @Autowired

你可以在属性中使用 @Autowired 注解来除去 setter 方法。当时使用 为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。所以利用在属性中 @Autowired 的用法,你的 TextEditor.java 文件将变成如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
    @Autowired
    private SpellChecker spellChecker;
    public TextEditor() {
        System.out.println("Inside TextEditor constructor." );
    }  
    public SpellChecker getSpellChecker( ){
        return spellChecker;
    }  
    public void spellCheck(){
        spellChecker.checkSpelling();
    }
}

下面是配置文件 Beans.xml:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>

    <!-- Definition for textEditor bean -->
    <bean id="textEditor" class="com.tutorialspoint.TextEditor">
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
    </bean>

</beans>

一旦你在源文件和 bean 配置文件中完成了上面两处改变,让我们运行一下应用程序。如果你的应用程序一切都正常的话,这将会输出以下消息:

Inside TextEditor constructor. Inside SpellChecker constructor. Inside checkSpelling.

构造函数中的 @Autowired

你也可以在构造函数中使用 @Autowired。一个构造函数 @Autowired 说明当创建 bean 时,即使在 XML 文件中没有使用 元素配置 bean ,构造函数也会被自动连接。让我们检查一下下面的示例。 这里是 TextEditor.java 文件的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
    private SpellChecker spellChecker;
    @Autowired
    public TextEditor(SpellChecker spellChecker){
        System.out.println("Inside TextEditor constructor." );
        this.spellChecker = spellChecker;
    }
    public void spellCheck(){
        spellChecker.checkSpelling();
    }
}

下面是配置文件 Beans.xml:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>

    <!-- Definition for textEditor bean without constructor-arg  -->
    <bean id="textEditor" class="com.tutorialspoint.TextEditor">
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
    </bean>

</beans>

一旦你在源文件和 bean 配置文件中完成了上面两处改变,让我们运行一下应用程序。如果你的应用程序一切都正常的话,这将会输出以下消息:

Inside TextEditor constructor. Inside SpellChecker constructor. Inside checkSpelling.

@Autowired 的(required=false)选项

默认情况下,@Autowired 注解意味着依赖是必须的,它类似于 @Required 注解,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。 即使你不为 age 属性传递任何参数,下面的示例也会成功运行,但是对于 name 属性则需要一个参数。你可以自己尝试一下这个示例,因为除了只有 Student.java 文件被修改以外,它和 @Required 注解示例是相似的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tutorialspoint;
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
    private Integer age;
    private String name;
    @Autowired(required=false)
    public void setAge(Integer age) {
        this.age = age;
    }  
    public Integer getAge() {
        return age;
    }
    @Autowired
    public void setName(String name) {
        this.name = name;
    }   
    public String getName() {
        return name;
    }
}

其他

@Autowired注释应用于具有任意名称和多个参数的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
                        CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

您也可以将@Autowired应用于字段,或者将其与构造函数混合,如以下示例所示

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

将@Autowired注释添加到需要该类型数组的字段或方法,则spring会从ApplicationContext中搜寻符合指定类型的所有bean,如以下示例所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

数组可以,我们可以马上举一反三,那容器也可以吗,答案是肯定的,下面是set以及map的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MovieRecommender {
 
    private Map<String, MovieCatalog> movieCatalogs;
 
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
 
    // ...
}

以上就是@Autowired注解的主要使用方式,经常使用spring的话应该对其中常用的几种不会感到陌生。

@Autowired装配不成功的几种情况?

没有加@Component注解

在类上面忘了加@Controller、@Service、@Component、@Repository等注解,spring就无法完成自动装配的功能,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class UserService {
    @Autowired    
    private IUser user;    
    public void test() {        
        user.say();    
    }
}

这种情况应该是最常见的错误了,不会因为你长得帅,就不会犯这种低级的错误。

注入Filter或Listener

web应用启动的顺序是:listener->filter->servlet。

接下来看看这个案例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class UserFilter implements Filter {
    @Autowired
    private IUser user;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        user.say();
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    }
    @Override
    public void destroy() {
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        bean = new FilterRegistrationBean();
        bean.setFilter(new UserFilter());
        bean.addUrlPatterns("/*");
        return bean;
    }
}

程序启动会报错,tomcat也无法正常启动???什么原因??

众所周知,springmvc的启动是在DisptachServlet里面做的,而它是在listener和filter之后执行。如果我们想在listener和filter里面@Autowired某个bean,肯定是不行的,因为filter初始化的时候,此时bean还没有初始化,无法自动装配。

如果工作当中真的需要这样做,我们该如何解决这个问题呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class UserFilter  implements Filter {
    private IUser user;    

    @Override    
    public void init(FilterConfig filterConfig) throws ServletException {        
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(
            filterConfig.getServletContext());        
        this.user = ((IUser)(applicationContext.getBean("user1")));        
        user.say();    
    }    

    @Override    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  

    }   

    @Override    
    public void destroy() {  

    }
}

答案是使用 WebApplicationContextUtils.getWebApplicationContext获取当前的ApplicationContext,再通过它获取到bean实例。

注解未被@ComponentScan扫描

通常情况下,@Controller、@Service、@Component、@Repository、@Configuration等注解,是需要通过@ComponentScan注解扫描,收集元数据的。

但是,如果没有加@ComponentScan注解,或者@ComponentScan注解扫描的路径不对,或者路径范围太小,会导致有些注解无法收集,到后面无法使用@Autowired完成自动装配的功能。

有个好消息是,在springboot项目中,如果使用了@SpringBootApplication注解,它里面内置了@ComponentScan注解的功能。

循环依赖问题

如果A依赖于B,B依赖于C,C又依赖于A,这样就形成了一个死循环。

spring的bean默认是单例的,如果单例bean使用@Autowired自动装配,大多数情况,能解决循环依赖问题。

但是如果bean是多例的,会出现循环依赖问题,导致bean自动装配不了。

还有有些情况下,如果创建了代理对象,即使bean是单例的,依然会出现循环依赖问题。

@Autowired和@Resouce的区别

@Autowired功能虽说非常强大,但是也有些不足之处。比如:比如它跟spring强耦合了,如果换成了JFinal等其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。

除此之外,有些场景使用@Autowired无法满足的要求,改成@Resource却能解决问题。接下来,我们重点看看@Autowired和@Resource的区别。

  • @Autowired默认按byType自动装配,而@Resource默认byName自动装配。
  • @Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
  • @Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
  • @Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
  • @Autowired是spring定义的注解,而@Resource是JSR-250定义的注解。

此外,它们的装配顺序不同。

@Autowired的装配顺序如下:

@Resource的装配顺序如下:

1.如果同时指定了name和type:

如果指定了name:

如果指定了type:

如果既没有指定name,也没有指定type:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
iOS block探究(二): 深入理解你要知道的block都在这里
你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 三种block类型 NSGlobalBlock 如果block不捕获外部变量,那么在ARC环境下就是创建一个全局block。全局block存储在全局内存中,不需要在每次调用的时候都在栈中创建,块所使用的整个内存区在编译期已经确定了,因此这种块是一种单例,不需要多次创建。 NSMallocBlock 如果block捕获外部变量,那么在ARC环境下就是创了一个堆区blo
WWWWDotPNG
2018/04/10
1K0
Block那些事
__main_block_func_0这个是block的执行函数,参数是__main_block_impl_0类型。
Helloted
2022/06/07
4340
Block那些事
3. __block  __weak  __strong   这都是做什么的
1.1 局部变量 局部自动变量,在Block中可被读取。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值,Block此时对局部变量只是做了值传递的操作。 1.2 static 修饰的全局变量 因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存地址读出,获取到的是最新值,而不是在定义时copy的常量。 1.3 对OC对象的截获 NSMutableArray *array = [NSMut
陈雨尘
2018/06/07
6010
深入研究Block捕获外部变量和__block实现原理
Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现在iOS和Mac系统各个API中,并被大家广泛使用。一句话来形容Blocks,带有自动变量(局部变量)的匿名函数。
一缕殇流化隐半边冰霜
2018/08/30
1.2K0
深入研究Block捕获外部变量和__block实现原理
Block原理探究(下篇)-捕获变量分析及__block原理
主要内容: 1.分析Block捕获外部变量的过程 2.理解Block修改外部变量的限制 3.分析__block存储域类说明符的原理 4.理解__block变量的存储域 5.探究Block对对象的捕获过程 6.Block的循环引用问题
梧雨北辰
2019/10/08
1.7K0
Block原理探究(下篇)-捕获变量分析及__block原理
iOS-从循环引用看Block
说明:在block区内已经释放,到self.block()调用时已经被释放,所以值为null。
Wilbur-L
2020/12/15
1.2K0
iOS-从循环引用看Block
《Objective-C-高级编程》干货三部曲(二):Blocks篇
这一章讲解了Block相关的知识。因为作者将Objective-C的代码转成了C++的代码,所以第一次看的时候非常吃力,我自己也不记得看了多少遍了。
用户1740424
2018/07/23
1.1K0
《Objective-C-高级编程》干货三部曲(二):Blocks篇
利用Clang探究__block的本质
上一篇文章利用Clang探究block的本质得出的结论是block的本质是一个结构体对象,该对象包含调用block时要执行的函数指针。当我们在Objective-C层面调用block的时候,底层就可以通过调用block对应的C++结构体对象的函数指针来实现同等操作。
VV木公子
2019/11/04
7.3K0
iOS底层原理总结 - 探寻block的本质(二)
上一篇文章iOS底层原理总结 - 探寻block的本质(一)中已经介绍过block的底层本质实现以及了解了变量的捕获,本文继续探寻block的本质。 block对对象变量的捕获 block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量相同吗? 查看一下代码思考:当在block中访问的为对象类型时,对象什么时候会销毁? typedef void (^Block)(void); int main(int argc, const char * argv[]) { @autore
xx_Cc
2018/06/04
1K0
老司机出品——源码解析之从Block说开去
近来把《iOS与OS X多线程和内存管理》这本书又掏出来看了一遍,这本书前前后后加起来看了能有三四遍了,每次看都有新的理解。现在就把个人对Block的一些理解记录下来。
老司机Wicky
2018/08/22
4650
老司机出品——源码解析之从Block说开去
iOS 开发:『Blocks』详尽总结 (二)底层原理
在第一篇中我们讲解了 Blocks 的基本使用,也知道了 Blocks 是 带有局部变量的匿名函数。但是 Block 的实质究竟是什么呢?类型?变量?还是什么黑科技?
程序员充电站
2019/04/01
7080
iOS 开发:『Blocks』详尽总结 (二)底层原理
面试驱动技术 - Block看我就够了【干货】
Block 在 iOS 算比较常见常用且常考的了,现在面试中,要么没面试题,有面试题的,基本都会考到 block 的点。本文特别干!(但是初中级iOSer应该能有所收获~)
小蠢驴打代码
2019/03/15
7890
面试驱动技术 - Block看我就够了【干货】
iOS Block的本质(四)
typedef void (^Block)(void); struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void); }; struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; struct __Block_byref_age_0 *age; // by ref }; int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; Block block = ^{ age = 20; NSLog(@"age is %d",age); }; block(); struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block; NSLog(@"%p",&age); } return 0; }
用户1941540
2019/02/15
6940
iOS Block的本质(四)
【IOS开发高级系列】Block专题
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
江中散人_Jun
2023/10/16
3170
【IOS开发高级系列】Block专题
iOS 进阶之 Block 的本质及原理
相信稍微有点开发经验的开发者,应该都对 block 有一定的了解。刚开始使用 block 的时候,可能觉得很方便。知道怎么去用,但是知其然知其所以然的态度来分析一下 block 的原理和内部结构是什么。
网罗开发
2021/01/29
6710
iOS 进阶之 Block 的本质及原理
block内部实现
c. 我们先看一下__ main_block_impl_0结构体和 __ main_block_func_0 还有__main_block_desc_0_DATA都代表写什么
老沙
2019/09/27
4140
《Objective-C高级编程》温故知新之"Blocks"
在计算机科学中,此概念也称为闭包(Closure)、lambda计算等。Swift中称作闭包
Dwyane
2018/09/26
6340
# iOS Block的本质(三)
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__weak person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
用户1941540
2019/02/15
4840
# iOS Block的本质(三)
Block原理详解(一)
今天这篇文章也是在前面这三篇文章的基础上,再结合自己最近的一些工作感悟,进行内容的完善。
拉维
2021/04/16
6260
Block原理探究(上篇)-Block本质及存储域问题
主要内容: 1.理解Block的本质 2.理解Block的存储域分类 3.理解Block的Copy原理
梧雨北辰
2019/10/08
1K0
相关推荐
iOS block探究(二): 深入理解你要知道的block都在这里
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验