`

aop浅显易懂

aop 
阅读更多
引子:
AOP(面向方面编程:Aspect Oriented Programing)和IoC一样是Spring容器的内核,声明式事务的功能在此基础上开花结果。但是AOP和OOP差别较大,要很好地理解这个概念,做到心领神会还是不容易的,不过相信看完帖子,你就不再迷惑了。

编程语言最终极的目标就是能以更自然、更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,编程语言一步步地用更自然、更灵活的方式描述软件。AOP是软件开发思想发展到一定阶段的产物,但AOP的出现并不是要完全替代OOP,而仅是作为OOP的有益补充。虽然AOP作为一项编程技术已经有多年的历史,但一直长时间停留在学术领域,直到近几年,AOP才作为一项真正的实用技术在应用领域开疆扩土。需要指出的是AOP的应用场合是受限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理以及日志记录(虽然有很多文章用日志记录作为讲解AOP的实例,但很多人认为很难用AOP编写实用的程序日志,笔者对此观点非常认同)。不过,这丝毫不影响AOP作为一种新的软件开发思想在软件开发领域所占有的地位。

AOP到底是什么

AOP是Aspect Oriented Programing的简称,最初被译为“面向方面编程”,这个翻译向来为人所诟病,但是由于先入为主的效应,受众广泛,所以这个翻译依然被很多人使用,但我们更倾向于用“面向切面编程”的译法,因为它更加达意。

按照软件重构思想的理念,如果多个类中出现相同的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。比如Horse、Pig、Camel这些对象都有run()、eat()的方法,通过引入一个包含这两个方法抽象的Animal父类,Horse、Pig、Camel就可以通过继承Animal复用到run()和eat()的方法。通过引入父类消除多个类中重复代码的方式在大多情况下是可行的,但世界并非永远这样简单,请看下面论坛管理业务类的代码:

代码清单6-1  ForumService
Java代码  收藏代码

    package com.baobaotao.concept; 
    public class ForumService { 
        private TransactionManager transManager; 
        private PerformanceMonitor pmonitor; 
        private TopicDao topicDao; 
        private ForumDao forumDao; 
     
        public void removeTopic(int topicId) { 
            pmonitor.start();//①-1性能监控开始 
            transManager.beginTransaction();//②-1 事务处理开始 
     
            topicDao.removeTopic(topicId); //③-1 业务逻辑 
     
            transManager.commit();//②-1事务处理结束 
            pmonitor.end();//①-2 性能监控结束 
        } 
        public void createForum(Forum forum) { 
            pmonitor.start();//①-1性能监控开始 
            transManager.beginTransaction();//②-1 事务处理开始 
     
            forumDao.create(forum); //③-2 业务逻辑 
     
            transManager.commit();//②-1事务处理结束 
            pmonitor.end();//①-2 性能监控结束 
        } 
        … 
    } 


代码清单6-1中①的代码是方法性能监视代码,它在方法调用前启动,在方法调用返回前结束,并在内部记录性能监视的结果信息。

而②的代码是事务开始和事务提交的代码。我们发现③处的业务代码淹没在重复化非业务性的代码之中,性能监视和事务管理这些非业务性代码葛藤缠树般包围着业务性代码。

如图6-1所示,假设我们将ForumService业务类看成一段圆木,将removeTopic()和createForum()方法分别看成圆木的一截,我们会发现性能监视和事务管理的代码就好像一个年轮,而业务代码是圆木的树心,这也正是横切代码概念的由来。



我们无法通过抽象父类的方式消除以上所示的重复性横切代码,因为这些横切逻辑依附在业务类方法的流程中,它们不能转移到其他地方去。

AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方案。对于习惯了纵向抽取的开发者来说,可能不容易理解横向抽取方法的工作机制,因为Java语言本身不直接提供这种横向抽象的能力,我们暂把具体实现放在一旁,先通过图解的方式归纳出AOP的解决思路,如图6-2所示。







从图6-2中,我们可以看出AOP希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。

当然,我们知道将这些重复性的横切逻辑独立出来是很容易的,但如何将这些独立的逻辑融合到业务逻辑中完成和原来一样的业务操作,这才是事情的关键,也正是AOP要解决的主要问题。

引用
我们现在常用“雁过拔毛”来形容某人爱贪便宜,凡他经手的事情都要捞点好处,“雁过拔毛”就是现实生活中AOP的一个很形象的例子。其实“雁过拔毛”原意是形容武艺高超,大雁飞过时也能拔下毛来。


AOP术语

如学习电学就得先学习电阻、电压、电容等专业术语一样,AOP也有一些自己的行话,为了方便后面的学习,我们先来学习一下AOP相关重要术语。

 连接点(Joinpoint)

程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。我们知道黑客攻击系统需要找到突破口,没有突破口就无法进行攻击,从某种程度上来说,AOP是一个黑客(因为它要向目前类中嵌入额外的代码逻辑),连接点就是AOP向目标类打入楔子的候选点。

连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义。

切点(Pointcut)

每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但在这为数众多的连接点中,如何定位到某个感兴趣的连接点上呢?AOP通过“切点”定位特定接连点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。其实确切地说,用切点定位应该是执行点而非连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。

增强(Advice)

增强是织入到目标类连接点上的一段程序代码。是不是觉得AOP越来越像黑客了:),这不是往业务类中装入木马吗?读者大可按照这一思路去理解增强,因为这样更形象易懂。在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点了!正因为增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以Spring所提供的增强接口都是带方位名的:BeforeAdvice、AfterRetuningAdvice、ThrowsAdvice等。BeforeAdvice表示方法调用前的位置,而AfterReturingAdvice表示访问返回后的位置。所以只有结合切点和增强两者一起上阵才能确定特定的连接点并实施增强逻辑。

引用
有很多书籍和文章将Advice译为通知,我们觉得“通知”的译法很不达意。我们来看几个使用“通知”的语境:银行向张三发出了一个催款通知;班主任通知学生明天进行大扫除。从这些语境中,我们可以知道通知者只是把某个消息传达给被通知者,并不会替被通知者做任何事情,而Spring的Advice必须嵌入到某个类的连接点上,并完成了一段附加的应用逻辑,这明显是去“增强”目标类的功能。当然,我们不能对这个翻译有过多的微词,毕竟Advice这个英文单词本身就有些不知所云,如果将其改为Enhancer,相信理解起来会更容易一些。


目标对象(Target)

增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,就如中ForumService所示。在AOP的帮助下,ForumService只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。

引介(Introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

织入(Weaving)

织入是将增强添加对目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP这台织布机天衣无缝地编织到一起。我们不能不说“织入”这个词太精辟了。根据不同的实现技术,AOP有三种织入的方式:
1)编译期织入,这要求使用特殊的Java编译器;
2)类装载期织入,这要求使用特殊的类装载器;
3)动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

代理(Proxy)

一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

切面(Aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里首先包括两个工作:第一,如何通过切点和增强定位到连接点上;第二,如何在增强中编写切面的代码。本章大部分的内容都围绕这两点展开。

AOP的实现者


AOP工具的设计目标是把横切的问题(如性能监视、事务管理)模块化。使用类似于OOP的方式进行切面的编程工作。位于AOP工具核心的是连接点模型,它提供了一种机制,可以识别出在哪里发生了横切。

 AspectJ

AspectJ是语言级的AOP实现,2001年由Xerox PARC的AOP小组发布,目前版本已经更新到1.6。AspectJ扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。主页位于http://www.eclipse.org/aspectj。

AspectWerkz

基于Java的简单、动态、轻量级的AOP框架,该框架2002年就已经发布,由BEA Systems提供支持。它支持运行期或类装载期织入横切代码,所以它拥有一个特殊的类装载器。现在,AspectJ和AspectWerkz项目已经合并,以便整合两者的力量和技术创建统一的AOP平台。他们合作的第一个发布版本是AspectJ 5:扩展AspectJ语言,以基于注解的方式支持类似AspectJ的代码风格。

JBoss AOP

2004年作为JBoss应用程序服务器框架的扩展功能发布,读者可以从这个地址了解到JBoss AOP的更多信息:http://www.jboss.org/products/aop。

Spring AOP

Spring AOP使用纯Java实现,它不需要专门的编译过程,不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。Spring并不尝试提供最完整的AOP实现,相反,它侧重于提供一种和Spring IoC容器整合的AOP实现,用以解决企业级开发中的常见问题。在Spring中,我们可以无缝地将Spring AOP、IoC和AspectJ整合在一起。

这些文章摘自于我的《Spring 3.x企业应用开发实战》的第6章。欢迎大家讨论。

延伸阅读:学习Spring必学的Java基础知识----动态代理http://www.iteye.com/topic/1123293
  • 大小: 44.2 KB
分享到:
评论

相关推荐

    SSM学生管理系统浅显易懂

    SSM学生管理系统是一个基于Java技术栈的Web应用项目,它主要使用Spring、SpringMVC和MyBatis这三大框架构建,旨在提供一个简单易懂的学生信息管理平台。这个项目非常适合初学者作为学习Java Web开发的实践案例。下面...

    AOP基础知识

    多年前搜到的一个AOP教程,代码和理论结合,浅显易懂,非常经典

    115个Java面试题和答案-终极-尚硅谷-宋红康.pdf

    这份资料的整理者宋红康老师,是尚硅谷教育的资深讲师,他在IT教育领域有着丰富的经验,擅长将复杂的技术问题用浅显易懂的语言表达出来,这使得他的教材对于初学者和准备面试的人都有很高的实用价值。通过这份资料的...

    Spring完美教程

    对于初学者来说,这是一个浅显易懂的入门指南;而对于已经熟悉Spring框架的开发者而言,通过本教程的学习可以进一步加深对Spring内部机制的理解。 #### 三、Spring框架启动流程分析 在深入理解Spring框架之前,...

    3.4.7-卡尔曼滤波与运动估计算法介绍和使用 STM32串口通信 openmv+STM32串口通信 openmv串口通信openmv识别物体 openmv神经网络训练 openmv数字识.md

    3.4.7-卡尔曼滤波与运动估计算法介绍和使用 STM32串口通信 openmv+STM32串口通信 openmv串口通信openmv识别物体 openmv神经网络训练 openmv数字识

    【MATLAB编程】MATLAB高级编程技巧全解析:数据结构、高效编程、可视化与并行计算

    内容概要:本文档详细介绍了MATLAB的高级编程技巧,涵盖高级数据结构与操作、高效编程与优化、高级可视化技术、并行计算与GPU编程、代码调试与性能分析以及高级算法与数值方法六个主要部分。具体内容包括细胞数组和结构体的创建与使用、面向对象编程、向量化

    一个用于设计和测试单点海洋系泊的工具-MATLAB

    用于设计和测试受洋流和风影响的单点海洋系泊设备。提供的数据库(可以添加到)将允许您在典型的当前条件下构建系泊并对其进行测试,或者通过电流剖面的时间序列迫使其生成系泊形状和组件位置的时间序列。该程序为地下和地面系泊提供了解决方案,甚至可以预测地面系泊何时被淹没。将溶液上下翻转,它还模拟了拖曳体,在拖曳体中,一个负浮力(重)体被拖在一艘移动的船后面(有一个立管下沉器)。如果聚焦电流剖面随时间变化(即来自ADCP),则会保存/访问系泊组件位置和形状的动画(电影)或时间(序列)历史。系泊装置可以保存和取回

    C++ 无锁队列,目前测试10线程读,10线程写无误

    基于C++的无锁队列,有信号量,可以阻塞读、写线程,目前测试10线程读,10线程写是没有问题的

    混合动力汽车拥堵路况下基于ECMS算法的节能动力总成控制研究与实现

    内容概要:本文详细介绍了混合动力汽车在交通拥堵情况下采用等效燃油消耗最小策略(ECMS)进行节能控制的研究与实现。通过MATLAB代码展示了如何模拟前车速度、跟车策略以及能量管理系统的工作原理。文中提到,ECMS算法能够根据实时速度、加速度和电池状态等因素动态调整发动机和电动机之间的能量分配,从而达到最佳的燃油经济性和排放性能。此外,文章还讨论了安全距离模型、动能回收机制以及应对突发情况的具体措施,如急减速时的能量管理和电池温度保护等。 适合人群:从事汽车工程、自动化控制领域的研究人员和技术人员,尤其是关注混合动力汽车节能技术的专业人士。 使用场景及目标:适用于希望深入了解混合动力汽车在复杂城市交通环境中的节能控制策略及其具体实施方法的人群。目标是在理论层面掌握ECMS算法的核心思想,并能够在实践中应用相关技术和工具进行验证。 其他说明:文章不仅提供了详细的代码示例,还分享了一些实用的操作技巧和经验教训,帮助读者更好地理解和运用这一先进技术。

    环形队列的一种实现方式

    环形队列的一种实现方式

    2005-2019年各地级市绿色专利申请量数据

    2005-2019年各地级市绿色专利申请量数据 1、时间2005-2019年 2、来源:国家知识产权j 3、指标:省份、城市、年份、绿色发明专利申请量、绿色实用新型专利申请量 4、范围:360+地级市

    (源码)基于C语言的WebP图片编码解码库.zip

    # 基于C语言的WebP图片编码解码库 ## 项目简介 本项目是一个基于C语言的WebP图片编码解码库。WebP是一种现代开源图像格式,支持无损和有损压缩,在保持图像质量的同时能显著减少图像文件大小,加快传输速度。此库实现了WebP格式的核心编码解码功能,还支持动画WebP图像的编码和组装,以及颜色空间转换、图像缩放等辅助功能,适用于网页开发、图像处理软件、视频编辑工具等多种应用场景。 ## 项目的主要特性和功能 1. 高性能采用优化算法和指令集(如SSE2、NEON、MIPS等)加速编码解码过程。 2. 灵活性支持多种图像格式(如RGB、YUV)和颜色空间转换。 3. 支持动画可编码和组装WebP格式的动画图像。 4. 内存管理提供安全的内存分配和释放函数,防止内存泄漏。 5. 错误处理具备错误报告和处理机制,确保程序的健壮性。 6. 核心功能实现图像分析、预测、变换、量化、反变换等编码解码步骤,并提供进度报告机制。 ## 安装使用步骤

    基于电影知识图谱和微信小程序的智能问答系统新版源码+说明.zip

    本资源是《基于电影知识图谱和微信小程序的智能问答系统新版源码+说明.zip》,专为计算机科学领域的学习者设计,融合了人工智能、数据管理和前端应用开发的先进技术。通过构建电影知识图谱,该系统能够深度理解和处理用户的电影相关查询,提供精准且丰富的答案。微信小程序作为前端交互平台,以其便捷性和广泛覆盖性,让用户随时随地享受智能化的电影信息服务。此资源不仅是课程设计和毕业设计的理想选择,也为开发者提供了实践前沿技术的机会,助力提升编程能力和项目经验。请务必用于学习和研究目的,不得用于商业用途。

    (源码)基于TensorFlow的GAN图像生成项目SteGANography.zip

    # 基于TensorFlow的GAN图像生成项目SteGANography ## 项目简介 本项目是一个基于TensorFlow深度学习框架的图像生成项目,主要利用生成对抗网络(GAN)进行图像混淆与恢复的研究。项目名称中的“Ste”代表Steganography(隐写术),是一种隐藏信息的技术。本项目的目的是利用神经网络将信息嵌入图像中,然后再恢复出来。这涉及到图像生成、加密和解密的过程。 ## 主要功能及特点 利用GAN生成混淆图像。 通过对混淆图像进行解码恢复原始图像。 包含Alice、Bob和Eve三个网络部分,分别负责生成、处理和识别图像。 提供了可视化的功能,能够绘制关于训练迭代与位错误之间的关系图。 可配置的训练参数,包括学习率、迭代次数等。 提供了模型保存和恢复的机制,便于训练中断后继续训练或在不同任务间迁移模型。 ## 安装与运行 ### 环境依赖 Python 3.x

    (源码)基于Arduino的环境监测系统.zip

    # 基于Arduino的环境监测系统 ## 项目简介 本项目是一个基于Arduino的环境监测系统,主要用于收集并保存环境数据,如温度、湿度、降雨量、气体值和风速等。通过使用Arduino平台和多种传感器,实现数据的实时采集、处理和存储。 ## 项目的主要特性和功能 1. 数据采集通过Arduino板连接多种环境传感器,实时采集环境数据。 2. 数据处理对采集的数据进行本地处理,如温度湿度的转换等。 3. 数据存储将处理后的数据保存到SD卡或其他存储设备中,以便于后续分析。 4. 数据传输通过串行通信或无线通信模块将数据发送到计算机或其他设备。 ## 安装使用步骤 1. 硬件准备准备Arduino板、环境传感器(如DHT温湿度传感器)、SD卡模块等硬件。 2. 软件准备安装Arduino IDE,并下载项目代码。 3. 传感器连接将传感器连接到Arduino板的相应引脚。 4. 代码上传将项目代码上传到Arduino板。

    100kw模块式三相光伏并网逆变器方案:原理图、PCB、源码及元器件详解

    内容概要:本文详细介绍了100kw模块式三相光伏并网逆变器的整体设计方案,涵盖功率接口板、主控DSP板、驱动扩展板及其逆变器并联仿真的各个方面。首先,文章阐述了功率接口板的原理图和PCB设计,解释了各个元件的作用及其选择依据。接着,重点讲解了主控DSP板的原理图、元器件明细表及核心代码,特别是PID控制算法的应用。然后,探讨了驱动扩展板的设计要点,包括驱动芯片的选择和PCB布局技巧。最后,分析了逆变器并联仿真文件,讨论了环流抑制算法及其效果。 适合人群:具备一定电子工程和嵌入式系统基础知识的专业人士,尤其是从事光伏逆变器及相关电力电子设备的研发工程师和技术爱好者。 使用场景及目标:①帮助读者深入了解100kw模块式三相光伏并网逆变器的工作原理和设计思路;②为实际项目开发提供详尽的技术参考资料,包括原理图、PCB设计、源码及元器件选择;③通过实例代码和仿真分析,提升读者解决复杂控制系统问题的能力。 其他说明:文中不仅提供了理论知识,还分享了许多实际设计中的经验和技巧,如PCB布线注意事项、元件选型标准、代码优化方法等,有助于读者更好地理解和应用所学内容。

    (源码)基于Python和OpenAI的微信智能聊天机器人.zip

    # 基于Python和OpenAI的微信智能聊天机器人 ## 项目简介 本项目是一个基于Python和OpenAI的微信智能聊天机器人,能够将微信打造成一个智能对话平台。通过集成OpenAI的ChatGPT模型,机器人可以进行智能对话,支持多轮会话上下文记忆、语音识别、图片生成等功能。此外,项目还支持多种插件扩展,如多角色切换、文字冒险游戏、敏感词过滤等,极大地丰富了用户的互动体验。 ## 主要特性和功能 多端部署支持个人微信、微信公众号和企业微信应用等多种部署方式。 智能对话支持私聊及群聊的智能回复,支持多轮会话上下文记忆,支持GPT3、GPT3.5、GPT4模型。 语音识别支持语音消息的识别与回复,支持Azure、Baidu、Google、OpenAI等多种语音模型。 图片生成支持图片生成和图生图功能,支持DALLE、Stable Diffusion、Replicate等模型。

    图表组件(柱状图、折线图、饼图、雷达图...)微信小程序源码.zip

    微信小程序图表组件源码简介 这份资源是精心整理的微信小程序图表组件源码包,涵盖了柱状图、折线图、饼图、雷达图等多种常见且实用的图表类型。在当今数据可视化盛行的时代,无论是商业数据分析、项目进度展示,还是日常信息统计,这些图表都起着关键作用。 对于微信小程序开发者而言,这无疑是一份极具价值的学习宝藏。它提供了现成的图表实现代码,能帮助开发者深入理解不同图表在小程序中的构建逻辑与交互方式,快速掌握如何将数据以直观的图表形式呈现给用户,从而提升小程序的用户体验与功能性。通过研究这些源码,开发者可以学习到图表绘制的技巧、数据处理的方法以及与小程序其他组件协同工作的方式,为开发更复杂、更专业的小程序应用奠定坚实基础,加速开发进程,少走弯路,进而打造出功能完备、界面美观且数据展示清晰的优质小程序产品,满足用户多样化的数据可视化需求。总之,这是一份不可多得的学习资源,助力开发者在微信小程序开发领域不断进步。

    幼儿园招生报名小程序源码(幼儿家长可以填写幼儿的基本信息,住址信息,监护人信息等资料(可自定义设置), 园方人员根据资料可以做预先审核,并提示用户修改完善资料,并可查看和导出名单).zip

    《幼儿园招生报名小程序源码简介》 本资源是一份极具实用价值的幼儿园招生报名小程序源码。它为幼儿园的招生工作提供了高效便捷的解决方案,同时也方便家长进行报名操作。 在功能方面,幼儿家长能够通过该小程序填写丰富的信息,涵盖幼儿的基本信息、住址信息以及监护人信息等各类必要资料,且这些资料可根据实际需求进行灵活的自定义设置,满足不同幼儿园的个性化要求。园方人员则拥有强大的管理权限,可依据家长提交的资料进行预先审核,若发现资料不完善或存在问题,能及时提示用户修改,确保信息的准确性和完整性。此外,园方还能方便地查看所有报名名单,并支持将名单导出,便于后续的整理和统计工作。 需要强调的是,此资源仅为学习资源,旨在帮助开发者学习和研究相关技术,不应用于商业用途。

Global site tag (gtag.js) - Google Analytics