Spring AOP从入门到精通

目录

1. AOP的演化过程

1. 代理模式

2. 动态代理

2.1 JDK动态代理

2.2 Cglib动态代理

3. Spring模式

3.1 ProxyFactory

3.2 ProxyFactoryBean

3.3 AbstractAutoProxyCreator

2. Spring AOP抽象

1. 核心术语

1.1 连接点(JoinPoint)

1.2 切点(Pointcut)

1.3 增强(Advice)

1.4 切面(Aspect)

1.5 目标对象(Target)

1.6 代理对象(Proxy)

2. 核心组件

2.1 AutoProxyCreator工作原理

2.2 @EnableAspectJAutoProxy工作原理

3. Spring AOP案例


1. AOP的演化过程

编程中我们经常会遇到这样的场景,有一段通用逻辑横跨多个业务,不能用继承来解决,比如方法耗时、数据库事务、通用日志等等。一个典型的DAO方法调用分为下面3个步骤:

每一次都手工的开启和提交事务,即显得啰嗦,又影响业务的可读性。AOP就十分擅长解决这类问题,它把开启和提交事务的逻辑抽取到Advice当中,在任务DAO上复用。那么AOP到底是怎么实现的呢?

1. 代理模式

熟悉设计模式的人这时候会想到代理模式,通过新增一个代理类实现,负责处理事务的开启和提交。假设我们要处理的是下面的类图,Staff是我们要处理的实体类,StaffDao是接口,定义两个方法employ、paySalary,StaffDaoImpl是实际的DAO实现,StaffDaoProxy是代理类,负责事务开启和提交。

Dao的操作用伪代码实现,StaffDaoImpl和代理类,核心代码如下

public class StaffDaoImpl implements StaffDao {public void employ(Staff p) {System.out.println("employ staff: " + p);}public void paySalary(Staff p) {System.out.println("pay salary :" + p);}
}
public class StaffDaoProxy implements StaffDao {private StaffDao staffDao;public StaffDaoProxy(StaffDao staffDao) {this.staffDao = staffDao;}public void employ(Staff p) {System.out.println("start transaction...");staffDao.employ(p);System.out.println("commit transaction...");}public void paySalary(Staff p) {System.out.println("start transaction...");staffDao.paySalary(p);System.out.println("commit transaction...");}
}

通过如下代码进行测试,StaffDaoImpl只是完成了数据操作,StaffDaoProxy会额外事务开启和提交操作

Staff staff = new Staff("zhangsan",18); StaffDao staffDao = new StaffDaoImpl();  // 直接调用
staffDao.employ(staff);
staffDao.paySalary(staff);StaffDaoProxy proxy = new StaffDaoProxy(staffDao); // 通过代理类使用
proxy.employ(staff);
proxy.paySalary(staff);

2. 动态代理

代理模式确实达成了我们想要的效果,如果每一个需要横切逻辑的类都需要通过代理模式类实现的话,人力成本过高,而且也不便于维护,后续每一次修改都需要改大量的代理类。通过JDK提供的动态代理,或者字节码操作的第三方库能解决这个问题。

2.1 JDK动态代理

我们来看一个通过JDK的Proxy类实现动态代理的示例,首先要定义一个InvocationHandler的实现

public class TransactionInvocationHandler implements InvocationHandler {private StaffDao staffDao;public TransactionInvocationHandler(StaffDao staffDao) {this.staffDao = staffDao;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("start transaction...");Object result = method.invoke(staffDao,args);System.out.println("commit transaction...");return result;}
}

接着就可以通过Proxy.newInstance来创建代理类实例,并测试,其中staff和staffDao的实例和代理模式里的创建方式一样,通过proxy.employ和proxy.paySalary调用输出也同之前的案例。

InvocationHandler invocationHandler = new TransactionInvocationHandler(staffDao);
StaffDao proxy = (StaffDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{StaffDao.class},invocationHandler);
proxy.employ(staff);
proxy.paySalary(staff);
2.2 Cglib动态代理

JDK动态代理存在的一个问题是,生成代理对象必须基于接口,如果一个类没有实现接口就无法创建代理。好在还有大量的第三方库支持无接口的类生成动态代理。我们来看一个cglib的示例。和JDK代理类似,首先我们要实现一个回调类,不过cglib里回调类接口是MethodInterceptor。

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TransactionMethodInterceptor implements MethodInterceptor {private StaffDao staffDao;public TransactionMethodInterceptor(StaffDao staffDao) {this.staffDao = staffDao;}public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("start transaction...");Object result = method.invoke(staffDao, args);System.out.println("commit transaction...");return result;}
}

