spring 事务源码阅读之实现原理

news/2024/5/17 16:14:25/文章来源:https://blog.csdn.net/sinat_16493273/article/details/133644343

开启事务

使用@EnableTransactionManagement注解开启事务

该注解会引入TransactionManagementConfigurationSelector类,然后该类导入两个类AutoProxyRegistrar和ProxyTransactionManagementConfiguration。

1、添加bean后置处理器

AutoProxyRegistrar类的作用是注册InfrastructureAdvisorAutoProxyCreator类,InfrastructureAdvisorAutoProxyCreator是Spring中实现AOP代理的关键组件,它会扫描所有的Advisor(通知器),并将其与BeanFactory中的Bean进行匹配,以决定是否需要为该Bean创建AOP代理。

InfrastructureAdvisorAutoProxyCreator 继承AbstractAdvisorAutoProxyCreator 继承AbstractAutoProxyCreator,实现SmartInstantiationAwareBeanPostProcessor和BeanFactoryAware接口。

是一个bean后置处理器

public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {@Nullableprivate ConfigurableListableBeanFactory beanFactory;@Overrideprotected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {super.initBeanFactory(beanFactory);this.beanFactory = beanFactory;}@Overrideprotected boolean isEligibleAdvisorBean(String beanName) {return (this.beanFactory != null && this.beanFactory.containsBeanDefinition(beanName) &&this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);}}

代理的创建在上一篇AOP源码阅读中有说过就是在后置处理器的after方法寻找合适的advisor进行创建代理。上面的源码看到InfrastructureAdvisorAutoProxyCreator只是重写了initBeanFactory方法用来获取beanfacotry和isEligibleAdvisorBean方法用来判断是否是合适的bean。

2、引入Advisor

TransactionManagementConfigurationSelector引入的另一个类是ProxyTransactionManagementConfiguration,该类代码也不多,如下

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();advisor.setTransactionAttributeSource(transactionAttributeSource);advisor.setAdvice(transactionInterceptor);if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {return new AnnotationTransactionAttributeSource();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource);if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}

ProxyTransactionManagementConfiguration类主要引入BeanFactoryTransactionAttributeSourceAdvisor通知类。并为其TransactionAttributeSource和TransactionInterceptor两个属性赋值。该类实现了Advisor接口,是一个PointcutAdvisor类型的Advisor,用来匹配@Transaction注解。具体如何匹配下面结合源码说。

代理创建

1、获取所有Advisor

