`
dreamoftch
  • 浏览: 505099 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

applicationContext.xml中<context:annotation-config> 和 <context:component-scan>的区别

阅读更多

Difference between <context:annotation-config> vs <context:component-scan>

 

<context:annotation-config> 是用于激活那些已经在spring容器里注册过的bean(无论是

通过xml的方式还是通过package sanning的方式)上面的注解。

<context:component-scan>除了具有<context:annotation-config>的功能之外,

<context:component-scan>还可以在指定的package下扫描以及

注册javabean 。

 

下面我们通过例子来详细查看他们的区别,

有三个class   A,B,C,并且B,C的对象被注入到A中.

 

package com.xxx;
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
public class C {
  public C() {
    System.out.println("creating bean C: " + this);
  }
}

package com.yyy;
import com.xxx.B;
import com.xxx.C;
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc; 
  }
}

 

 

在applicationContext.xml中加入下面的配置 :

 

<bean id="bBean"class="com.xxx.B"/>
<bean id="cBean"class="com.xxx.C"/>
<bean id="aBean"class="com.yyy.A">
   <property name="bbb" ref="bBean"/>
   <property name="ccc" ref="cBean"/>
</bean>
  

 

 

 

加载applicationContext.xml配置文件,将得到下面的结果:

 

creating bean B: com.xxx.B@c2ff5
creating bean C: com.xxx.C@1e8a1f6
creating bean A: com.yyy.A@1e152c5
setting A.bbb with com.xxx.B@c2ff5
setting A.ccc with com.xxx.C@1e8a1f6
 

 

 

 

OK, 这个结果没什么好说的,就是完全通过xml的方式,不过太过时了,下面通过注解的方式来简化我们的xml配置文件

首先,我们使用autowire的方式将对象bbb和ccc注入到A中:

 

 

package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.B;
import com.xxx.C;
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc;
  }
}
 

 

 

然后,我们就可以从applicationContext.xml中移除下面的配置

 

<property name="bbb" ref="bBean"/>
<property name="ccc" ref="cBean"/>

 

 

 

移除之后,我们的applicationContext.xml配置文件就简化为下面的样子了

<bean id="bBean"class="com.xxx.B"/>
<bean id="cBean"class="com.xxx.C"/>
<bean id="aBean"class="com.yyy.A"/>

  

 

 

当我们加载applicationContext.xml配置文件之后,将得到下面的结果:

 

creating bean B: com.xxx.B@5e5a50
creating bean C: com.xxx.C@54a328
creating bean A: com.yyy.A@a3d4cf

 

OK, 结果是错误的的,究竟是因为什么呢?为什么我们的属性没有被注入进去呢?

是因为注解本身并不能够做任何事情,它们只是最基本的组成部分,我们需要能够处理这些注解的处理工具来处理这些注解

这就是<context:annotation-config> 所做的事情

我们将applicationContext.xml配置文件作如下修改:

 

<context:annotation-config />
<bean id="bBean"class="com.xxx.B"/>
<bean id="cBean"class="com.xxx.C"/>
<bean id="aBean"class="com.yyy.A"/>

  

 

 

当我们加载applicationContext.xml配置文件之后,将得到下面的结果:

 

creating bean B: com.xxx.B@15663a2
creating bean C: com.xxx.C@cd5f8b
creating bean A: com.yyy.A@157aa53
setting A.bbb with com.xxx.B@15663a2
setting A.ccc with com.xxx.C@cd5f8b

 

OK, 结果正确了

但是如果我们将代码作如下修改:

 

 

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class C {
  public C() {
    System.out.println("creating bean C: " + this);
  }
}

package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xxx.B;
import com.xxx.C;
@Component
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc;
  }
}
 

 

 

 

applicationContext.xml配置文件修改为:

 

<context:annotation-config />

 

当我们加载applicationContext.xml配置文件之后,却没有任何输出,这是为什么呢?

那是因为<context:annotation-config />仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。

但是不用担心,<context:component-scan>除了具有<context:annotation-config />的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到

spring容器中的功能。

我们将applicationContext.xml配置文件作如下修改:

 

<context:component-scan base-package="com.xxx"/>

 

当我们加载applicationContext.xml的时候,会得到下面的结果:

 

creating bean B: com.xxx.B@1be0f0a
creating bean C: com.xxx.C@80d1ff

 

这是什么原因呢?

是因为我们仅仅扫描了com.xxx包及其子包的类,而class  A是在com.yyy包下,所以就扫描不到了

下面我们在applicationContext.xml中把com.yyy也加入进来:

 

<context:component-scan base-package="com.xxx"/>

<context:component-scan base-package="com.xxx,com.yyy"/>
然后加载applicationContext.xml就会得到下面的结果:
creating bean B: com.xxx.B@cd5f8b
creating bean C: com.xxx.C@15ac3c9
creating bean A: com.yyy.A@ec4a87
setting A.bbb with com.xxx.B@cd5f8b
setting A.ccc with com.xxx.C@15ac3c9

 

哇,结果正确啦 !

回头看下我们的applicationContext.xml文件,已经简化为:

 

<context:component-scan base-package="com.xxx"/>

<context:component-scan base-package="com.xxx,com.yyy"/>

 

了。

 

那如果我们在applicationContext.xml手动加上下面的配置,也就是说既在applicationContext.xml中手动的注册了A的实例对象,同时,通过component-scan去扫描并注册B,C的对象

 

<context:component-scan base-package="com.xxx"/><bean id="aBean"class="com.yyy.A"/>

 

结果仍是正确的:

 

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

 

虽然class  A并不是通过扫描的方式注册到容器中的 ,但是<context:component-scan> 所产生的的处理那些注解的处理器工具,会处理所有绑定到容器上面的bean,

不管是通过xml手动注册的还是通过scanning扫描注册的。

那么,如果我们通过下面的方式呢?我们既配置了<context:annotation-config />,又配置了<context:component-scan base-package="com.xxx" />,它们都具有处理在

容器中注册的bean里面的注解的功能。会不会出现重复注入的情况呢?

 

<context:annotation-config /><context:component-scan base-package="com.xxx"/><bean id="aBean"class="com.yyy.A"/>

 

不用担心,不会出现的:

 

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

 

因为<context:annotation-config />和 <context:component-scan>同时存在的时候,前者会被忽略。也就是那些@autowire,@resource等注入注解只会被注入一次

哪怕是你手动的注册了多个处理器,spring仍然只会处理一次:

 

<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
<bean id="bla" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla1" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla2" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla3" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />

 

 

结果仍是正确的:

 

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@25d2b2
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

 

 

分享到:
评论

相关推荐

    实训商业源码-苹果cms二开修复mxone开源无加密版-毕业设计.zip

    实训商业源码-苹果cms二开修复mxone开源无加密版-毕业设计.zip

    多品类农业目标检测数据集.zip

    数据集介绍:多品类农业目标检测数据集 数据集名称:多品类农业目标检测数据集 图片数量: - 训练集:11,911张图片 - 验证集:422张图片 - 测试集:124张图片 - 总计:12,457张高质量图片 分类类别: 涵盖51个农业相关类别,包括水果(苹果、香蕉、芒果、葡萄)、蔬菜(卷心菜、黄瓜、茄子、菠菜)、坚果(杏仁、腰果、榛子、核桃)、调味作物(辣椒、生姜、大蒜)及肉类(牛肉、鸡肉、猪肉)等,完整覆盖农业生产链关键品类。 标注格式: YOLO格式,包含标准化边界框坐标及类别标签,可直接用于目标检测模型训练。 1. 农业自动化分拣系统 支持开发AI驱动的分拣机器人,精准识别水果成熟度、坚果品类及蔬菜质量,提升加工效率。 1. 智能农场监测 用于无人机或摄像头系统,实时检测作物生长状态、病虫害区域及成熟作物分布。 1. 食品加工质量控制 集成至生产线视觉系统,自动检测原料种类(如肉类分类、坚果筛选),确保加工合规性。 1. 农业科研与教育 为农业院校提供多品类检测基准数据,支持算法研究及教学案例开发。 全链路覆盖 从田间作物(甜玉米、土豆)到加工原料(肉类、坚果),覆盖农业生产-加工全流程检测需求。 标注专业性 YOLO标注经多轮校验,边界框紧密贴合目标,支持复杂场景下的密集目标检测(如混合坚果分拣)。 场景多样性 包含自然光照、阴影遮挡、多角度拍摄等真实农业环境数据,强化模型鲁棒性。 高扩展性 兼容YOLOv5/v7/v8等主流框架,支持快速迁移至分类、计数等衍生任务。

    QT6 阅读与注释 LCD显示类 QLCDNumber ,该类继承于容器框架 QFrame

    QT6 阅读与注释 LCD显示类 QLCDNumber ,该类继承于容器框架 QFrame

    基于注意力机制的CNN-GRU混合模型在多领域高精度时间序列预测的应用

    内容概要:本文介绍了一种基于注意力机制的CNN-GRU混合模型,专门用于高精度时间序列预测。该模型结合了卷积神经网络(CNN)、门控循环单元(GRU)和注意力机制的优点,适用于风电功率预测、电力负荷预测、交通预测、经济预测和排放预测等多个领域。文中详细解释了模型的工作原理,包括CNN用于特征提取、GRU捕捉时序关系、注意力机制加权不同时间步的特征。同时提供了Python和PyTorch的代码实现,涵盖模型定义、训练和测试过程,并强调了精度分析的重要性。 适合人群:对时间序列预测感兴趣的机器学习爱好者、研究人员和技术开发者。 使用场景及目标:① 高精度预测风电功率、电力负荷、交通流量等;② 使用CNN-GRU-Attention模型进行时间序列数据分析;③ 提供详细的代码实现和注释,便于快速上手和应用。 其他说明:该模型不仅展示了理论背景,还提供了实际的代码实现,使读者能够直接替换数据进行实验。通过训练和测试过程,用户可以评估模型性能并进行进一步优化。

    MATLAB实现DTMF信号仿真的GUI系统:从编码到解码的完整解析

    内容概要:本文详细介绍了基于MATLAB构建的DTMF(双音多频)信号仿真系统,该系统带有图形用户界面(GUI),能够模拟电话按键声音并进行实时解码。文中首先解释了DTMF的基本原理,即通过两个特定频率的正弦波组合表示不同的按键。接着描述了如何利用MATLAB App Designer创建GUI界面,展示了具体的按键布局以及与之关联的时间和频谱图表。随后深入探讨了关键的技术细节,如采样率的选择、音频信号的生成方法及其优化措施。特别地,文章重点讲解了采用Goertzel算法对DTMF信号进行高效解码的方法,并分享了一些调试过程中遇到的问题及解决方案。此外,还介绍了一个有趣的隐藏功能——当用户连续按下特定序列的按键时,可以播放一段音乐作为彩蛋。 适合人群:对MATLAB编程有一定了解,尤其是对信号处理感兴趣的工程技术人员或学生。 使用场景及目标:适用于希望深入了解DTLAB中DTMF信号处理机制的人群,可用于教学演示、个人学习或者小型科研项目的开发。 其他说明:作者提供了完整的源代码托管于GitHub平台,鼓励读者参与交流改进。

    双馈风力发电机与900V直流混合储能并网系统的MATLAB仿真建模及其实现

    内容概要:本文详细介绍了基于双馈感应风机的900V直流混合储能并网系统的MATLAB仿真项目。主要内容涵盖五个模块:双馈感应风机模块、混合储能模块、逆变器与整流器控制模块、转子过电流保护模块。每个模块都通过MATLAB的不同工具(如Simulink、Simscape Power Systems)进行建模,并展示了具体的实现方法和代码片段。通过真实风速数据驱动,模拟了风力发电机的实际运行情况,探讨了能量的高效存储和释放机制,确保系统的稳定性和安全性。 适合人群:对风力发电技术和MATLAB仿真感兴趣的科研人员、工程师和技术爱好者。 使用场景及目标:适用于希望深入了解风力发电系统及其仿真技术的研究人员和从业者,旨在提高对风力发电技术的理解,促进绿色能源的应用和发展。 其他说明:文中提供的代码片段和详细的模块构建步骤有助于读者实际操作和验证理论知识,为后续研究提供重要参考。

    响应式酒店民宿旅馆住宿网站模板.zip

    响应式酒店民宿旅馆住宿网站模板.zip

    【医学图像处理】基于深度学习的医学图像处理技术方案:计算机毕业设计临床应用与技术创新指南计算机毕业设计中

    内容概要:本文为2025年更新的计算机毕业设计医学图像处理指南,涵盖选题方向、技术实现、论文创新设计、实验与写作规范以及避坑指南。选题方向分为临床应用和技术突破两类,具体包括病灶检测与分割、影像增强与重建、多模态融合、轻量化模型设计、少样本学习等。技术实现方面推荐了PyTorch和TensorFlow Lite作为核心框架,并介绍MONAI用于DICOM数据处理。关键技术涉及数据增强、U-Net++和Swin-Transformer模型设计。论文创新点鼓励学科交叉融合,如影像与知识图谱、边缘计算结合,并提出算法优化方向。实验设计强调对比基线模型和可视化展示,论文写作需注意伦理声明和工程价值。最后提醒数据合规性和代码可复现性,并推荐相关开源资源和工具。; 适合人群:计算机专业高年级本科生或研究生,尤其是对医学图像处理领域感兴趣的毕业生。; 使用场景及目标:①帮助学生确定具有实际意义的医学图像处理课题;②指导学生掌握从模型设计到实验验证的完整流程;③确保论文撰写符合学术规范并具备工程实用性。; 其他说明:建议学生在选题时充分考虑自身兴趣和技术背景,同时关注数据合规性和代码复现性,以确保项目顺利进行。此外,应积极利用提供的资源和工具,提高研究效率和质量。

    游戏试玩站打码赚钱平台系统源码-可运营的任务网源码.zip

    游戏试玩站打码赚钱平台系统源码-可运营的任务网源码.zip

    实训商业源码-特效喝酒神器小程序源码-毕业设计.zip

    实训商业源码-特效喝酒神器小程序源码-毕业设计.zip

    MATLAB环境下基于小波散射网络的纹理与寄生虫感染图像分类研究

    内容概要:本文详细介绍了两种基于MATLAB的小波散射网络图像分类方法。首先,针对纹理图像分类,文中阐述了如何利用小波变换对图像进行多尺度分解并提取特征,再通过深度学习网络辅助实现高效分类。其次,对于寄生虫感染图像分类,结合了小波散射网络和深度学习模型,从带标签的数据集中学习特征,从而达到高精度分类的效果。两种方法均适用于多种类型的灰度及其他图像,且在分类精度和效率上均有显著提升。 适合人群:从事图像处理、生物医学工程的研究人员和技术人员,尤其是熟悉MATLAB和深度学习的从业者。 使用场景及目标:①需要对纹理图像进行高效分类的研究项目;②涉及寄生虫感染图像识别的医疗诊断系统开发;③希望深入了解小波散射网络和深度学习在网络架构设计方面的应用。 其他说明:文中提供的方法不仅限于特定类型的图像,还可以扩展到其他领域的图像分类任务中。

    高空视角多目标检测数据集0.zip

    高空视角多目标检测数据集 数据集名称:高空视角多目标检测数据集 验证集规模:4,106张航拍图片 分类类别: - 体育设施:棒球场/篮球场/足球场/网球场/田径场 - 交通设施:桥梁/大型车辆/小型车辆/船舶/直升机 - 工业设施:集装箱起重机/储油罐/港口 - 地理特征:圆形交通环岛/游泳池 - 航空器:飞机 标注特性: - YOLO格式多边形标注,支持旋转目标检测 - 包含密集小目标标注(如船舶、车辆) - 多角度航拍视角覆盖 无人机智能巡检系统: 支持电力巡检、交通监控等场景的自动目标识别,实现基础设施的智能巡查与异常检测 卫星影像解析系统: 适用于城市发展规划、港口物流管理等领域的卫星影像自动分析 地理信息系统(GIS)更新: 自动化识别地表建筑变化,辅助地图数据实时更新 应急救援支持: 灾害现场的直升机坪识别、道路通行性评估等应急场景应用 智慧城市建设: 支持城市三维建模、交通流量分析等智慧城市应用场景 高价值目标覆盖: 包含16类关键基础设施目标,特别涵盖港口起重机、储油罐等工业场景稀缺标注数据 复杂场景标注: - 支持旋转框检测,适应航拍目标的任意朝向 - 密集小目标标注经专业质检,保证重叠目标的识别精度 多尺度特征学习: 包含从大型机场到小型车辆的跨尺度目标,提升模型尺度适应能力 实战验证数据: 专为模型验证优化的数据集,包含光照变化、目标遮挡等真实场景挑战 算法兼容性强: YOLO格式标注可直接适配主流检测框架(YOLO系列、MMDetection等),支持旋转目标检测算法开发

    Ansys/LS-DYNA多孔延时起爆与重复起爆模拟技术及其工程应用

    内容概要:本文详细介绍了Ansys/LS-DYNA在多孔延时起爆与重复起爆模拟方面的应用。首先阐述了这两种技术的基本概念及其重要性,接着分别讲解了多孔延时起爆和重复起爆的具体实现步骤,包括建模、材料属性设置、求解策略的选择等。文中还特别强调了不同起爆时间和位置对爆炸效果的影响,并通过实例展示了如何利用这些技术研究爆炸传播规律、预测爆炸效果并优化设计方案。此外,文章还涉及了相关代码的编写与分析,帮助读者深入理解仿真的具体过程。 适合人群:从事爆炸力学、冲击波研究及相关领域的科研人员和技术工程师。 使用场景及目标:适用于需要精确模拟复杂爆炸环境的研究项目,旨在提高对爆炸现象的理解,优化实际工程中的爆炸设计。 其他说明:通过对Ansys/LS-DYNA软件特性的深入了解,能够有效提升爆炸与冲击波研究的质量和效率。

    车辆紧急防避撞AEB控制系统设计:融合模糊控制与逆动力学模型的仿真研究

    内容概要:本文深入探讨了车辆紧急防避撞自动紧急制动(AEB)系统的控制算法及其仿真方法。首先介绍了驾驶员制动模型,用于模拟真实驾驶环境下的制动过程。接着引入模糊控制技术,根据车辆状态动态调整期望减速度,提高响应精度。然后阐述了纵向发动机逆动力学模型的应用,通过实时计算期望节气门开度优化发动机性能。此外,还讨论了驱动与制动切换控制机制,以及制动压力与减速度间的关系,充分考虑风阻和滚动阻力的影响。最后提及档位控制对系统整体表现的作用。文中提供了详细的算法实现论文和仿真步骤,帮助读者全面理解并掌握AEB控制系统的设计要点。 适合人群:汽车工程专业学生、自动驾驶技术研发人员、交通安全研究人员。 使用场景及目标:适用于希望深入了解AEB系统工作原理的研究者和技术开发者,旨在提升车辆主动安全性,减少交通事故发生率。 其他说明:资料详尽覆盖从理论到实践各个环节,有助于快速入门并进行相关领域的创新研究。

    实训商业源码-视频打赏系统全开源-毕业设计.zip

    实训商业源码-视频打赏系统全开源-毕业设计.zip

    实训商业源码-技术导航系统源码-毕业设计.zip

    实训商业源码-技术导航系统源码-毕业设计.zip

    实训商业源码-化妆品商城小程序模板-毕业设计.zip

    实训商业源码-化妆品商城小程序模板-毕业设计.zip

    实训商业源码-仿vivo手机商城小程序模板-毕业设计.zip

    实训商业源码-仿vivo手机商城小程序模板-毕业设计.zip

    电网技术前沿:基于IEEE73节点的三区输电网暂态仿真与调频调压研究

    内容概要:本文详细介绍了利用IEEE73节点三区输电网进行暂态仿真和调频调压的研究方法。首先,通过Python加载IEEE73节点的数据并解析关键节点信息,特别是第15号节点作为区域间联络枢纽的作用。接着,通过PSCAD设置故障条件,如三相短路故障,来模拟暂态过程,并分析发电机转速波动情况。对于调压部分,采用Python进行灵敏度分析,识别对系统电压最敏感的机组。最后,使用MATLAB计算系统的动态响应,探讨不同区域之间的耦合效应及其对频率恢复的影响。文中还强调了模型校验的重要性以及参数设置的精确性。 适合人群:电力系统研究人员、电网工程师、高校相关专业师生。 使用场景及目标:适用于需要深入了解输电网暂态特性和调频调压机制的研究人员和技术人员。目标是掌握如何通过仿真工具分析复杂电网行为,优化电网运行性能。 其他说明:建议初学者从简单的单机无穷大系统入手,逐步深入到完整的三区输电网模型,确保理解和应用的准确性。

    基于MATLAB的齿轮几何学与啮合理论的技术实现及应用

    内容概要:本文详细介绍了不同类型的齿轮(如行星齿轮、端面齿轮、斜齿轮、非圆齿轮、圆弧齿轮等)及其啮合理论和传动特性。重点探讨了齿轮的啮合原理、齿面求解、接触分析(TCA)、传动误差等关键技术,并展示了如何使用MATLAB进行这些技术的具体实现。通过建立齿轮的数学模型,MATLAB可以帮助计算齿面形状和位置,分析啮合过程中的接触状态,求解齿面方程,评估传动误差,从而优化齿轮设计。文中还引用了李特文的经典著作《齿轮几何学与啮合理论》,为读者提供了丰富的理论支持和技术指导。 适合人群:机械工程领域的研究人员、工程师以及对齿轮设计感兴趣的高校学生。 使用场景及目标:适用于需要深入了解齿轮设计原理并掌握MATLAB编程技能的人群。目标是帮助读者理解齿轮的复杂性和设计要点,提升齿轮设计的效率和准确性。 其他说明:本文不仅涵盖了齿轮的基础理论,还结合了实际案例和MATLAB程序实现,有助于读者将理论应用于实践。

Global site tag (gtag.js) - Google Analytics