紧接着使用Enhancer类创建代理类对象,并使用测试,也能得到同样的测试结果。

TransactionMethodInterceptor interceptor = new TransactionMethodInterceptor(staffDao);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(interceptor);
enhancer.setSuperclass(StaffDao.class);
StaffDao proxy = (StaffDao) enhancer.create();

3. Spring模式

动态代理解决代理模式带来的部分问题,我不需要再为每个类手动创建子类,所有横切逻辑都集中到InvocationHandler或MethodInterceptor中了。但是每次使用的时候都需要手工创建代理对象,还是比较麻烦。Spring基于自己的能力对这个问题做了简化

3.1 ProxyFactory

Spring提供了ProxyFactory来简化代理对象的创建,频闭通过JDK或Cglib创建代理对象的差异(涉及类如下图),除此以外并没有其他改善。

同样需要实现一个回调类,实现aopaliance提供的MethodInterceptor接口,具体代码如下

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;public class SpringTransactionMethodInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("start transaction...");Object result = invocation.proceed();System.out.println("commit transaction...");return result;}
}

然后使用ProxyFactory来创建代理对象,并进行测试,测试结果同之前的示例

ProxyFactory factory = new ProxyFactory();
factory.setTarget(staffDao);
factory.addAdvice(new SpringTransactionMethodInterceptor());
StaffDao proxy = (StaffDao) factory.getProxy();Staff staff = new Staff("zhangsan", 18);
proxy.employ(staff);
proxy.paySalary(staff);
3.2 ProxyFactoryBean

到目前为止我们依然需要手动创建代理类对象,通过Spring本身的FactoryBean机制,Spring AOP为我们提供了ProxyFactoryBean实现。首先通过xml配置Bean实现

<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/>
<bean id="staffDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="staffDao"/><property name="interceptorNames"><list><value>springTransaction</value></list></property>
</bean>

接着通过创建ApplicationContext,获取Bean实例,测试代码如下

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDaoProxy", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);
3.3 AbstractAutoProxyCreator

ProxyFactoryBean已经基本可以使用了,只是每一个Bean要做代理时都需要手工配置一个ProxyFactoryBean,使用起来还是略显繁琐。Spring提供了AbstractAutoProxyCreator来完成AOP代理的自动创建。这里我们以BeanNameAutoProxyCreator为例,看看AutoProxyCreator是如何使用,它是怎么工作的。同样先基于xml配置BeanNameAutoProxyFactory,使用相同interceptorNames的所有Bean都可以通过这一个配置自动创建代理,而且staffDao不需要和之前那样创建staffDao、staffDaoProxy两个Bean。

<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames"><list><value>staffDao</value></list></property><property name="interceptorNames" value="springTransaction"/>
</bean>

接着只需要正常的获取staffDao的Bean实例,并调用测试即可

Staff staff = new Staff("zhangsan", 18);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDao", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);

应该承认BeanNameAutoProxyCreator的使用已经比较简单了,对使用相同interceptorNames的AOP场景,只需要把Bean实例名称添加到beanNames就行了。再进一步简化就是通过AnnotationAwareAspectJAutoProxyCreator来实现了,它会自动判断Bean是否匹配AspectJ表达式,对符号条件的Bean完成代理。后面的代码中我们会进行源码级解释,现在我们先来讲讲Spring对AOP做的抽象,以及它实现AOP的核心组件。

2. Spring AOP抽象

1. 核心术语

1.1 连接点(JoinPoint)

连接点是程序中客观存在的特定位置,比如类初始化前、类初始化后、方法执行前、方法执行后等等。Spring AOP中只支持方法的连接点。连接到有两组信息组成,一个是程序的执行点,比如哪个类的哪个方法;一个是相对位置,比如方法执行前、方法返回后等等。Spring使用切点(Pointcut)表示执行点,用增强(Advice)表示相对位置。

1.2 切点(Pointcut)

连接点是程序中客观存在的事务,比如一个程序内有2个方法,方法执行前、方法执行后、抛出异常时,连接点个数就已经确定了,如果只看这3类相对位置的话,连接点就是6个。切点用来表示我们关心的连接点。Spring中使用Pointcut类表示切点。下图是Pointcut类的定义,它有两个成员变量,分别是ClassFilter实例、MethodMatcher实例。通过如下的UML图中的方法定义可以看到,ClassFilter用来表示哪些类是满足当前切点要求,MethodMatcher用来确定哪些方法满足当前切点要求。