在AbstractAutoProxyCreator的后置出来器afater方法里还是会调用wrapIfNecessary(bean, beanName, cacheKey)来判断当前bean是否需要包装代理。然后会调用getAdvicesAndAdvisorsForBean(bean.getClass(), beanName)方法尝试获取适用于该bean的advice。这个方法在AbstractAdvisorAutoProxyCreator 中实现

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {//获取符合条件的advisorList<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {//获取候选的所有advisorList<Advisor> candidateAdvisors = findCandidateAdvisors();//获取适用于当前bean的advisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

findCandidateAdvisors()方法还是在该类中实现,使用了一个BeanFactoryAdvisorRetrievalHelper工具类进行获取Advisor

AbstractAdvisorAutoProxyCreator#findCandidateAdvisors

protected List<Advisor> findCandidateAdvisors() {return this.advisorRetrievalHelper.findAdvisorBeans();
}

BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans

public List<Advisor> findAdvisorBeans() {String[] advisorNames = this.cachedAdvisorBeanNames;if (advisorNames == null) {//从beanfacotry总获取所有的AdvisoradvisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;}if (advisorNames.length == 0) {return new ArrayList<>();}List<Advisor> advisors = new ArrayList<>();for (String name : advisorNames) {if (isEligibleBean(name)) {//调用isEligibleBean判断bean是否满足基本条件advisors.add(this.beanFactory.getBean(name, Advisor.class));}}return advisors;
}

这里看到Advisor的获取是直接从beanFacotry中查找Advisor.class类型的beanDef,这样就能找到我们最开始通过import导入的BeanFactoryTransactionAttributeSourceAdvisor。这和上一篇说的AOP的Aspect方式有一些区别(扫描所有的bean,反射解析@Aspect注解)。

2、寻找匹配的Advisor

找到所有的Advisor后下一步就是逐个判断是否适用于当前bean

protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}

AopUtils#findAdvisorsThatCanApply

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List<Advisor> eligibleAdvisors = new ArrayList<>();//这里不是IntroductionAdvisor类型的不会进入循环for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {// already processedcontinue;}//进入canApply方法判断if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}

AopUtils#canApply

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {//走这里PointcutAdvisor pca = (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}
}

最开始说过我们引入的advisor是一个PointcutAdvisor类型。所以会进入第二个if分支。拿出pointcut进行匹配。这里的advisor是BeanFactoryTransactionAttributeSourceAdvisor实例,器pointcut是TransactionAttributeSourcePointcut类型实例。下一个重载canApply方法主要匹配逻辑是下面代码

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {//...//这里返回的matcher还是TransactionAttributeSourcePointcut本身MethodMatcher methodMatcher = pc.getMethodMatcher();for (Class<?> clazz : classes) {//拿出class所有方法与matcher进行匹配Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

主要的匹配逻辑是methodMatcher.matches,也就是

TransactionAttributeSourcePointcut#matches

public boolean matches(Method method, Class<?> targetClass) {//获取TransactionAttributeSource,这是最开始在引入Advisor设置的TransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

这里TransactionAttributeSource是最开始引入由ProxyTransactionManagementConfiguration类内部设置的,其实例类型是AnnotationTransactionAttributeSource ,该类继承 AbstractFallbackTransactionAttributeSource。getTransactionAttribute()方法在其父类中

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {if (method.getDeclaringClass() == Object.class) {//Object类方法跳过return null;}// First, see if we have a cached value.Object cacheKey = getCacheKey(method, targetClass);TransactionAttribute cached = this.attributeCache.get(cacheKey);if (cached != null) {//先从缓存获取,如果没有解析// Value will either be canonical value indicating there is no transaction attribute,// or an actual transaction attribute.if (cached == NULL_TRANSACTION_ATTRIBUTE) {return null;}else {return cached;}}else {// 解析当前方法事务属性TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);// Put it in the cache.if (txAttr == null) {this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);}else {String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);if (txAttr instanceof DefaultTransactionAttribute) {DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr;dta.setDescriptor(methodIdentification);dta.resolveAttributeStrings(this.embeddedValueResolver);}this.attributeCache.put(cacheKey, txAttr);}return txAttr;}
}

解析实际方法是computeTransactionAttribute()

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {//step1 判断方法必须是public方法if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);//step2 解析方法事务属性TransactionAttribute txAttr = findTransactionAttribute(specificMethod);if (txAttr != null) {return txAttr;}// Second try is the transaction attribute on the target class.txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {return txAttr;}if (specificMethod != method) {// Fallback is to look at the original method.txAttr = findTransactionAttribute(method);if (txAttr != null) {return txAttr;}// Last fallback is the class of the original method.txAttr = findTransactionAttribute(method.getDeclaringClass());if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {return txAttr;}}return null;
}

step2处findTransactionAttribute()方法解析事务属性,该方法是AnnotationTransactionAttributeSource类实现,其调用determineTransactionAttribute(method)方法。

AnnotationTransactionAttributeSource#determineTransactionAttribute

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {for (TransactionAnnotationParser parser : this.annotationParsers) {TransactionAttribute attr = parser.parseTransactionAnnotation(element);if (attr != null) {return attr;}}return null;
}

这里拿出所有的annotationParsers进行解析方法是否定义事务。annotationParsers是在AnnotationTransactionAttributeSource的构造方法初始化,这里有两个SpringTransactionAnnotationParser和JtaTransactionAnnotationParser。我们主要看SpringTransactionAnnotationParser。

SpringTransactionAnnotationParser#parseTransactionAnnotation

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(element, Transactional.class, false, false);if (attributes != null) {return parseTransactionAnnotation(attributes);}else {return null;}
}

终于看到熟悉的Transactional注解了,也不往方法里面看了。这里就是解析@Transactional注解配置的事务属性。

3、事务管理

代理创建和AOP过程一致,这里还是以JDK代理类为例。代理对象是JdkDynamicAopProxy。原方法的执行会首先进入JdkDynamicAopProxy.invoke方法。

这里还是从invoke的获取拦截器链开始

List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

最后会调到

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {List<MethodInterceptor> interceptors = new ArrayList<>(3);Advice advice = advisor.getAdvice();if (advice instanceof MethodInterceptor) {//会走这里interceptors.add((MethodInterceptor) advice);}for (AdvisorAdapter adapter : this.adapters) {//这是Aspect的情况if (adapter.supportsAdvice(advice)) {interceptors.add(adapter.getInterceptor(advisor));}}if (interceptors.isEmpty()) {throw new UnknownAdviceTypeException(advisor.getAdvice());}return interceptors.toArray(new MethodInterceptor[0]);
}

这里要回到开始ProxyTransactionManagementConfiguration引入Advisor引入的地方

public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();advisor.setTransactionAttributeSource(transactionAttributeSource);//设置的advisor是TransactionInterceptor类型advisor.setAdvice(transactionInterceptor);if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));}return advisor;
}

所以最后会调用TransactionInterceptor的invoke方法

TransactionInterceptor#invoke

public Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}

最后事务的控制在invokeWithinTransaction方法完成。

总结

开启事务为我们引入了两个重要的

1、BeanFactoryTransactionAttributeSourceAdvisor事务Advisor。设置两个属性TransactionInterceptor用来代理管理事务,TransactionAttributeSource用来匹配事务方法。

1、InfrastructureAdvisorAutoProxyCreator是bean后置处理器,用来判断当前bean是否需要代理。会拿出第一步引入的事务Advisor,与当前bean的所有方法进行匹配看是否有@Transaction注解。有就创建代理,最后使用BeanFactoryTransactionAttributeSourceAdvisor中设置的TransactionInterceptor拦截器进行事务管理。

整理流程
在这里插入图片描述

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

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

相关文章

大数据学习(2)Hadoop-分布式资源计算hive(1)

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博>主哦&#x…

前端uniapp生成海报并保存相册

uiapp插件 目录 图片qrcode.vue源码完整版封装源码qrcodeSwiper.vue最后 图片 qrcode.vue源码完整版 <template><view class"qrcode"><div class"qrcode_swiper SourceHanSansSC-Normal"><!-- <cc-scroolCard :dataInfo"dat…

10-09 周一 图解机器学习之深度学习感知机学习

10-09 周一 图解机器学习之深度学习感知机学习 时间版本修改人描述2023年10月9日14:13:20V0.1宋全恒新建文档 简介 感知机是神经网络中的概念&#xff0c;1958年被Frank Rosenblatt第一次引入。感知机作为一种基本的神经网络模型&#xff0c;它模拟了人脑神经元的工作原理。感…

Python+requests+Excel数据驱动的接口自动化测试中解决接口间数据依赖

在实际的测试工作中&#xff0c;在做接口自动化过程中往往会遇到接口间数据依赖问题&#xff0c;即API_03的请求参数来源与API_02的响应数据&#xff0c;API_02的请求参数又来源与API_01的响应数据&#xff0c;因此通过自动化方式测试API_03接口时&#xff0c;需要预先请求API_…

山西电力市场日前价格预测【2023-10-11】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-11&#xff09;山西电力市场全天平均日前电价为507.37元/MWh。其中&#xff0c;最高日前电价为873.70元/MWh&#xff0c;预计出现在18: 45。最低日前电价为313.23元/MWh&#xff0c;预计…

阿里云数据库MongoDB恢复到本地

共两种方式&#xff0c;建议使用第二种的逻辑恢复&#xff0c;比较方便快捷 一、下载物理备份文件 下载的格式是xb的&#xff0c;主要跟实例创建时间有关&#xff0c;2019年03月26日之前创建的实例&#xff0c;物理备份文件格式为tar&#xff0c;后面全部都是xb的格式了&#…

PTA 7-3 插松枝(单调栈)

题目7-3 插松枝 人造松枝加工场的工人需要将各种尺寸的塑料松针插到松枝干上&#xff0c;做成大大小小的松枝。他们的工作流程&#xff08;并不&#xff09;是这样的&#xff1a; 每人手边有一只小盒子&#xff0c;初始状态为空。 每人面前有用不完的松枝干和一个推送器&#…

C++ 获取文件创建时间、修改时间、大小等属性

简介 获取文件创建时间、修改时间、大小等属性 代码 #include <iostream> #include <string.h> #include <time.h>void main() {std::string filename "E:\\LiHai123.txt";struct _stat stat_buffer;int result _stat(filename.c_str(), &s…

shein汇总

如何将mysql的数据同步到es 1、使用Canal&#xff1a;Canal是阿里巴巴开源的一款基于MySQL数据库增量日志解析和同步的工具。Canal可以将MySQL中的数据同步到ES中&#xff0c;支持多种数据格式和数据源。 2、使用自定义脚本&#xff1a;可以编写自定义脚本&#xff0c;通过My…

C语言利用计算机找系统的最小通路集的算法

背景&#xff1a; 有人求助到博主希望分析一下他们老师给出的题目&#xff0c;博主思路分析和解题过程如下 题目要求&#xff1a; 联络矩阵法&#xff0c;当 n 较小时可以用手算,当然也可以用计算机计算。但当 n 很大时&#xff0c;需要计 算机的容量很大才行。为此要探求有…

git 取消待推送内容

选择最后一次提交的记录&#xff0c;右键->软合并

MyCat-web安装文档:安装Zookeeper、安装Mycat-web

安装Zookeeper A. 上传安装包 zookeeper-3.4.6.tar.gzB. 解压 #解压到当前目录&#xff0c;之后会生成一个安装后的目录 tar -zxvf zookeeper-3.4.6.tar.gz#加上-c 代表解压到指定目录 tar -zxvf zookeeper-3.4.6.tar.gz -C /usr/local/C. 在安装目录下&#xff0c;创建数据…

与创新者同行!Apache Doris 首届线下峰会即将开启,最新议程公开!|即刻预约

点击此处 即刻报名 Doris Summit Asia 2023 回顾人类的发展史&#xff0c;地球起源于 46 亿年前的原始星云、地球生命最初出现于 35 亿年前的原始海洋、人类物种诞生于数百万年前&#xff0c;而人类生产力的真正提升源于十八世纪六十年代的工业革命&#xff0c;自此以后&#…

【脑机接口论文与代码】High-speed spelling with a noninvasive brain–computer interface

High-speed spelling with a noninvasive brain–computer interface 中文题目 &#xff1a;非侵入性的高速拼写脑机接口论文下载算法程序下载摘要1 项目介绍2 方法2.1SSVEPs的基波和谐波分量JFPM刺激产生算法2.3基波和谐波SSVEP分量的幅度谱和信噪比 3讨论4实验环境设置与方法…

2023-10-10-C++指针和引用【程序员生涯的第一座里程碑】

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

AIGC | LLM 提示工程 -- 如何向ChatGPT提问

当前生成式人工智能已经成为革命性的驱动源&#xff0c;正在迅速地重塑世界&#xff0c;将会改变我们生活方式和思考模式。LLM像一个学会了全部人类知识的通才&#xff0c;但这不意味每个人可以轻松驾驭这个通才。我们只有通过学习面向LLM的提示工程&#xff0c;才可以更好的让…

通过后台系统添加一段div,在div中写一个<style></style>标签来修改div外面的元素的深层元素的样式

先看图 btn元素就是通过后台系统加上的元素,现在需要通过在btn里面写一个style标签来修改grid-nine里面的head元素的高度.开始想通过style来修改,但是不知道怎么去获取这个div外面的元素,想通过js方法去修改,写了script标签加了js代码,但不生效,后面问了才知道,这个项目是vue打…

软件测试工程师简历项目经验该如何编写(文档)

软件测试工程师简历项目经验怎么写 面试是我们进入一个公司的门槛&#xff0c;通过了面试才能进入公司&#xff0c;你的面试结果和你的薪资是息息相关的。 在面试之前&#xff0c;不得不聊聊简历&#xff0c;简历是职场的敲门砖&#xff0c;是拿到offer的通行证&#xff0c;那…

【B/S架构】医院不良事件报告系统源码

医院不良事件报告系统为医院内质量控制、患者安全关注、医疗安全不良事件方面的精细化的管理提供了平台&#xff0c;是等级医院评审的必备内容&#xff0c;评审要求医院直报系统要与卫生部“医疗安全(不良)事件报告系统”建立网络对接。 不良事件报告系统源码包括护理相关事件、…

什么是网络流量监控

随着许多服务迁移到云&#xff0c;网络基础架构的维护变得复杂。虽然云采用在生产力方面是有利的&#xff0c;但它也可能让位于未经授权的访问&#xff0c;使 IT 系统容易受到安全攻击。 为了确保其网络的安全性和平稳的性能&#xff0c;IT 管理员需要监控用户访问的每个链接以…