Spring内部的核心实现类有下面这些,后面我们会挑几个看一下如何使用。

1.3 增强(Advice)

在介绍连接点的时候我们提供到过,连接点相对位置是通过增强(Advice)来表示的,此外Advice还包含相对位置要执行的代码。下图是Spring提供的Advice的核心类。我们日常使用的较多的就是在图的中心位置的4个类:

  1. MethodBeforeAdvice,方法执行前增强
  2. MethodInterceptor,方法环绕增强
  3. AfterReturningAdvice,方法返回后增强
  4. ThrowsAdvice,方法抛异常是的正常,这里需要特别注意ThrowsAdvice是一个标记接口,Spring通过反射调用,查看类JavaDoc有描述对方法签名的限制

1.4 切面(Aspect)

从之前的定义中可知,Pointcut和Advice已经完整的定义了在哪里执行什么增强逻辑。切面有切点(Pointcut)和增强(引介)组成,即包括横切逻辑的定义,也包括连接点的定义。

1.5 目标对象(Target)

要进行增强逻辑的目标类,在我们前面的例子里,StaffDao类的实例就是目标对象。

1.6 代理对象(Proxy)

对目标对象进行增强后生成的对象,就称为代理对象,这个对象已经包含增强的执行逻辑。

2. 核心组件

这张图里给出了Spring AOP中的核心流程涉及的组件,包括怎么定义切点(Pointcut),增强(Advice),并且通过组合Pointcut和Advice获得Advisor的定义。拿到Advisor后,我们可以使用ProxyFactory将Advisor和目标对象(target)组合,生成代对象。Spring默认支持两种生成代理对象的实现,一种是基于JDK的实现,JdkDynamicAopProxy,一种是基于Cglib的实现,ObjenesisCglibAopProxy。

每一个组件都有大量的实现类,这里不进行展开。接下来我们对几个关键点做详细的说明

2.1 AutoProxyCreator工作原理

BeanNameAutoProxyCreator继承自AbstractAutoProxyCreator,实现了Bean生命周期里的InstantiationAwareBeanFactoryProcessor、BeanPostProcessor,覆写的是两个方法: postProcessBeforeInstantiation、postProcessAfterInitialization,核心流程图如下:

对于定义了customTargetSource的AbstractAutoProxyCreator,Bean不会走正常的实例化流程,在postProcessBeforeInstantiation就通过TargetSource实例化并创建代理返回。日常开发中我们极少使用TargetSource,TargetSource适用于aop时期望目标对象(target)支持池化或者热替换的场景,更多TargetSource信息可以阅读Spring官方文档中关于Using TargetSources章节。

BeanNameAutoProxy主要覆写了AbstractAutoProxyCreator以下几个方法:

  1. isSupportedBeanName,基于配置的beanNames判断当前bean是否支持自动创建代理对象
  2. getAdvicesAndAdvisorsForBean,支持的时候返回空数组,不支持返回null

BeanNameAutoProxyCreator的Advice和Advisor对象的注入主要时通过interceptorNames做为bean名称从容器中查找的,具体实现看AbstractorAutoProxyCreator的buildAdvisors实现

AnnotationAwareAspectJAutoProxyCreator基于AbstractAutoProxyCreator实现,不适用interceptorNames,核心逻辑在于查找bean匹配的Advisor,通过findCandidateAdvisors查找候选Advisor。

以下是AbstractAutoProxyCreator的骨干代码,实现类基于下面的模板修改部分逻辑。

    @Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {...TargetSource targetSource = getCustomTargetSource(beanClass, beanName);                // 自定义customTargetSourceCreator的Beanif (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // 通过自定义TargetSource直接返回BeanObject proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}...}@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyBeanReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
2.2 @EnableAspectJAutoProxy工作原理

EnableAspectJAutoProxy通过在自身注解@Import引入ImportBeanDefinitionRegistrar实现,ImportBeanDefinitionRegistrar接口允许用户通过代码注册自己的BeanDefinition。

@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy 

AspectJAutoProxyRegistrar的核心代码如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);...}
}

进一步往AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary下面追踪,我们发现自动注册了AnnotationAwareAspectJAutoProxyCreator,结合上面我们对AutoProxyCreator的了解,整个基于注解的配置AOP的流程就通顺了。

3. Spring AOP案例

基于上面对原理的探究,我们能理解怎么从Advice、Pointcut开始创建Advisor,怎么利用ProxyFactoryBean创建代理,通过AutoProxyCreator让Spring自动创建代理,Spring对所有这些元素的抽象,以及相互之间如何协同。然而在显示工作中,仅仅知道这些是不够的,我们不可能每次都从最原始的MethodBeforceAdvice来创建我们的Advice,不可能从ClassFilter、MethodMatcher开始定义Pointcut,这样的话使用成本太高了,Spring提供了大量的内置类型,降低我们使用Spring AOP的成本。下一篇中我们会专注提供实战案例,包括但不限于如下主题

1.使用Advice

2.使用Advisor

3.使用BeanNameAutoProxyCreator

4.使用AnnotationAwareAspectJAutoProxyCreator

5.使用aop名称空间

6.使用@Aspect注解

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每天五分钟计算机视觉:使用1*1卷积层来改变输入层的通道数量

本文重点 在卷积神经网络中有很多重要的卷积核&#xff0c;比如1*1的卷积核&#xff0c;3*3的卷积核&#xff0c;本文将讲解1*1的卷积核的使用&#xff0c;它在卷积神经网络中具有重要的地位。由于1*1的卷积核使用了最小的窗口&#xff0c;那么1*1的卷积核就失去了卷积层可以识…

《使用ThinkPHP6开发项目》 - 创建应用

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 《使用ThinkPHP6开发项目》 - 设置项目环境变量-CSDN博客 《使用ThinkPHP6开发项目》 - 项目使用多应用开发-CSDN博客 根据前面的步骤&#xff0c;我们现在就可以开发我们的项目开发了&#xff0c;根据项目开发的需要…

SAP UI5 walkthrough step9 Component Configuration

在之前的章节中&#xff0c;我们已经介绍完了MVC的架构和实现&#xff0c;现在我们来讲一下&#xff0c;SAPUI5的结构 这一步&#xff0c;我们将所有的UI资产从index.html里面独立封装在一个组件里面 这样组件就变得独立&#xff0c;可复用了。这样&#xff0c;无所什么时候我…

mjpg-streamer配置其它端口访问视频

环境 树莓派4B ubuntu 20.04 U口摄像头 确认摄像头可访问 lsusb查看 在dev下可查看到video* sudo mplayer tv://可打开摄像头并访问到视频 下载mjpg-streamer并编译安装 在github下载zip包&#xff0c;下载的源码&#xff0c;需要编译安装 unzip解压 cd mjpg-streamer/mjp…

【lesson3】数据库表的操作

文章目录 创建修改修改表名增加表类型修改表的某一类型的类型修改表某一类型的类型名 删除删除表的某一列删除表 查看查看表信息查看表内容 创建 建表指令&#xff1a; 查看是否建表成功&#xff1a; 查看表的具体信息&#xff1a; 修改 修改表名 法一&#xff1a;修改…

【Spring教程25】Spring框架实战:从零开始学习SpringMVC 之 SpringMVC入门案例总结与SpringMVC工作流程分析

目录 1.入门案例总结2. 入门案例工作流程分析2.1 启动服务器初始化过程2.2 单次请求过程 欢迎大家回到《Java教程之Spring30天快速入门》&#xff0c;本教程所有示例均基于Maven实现&#xff0c;如果您对Maven还很陌生&#xff0c;请移步本人的博文《如何在windows11下安装Mave…

分层网络模型(OSI、TCP/IP)及对应的网络协议

OSI七层网络模型 OSI&#xff08;Open System Interconnect&#xff09;&#xff0c;即开放式系统互连参考模型&#xff0c; 一般都叫OSI参考模型&#xff0c;是ISO组织于1985年研究的网络互连模型。OSI是分层的体系结构&#xff0c;每一层是一个模块&#xff0c;用于完成某种功…

hook其他调试技巧

输出堆栈信息 通过 android.util.Log 输出当前线程的堆栈跟踪信息。 function showStacks() {Java.perform(function () {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new() )); }) } 可以在需要的…

VS Code自动写注释并生成文档:Mintlify Doc Writer

文章目录 安装和初步使用界面说明VS Code插件 安装和初步使用 Mintlify Doc Writer&#xff08;后文简称MDW&#xff09;,是一款自动为代码写注释的AI工具&#xff0c;在插件栏搜索后&#xff0c;安装Mintlify Doc Writer for Python…&#xff0c;总之名字很长&#xff0c;安…

亿胜盈科HT7183 16V, 3A高效升压转换器 可编程软启动

HT7183是一款高功率异步升压转换器&#xff0c;集成 120mΩ功率开关管&#xff0c;为便携式系统提供G效的小尺寸处理方案。 HT7183具有2.6V至5.5V输入电压范围&#xff0c;可为各类不同供电的应用提供支持。HT7183具备3A 开关电流能力&#xff0c;并且能够提供高达16V的输出电…

JM中ref_pic_list_modification bug记录

问题描述 今天在用JM对YUV420p编码时,发现编出的码流用ffplay播放花屏,报如下错误: JM的版本时19.1,没有使能B帧,PicOrderCntType设置为2,其它都是encoder.cfg中的默认配置。我用一些码流分析工具播放H264码流正常,用一些播放器播放也都存在花屏,不过大多数播放器都是…

坚鹏:中国邮政储蓄银行数字化转型战略、方法与案例培训

中国邮政储蓄银行拥有优良的资产质量和显著的成长潜力&#xff0c;是中国领先的大型零售银行。2016年9月在香港联交所挂牌上市&#xff0c;2019年12月在上交所挂牌上市。中国邮政储蓄银行拥有近4万个营业网点&#xff0c;服务个人客户超6.5亿户。2022年&#xff0c;在《银行家》…

C++笔记之Delegate和委托构造(Delegating constructor)

C笔记之Delegate和委托构造辨析 code review! —— 杭州 2023-12-10 文章目录 C笔记之Delegate和委托构造辨析0.有道词典&#xff1a;英语发音1.ChatGPT&#xff1a;delegate概念详解2.Delegate和“将可调用对象作为函数参数”是不是一回事&#xff1f;3.C的Delegate示例4.…

MySQL数据库,创建和管理表

创建数据库&#xff1a; 方式一&#xff1a;创建数据库 CREATE DATABASE 数据库名&#xff1b;&#xff08;使用的是默认的字符集&#xff09; 方式二&#xff1a;创建数据库并指定字符集 CREATE DATABASE 数据库名 CHARACTER SET 字符集&#xff1b; 方式三&#xff1a;判断数…

openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup

文章目录 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup150.1 背景信息150.2 前提条件150.3 语法150.4 参数说明150.5 示例 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup 150.1 背景信息 openGaus…

解读 | GPT-4突然“变赖“ 是莫名其妙还是另有玄机

大家好&#xff0c;我是极智视界&#xff0c;欢迎关注我的公众号&#xff0c;获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码和资源下载&#xff0c;链接&#xff1a;https://t.zsxq.com/0aiNxERDq 事情是这样的&#…

DSP外部中断笔记

中断原理 三部分 注意 &#xff0c;外部中断使能&#xff0c;PIE使能&#xff0c;CPU中断使能 外部中断有7个&#xff0c;PIE有12组&#xff0c;一个组有8个中断复用。只有一个CPU中断可执行。 外部中断原理 1、外部中断概述 外部中断结构图 外部中断XINT1对应的是0到31GPIO…

记录一次云原生线上服务数据迁移全过程

文章目录 背景迁移方案调研迁移过程服务监控脚本定时任务暂停本地副本服务启动&#xff0c;在线服务下线MySQL 数据迁移Mongo 数据迁移切换新数据库 ip 本地服务启动数据库连接验证服务打包部署服务重启前端恢复正常监控脚本定时任务启动旧服务器器容器关闭 迁移总结 背景 校园…

TCP的滑动窗口机制

网络的错误检测和补偿机制非常复杂。 一、等待超时时间&#xff08;返回ACK号的等待时间&#xff09; 当网络繁忙时会发生拥塞&#xff0c;ACK号的返回变慢&#xff0c;较短的等待时间会导致频繁的数据重传&#xff0c;导致本就拥塞的网络雪上加霜。如果等待时间过长&#xf…

VC++使用GetProcessTimes获取进程创建时间、销毁时间、用户态时间、内核态时间

一、GetProcessTimes函数简介&#xff08;微软MSDN&#xff09; 微软提供了一个非常有用的API函数GetProcessTimes用来获取进程创建时间、销毁时间、用户态时间、内核态时间&#xff0c;msdn连接为&#xff1a;GetProcessTimes 函数 (processthreadsapi.h) 其函数原型为&#…