Spring源码解析(30)之AOP拦截链执行过程

news2024/11/15 10:16:28

一、前言

        在上一节中我们介绍了AOP动态代理对象的创建过程,并且看到了Spring AOP在生成calllBacks的时候第一个拦截器就是:DynamicAdvisorInterceptor,所以我们通过代理对象执行对应的方法的时候就如跳入到这个拦截器中,接下来我们继续跟着这个拦截器源码往下执行。

     

 

二、源码分析

        我们看下DynamicAdvisedInterceptor里面的源码。

	private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

		private final AdvisedSupport advised;

		public DynamicAdvisedInterceptor(AdvisedSupport advised) {
			this.advised = advised;
		}

		@Override
		@Nullable
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					// Make invocation available if necessary.
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
				// 从advised中获取配置好的AOP通知
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// Check whether we only have one InvokerInterceptor: that is,
				// no real advice, but just reflective invocation of the target.
				// 如果没有aop通知配置,那么直接调用target对象的调用方法
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					// We can skip creating a MethodInvocation: just invoke the target directly.
					// Note that the final invoker must be an InvokerInterceptor, so we know
					// it does nothing but a reflective operation on the target, and no hot
					// swapping or fancy proxying.
					Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
					// 如果拦截器链为空则直接激活原方法
					retVal = methodProxy.invoke(target, argsToUse);
				}
				else {
					// We need to create a method invocation...
					// 通过cglibMethodInvocation来启动advice通知
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null && !targetSource.isStatic()) {
					targetSource.releaseTarget(target);
				}
				if (setProxyContext) {
					// Restore old proxy.
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

        首先,不知道是否还记得我们在之前需要适配当前bean的advisor的时候,当类匹配之后去进行方法匹配,但是其中一个method匹配之后就已经返回true,并没有进行全部方法匹配,所以这里就会对具体的方法进行匹配。

 

        所以我们继续往西看 getInterceptorsAndDynamicInterceptionAdvice他是来获取我们对应的拦截器链。

	/** The AdvisorChainFactory to use. */
	AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();	
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
		// 创建一个method的缓存对象,在MethodCacheKey中实现了equals和hashcode方法同时还实现了compareTo方法
		MethodCacheKey cacheKey = new MethodCacheKey(method);
		List<Object> cached = this.methodCache.get(cacheKey);
		// 先从缓存中获取,如果缓存中获取不到,则再调用方法获取,获取之后放入到缓存中
		if (cached == null) {
			// 调用的是advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}

        如果缓存中不存在则调用DefaultAdvisorCha ginFactory中的etInterceptorsAndDynamicInterceptionAdvice方法。

@Override
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {

		// This is somewhat tricky... We have to process introductions first,
		// but we need to preserve order in the ultimate list.
		// 这里用了一个单例模式 获取DefaultAdvisorAdapterRegistry实例
		// 在Spring中把每一个功能都分的很细,每个功能都会有相应的类去处理 符合单一职责原则的地方很多 这也是值得我们借鉴的一个地方
		// AdvisorAdapterRegistry这个类的主要作用是将Advice适配为Advisor 将Advisor适配为对应的MethodInterceptor
		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
		Advisor[] advisors = config.getAdvisors();
		// 创建一个初始大小为 之前获取到的 通知个数的集合
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		// 如果目标类为null的话,则从方法签名中获取目标类
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		// 判断目标类是否存在引介增强,通常为false
		Boolean hasIntroductions = null;

		// 循环目标方法匹配的通知
		for (Advisor advisor : advisors) {
			// 如果是PointcutAdvisor类型的实例
			if (advisor instanceof PointcutAdvisor) {
				// Add it conditionally.
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
				// 如果提前进行过切点的匹配了或者当前的Advisor适用于目标类
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
					boolean match;
					//检测Advisor是否适用于此目标方法
					if (mm instanceof IntroductionAwareMethodMatcher) {
						if (hasIntroductions == null) {
							hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
						}
						match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
					}
					else {
						match = mm.matches(method, actualClass);
					}
					if (match) {
						// 拦截器链是通过AdvisorAdapterRegistry来加入的,这个AdvisorAdapterRegistry对advice织入具备很大的作用
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						// 使用MethodMatchers的matches方法进行匹配判断
						if (mm.isRuntime()) {
							// Creating a new object instance in the getInterceptors() method
							// isn't a problem as we normally cache created chains.
							// 动态切入点则会创建一个InterceptorAndDynamicMethodMatcher对象
							// 这个对象包含MethodInterceptor和MethodMatcher的实例
							for (MethodInterceptor interceptor : interceptors) {
								interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
							}
						}
						else {
							// 添加到列表中
							interceptorList.addAll(Arrays.asList(interceptors));
						}
					}
				}
			}
			// 如果是引介增强
			else if (advisor instanceof IntroductionAdvisor) {
				IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
				if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
					// 将Advisor转换为Interceptor
					Interceptor[] interceptors = registry.getInterceptors(advisor);
					interceptorList.addAll(Arrays.asList(interceptors));
				}
			}
			// 以上两种都不是
			else {
				// 将Advisor转换为Interceptor
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}

		return interceptorList;
	}

        首先我们看下这个AdvisorAdapterRegistry对象里面有哪些东西,往下跟GlobalAdvisorAdapterRegistry.getInstance(),他的作用主要是将Advice适配为Advisor 将Advisor适配为对应的MethodInterceptor。

	/**
	 * 单例模式的使用,使用静态类变量来保持一个唯一实例
	 *
	 * Keep track of a single instance so we can return it to classes that request it.
	 */
	private static AdvisorAdapterRegistry instance = new DefaultAdvisorAdapterRegistry();

	/**
	 * 返回单例对象
	 *
	 * Return the singleton {@link DefaultAdvisorAdapterRegistry} instance.
	 */
	public static AdvisorAdapterRegistry getInstance() {
		return instance;
	}

        然后继续往下跟DefaultAdvisorAdapterRegister方法。可以看到他默认会放入三个adapter。

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

	// 持有一个AdvisorAdapter的List,这个List中的adapter是与实现Spring AOP的advice增强功能相对应的
	private final List<AdvisorAdapter> adapters = new ArrayList<>(3);


	/**
	 * 此方法把已有的advice实现的adapter加入进来
	 *
	 * Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters.
	 */
	public DefaultAdvisorAdapterRegistry() {
		registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
		registerAdvisorAdapter(new AfterReturningAdviceAdapter());
		registerAdvisorAdapter(new ThrowsAdviceAdapter());
	}

        然后循环advisor,如果是方法能够匹配得上的,那就获取这个advisor对应的interceptor对象,我们继续往下跟getInterceptors方法。

// 将 Advisor转换为 MethodInterceptor
	@Override
	public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
		List<MethodInterceptor> interceptors = new ArrayList<>(3);
		// 从Advisor中获取 Advice
		Advice advice = advisor.getAdvice();
		if (advice instanceof MethodInterceptor) {
			interceptors.add((MethodInterceptor) advice);
		}
		for (AdvisorAdapter adapter : this.adapters) {
			if (adapter.supportsAdvice(advice)) {
				// 转换为对应的 MethodInterceptor类型
				// AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor  ThrowsAdviceInterceptor
				interceptors.add(adapter.getInterceptor(advisor));
			}
		}
		if (interceptors.isEmpty()) {
			throw new UnknownAdviceTypeException(advisor.getAdvice());
		}
		return interceptors.toArray(new MethodInterceptor[0]);
	}

        我们去看下我们那几个advisor的继承关系,看一下他们是否是MethodInterceptor的子类。可以看到关系图如下:

        可以看到有三个是实现了MethodInterceptor接口,另外两个是通过adapter来做转换的,我们看下他是如果做转换的。

class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}

}

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}

}

       其实这里总结就是Spring AOP都有两种方式来生成拦截器,一个是实现MethodInterceptor接口一个是通过适配器来实现,通过适配来返回对应的Interceptor。就是为了能够拓展实现而已。

        这样子就会返回一个拦截器链。

 

        然后将对应的拦截器链传入一个CglibMethodInvacation中去调用,我们来看下这个类里面做了哪些事情。

	private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

		@Nullable
		private final MethodProxy methodProxy;

		public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
				Object[] arguments, @Nullable Class<?> targetClass,
				List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {

			super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);

			// Only use method proxy for public methods not derived from java.lang.Object
			this.methodProxy = (Modifier.isPublic(method.getModifiers()) &&
					method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
					!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method) ?
					methodProxy : null);
		}

        

        CglibMethodInvocation是ReflectiveMethodInvocation

的子类,我们来看下ReflectiveMethodInvocation的构造函数做了哪些事情。

	protected ReflectiveMethodInvocation(
			Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
			@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		this.method = BridgeMethodResolver.findBridgedMethod(method);
		this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}

        其实就是做了一下属性赋值,主要是把拦截链路进行了赋值,接下来我们继续看一下真正的执行调用proceed方法。

		@Override
		@Nullable
		public Object proceed() throws Throwable {
			try {
				return super.proceed();
			}
			catch (RuntimeException ex) {
				throw ex;
			}
			catch (Exception ex) {
				if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) {
					throw ex;
				}
				else {
					throw new UndeclaredThrowableException(ex);
				}
			}
		}

         在CglibMethodInvocation中调用父类,也就是ReflecticeMthodInvocation中去,继续往下跟。

/**
	 * 递归获取通知,然后执行
	 * @return
	 * @throws Throwable
	 */
	@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		// 从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target的函数,这个函数是通过反射机制完成的
		// 具体实现在AopUtils.invokeJoinpointUsingReflection方法中
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		// 获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			// 这里对拦截器进行动态匹配的判断,这里是对pointcut触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				// 如果不匹配,那么proceed会被递归调用,知道所有的拦截器都被运行过位置
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			// 普通拦截器,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

        这里就是Spring AOP链式调用的地方,这里就会通过下标控制,然后递归调用这个方法,接下来我们debug来跟一下这个过程。

 

        首先我们第一次进来是-1,会先判断当前执行的interceptor是否是拦截链路中的最后一个,如果是,就直接调用方法逻辑,如果不是,则取出下一个拦截器。

        很明显我们第一次进来是-1,然后取出第一个拦截器,我们这里的第一个拦截器就是ExposeInvocationInterceptor。

         请注意这里我们传入的this对象代表的是CglibMethodProxy,也就是RefrectiveMethodInvocation,因为RefrectiveMethodInvocation是CglibMethodProxy的一个内部类。我们继续看下然后就执行 ExposeInvocationInterceptor里面的invoke方法。

        这里面不会做什么逻辑,记录一下当前的mi,然后调用mi的proceed方法,也就是上面传入的 cglibAopProxy。

        然后继续回调用父类的proceed方法,也就是 ReflectiveMethodInvocation.

        此时的下标index就变成1,就会取出第二interceptor,也就是我们AspectAfterThrowingAdvice。

        然后就会执行AspectJAfterThrowingAdvice里面的invoke方法,我们继续往下跟

AspectJAfterThrowingAdvice里面的invoke方法。
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			// 执行下一个通知/拦截器  methodInvocation
			return mi.proceed();
		}
		catch (Throwable ex) {
			// 抛出异常
			if (shouldInvokeOnThrowing(ex)) {
				// 执行异常通知
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}

        可以看得到,AspectAfterThrowingAdvice里面捕获一个异常,首先会调回去CglibAopProxy的CglibMethodInvocation的父类RefrectiveMethodInvocation的proceed方法,去继续获取下一个拦截器执行,如果没有拦截器,到时候如果有异常,就会执行我们这里捕获异常的逻辑。我们继续往下跟。

 

        此时的index为2就会取出来第三个interceptor,也就是我们AfterReturningAdviceInterceptor,然后继续调用。

 

        然后继续调回去 CglibAopProxy的CglibMethodInvocation的父类RefrectiveMethodInvocation的proceed方法,继续取出来下一个拦截器。

        然后继续往下跟invoke方法。

 

        然后继续调回去 CglibAopProxy的CglibMethodInvocation的父类RefrectiveMethodInvocation的proceed方法,继续取出来下一个拦截器。

 

        然后继续调用对应的invoke方法。

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (!(mi instanceof ProxyMethodInvocation)) {
			throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
		}
		ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
		ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
		JoinPointMatch jpm = getJoinPointMatch(pmi);
		return invokeAdviceMethod(pjp, jpm, null, null);
	}

	protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
			@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {

		return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
	}

	protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
		Object[] actualArgs = args;
		// 判断通知方法是否有参数
		if (this.aspectJAdviceMethod.getParameterCount() == 0) {
			actualArgs = null;
		}
		try {
			ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
			// TODO AopUtils.invokeJoinpointUsingReflection
			// 反射调用通知方法
			// this.aspectInstanceFactory.getAspectInstance()获取的是切面的实例
			return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("Mismatch on arguments to advice method [" +
					this.aspectJAdviceMethod + "]; pointcut expression [" +
					this.pointcut.getPointcutExpression() + "]", ex);
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}

         这里就会通过反射调用我们实际的方法中,然后再实际的方法中再调回来proceed方法。我们继续往下跟。

         

        继续调回去CglibAopProxy的CglibMethodInvocation的父类RefrectiveMethodInvocation的proceed方法。

        此时就获取得到最后一个interceptor,也就是我们的MethodBeforeadviceInterceptor,然后继续往下 MethodBeforeadviceInterceptor的invoke方法。

        这里就会调用执行对应的切面通知,然后继续调回去CglibAopProxy的CglibMethodInvocation的父类RefrectiveMethodInvocation的proceed方法。

        此时我们的拦截器已经全部获取出来然后执行完毕,这下就是执行真正的方法逻辑。

        此时不要忘记我们这里是递归调用,接下来就是一个个开始往回调用,之前每个拦截器的后续逻辑。

        到此,Spring AOP的拦截器链路的执行过程已经执行完毕。有了这个AOP基础接下来我们就要开始分析,Spring事务是如果工作的。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RISC-V设计-07】- RISC-V处理器设计K0A之CSR

【RISC-V设计-07】- RISC-V处理器设计K0A之CSR 文章目录 【RISC-V设计-07】- RISC-V处理器设计K0A之CSR1.简介2.顶层设计3.端口说明4.寄存器说明5.代码设计6.总结 1.简介 控制和状态寄存器&#xff08;Control and Status Register&#xff0c;简称CSR&#xff09;是用于控制和…

使用历史版本比对法排查C++程序中的内存泄漏问题

目录 1、问题描述 2、使用Process Explorer实时查看程序的虚拟内存占用 2.1、对于内存泄漏问题&#xff0c;需要查看程序占用的虚拟内存 2.2、Windows任务管理器中看不到程序进程占用的虚拟内存&#xff0c;使用Process Explorer工具可以看到 2.3、通过Process Explorer工…

通世智库:姚力渟——第一次走进缓和医疗

2024年7月9日&#xff0c;我因工作原因&#xff0c;第一次以工作者的视角走进了位于北京市中心最具盛名的协和医院缓和医疗门诊&#xff0c;亲临现场去感受缓和医疗给患者及家属的贴心温暖和有益帮助。在此之前&#xff0c;我是一个惧怕医院&#xff0c;并时刻抱着能不去医院就…

Waterfox水狐浏览器:追求性能与隐私的64位网络探索者

大家好&#xff0c;今天电脑天空要为大家详细介绍一款基于Mozilla Firefox源代码开发的浏览器——Waterfox&#xff08;水狐浏览器&#xff09;。它专为64位系统优化&#xff0c;致力于提供更快速、更高效的浏览体验&#xff0c;并高度重视用户隐私保护。 主要特点 1. 高性能…

Stable Diffusion绘画 | 图生图-批量处理

批量处理中&#xff0c;对待处理图片的要求&#xff1a;宽高比一致 修改提示词后批量处理 调整参数&#xff1a; 确保宽高与原图一致增加一定的重绘幅度 调整提示词信息&#xff1a; 批量处理后&#xff0c;出图如下所示&#xff1a; 修改模型后批量处理 恢复提示词&#xf…

【数学建模】 多模数据与智能模型

文章目录 多模数据与智能模型1. 数字图像处理与计算机视觉1.1 数字图像的表示与处理1.1.1 认识OpenCV1.1.2 色彩学1.1.3 常见图像操作 1.2 数字图像的特征点1.2.1 Sobel算子1.2.2 Canny算子1.2.3 Harris角点检测1.2.4 ORB特征点检测 1.3 计算机视觉1.3.1 卷积神经网络1.3.2 YOL…

学习日志8.7--NGFW(Next Generation Firewall)下一代防火墙

目录 一、NGFW&#xff08;Next Generation Firewall&#xff09;下一代防火墙 二、防火墙CLI命令行配置 三、防火墙初始化Web登入 一、NGFW&#xff08;Next Generation Firewall&#xff09;下一代防火墙 防火墙是用来实现识别外部的安全流量&#xff0c;抵御外部的攻击流…

vue3+element-plus+flask 简易【工作日志本】小软件(过程超详细)

终于有时间继续学习技术了&#xff01;开发了一个简易的用于记录日常工作内容的小软件&#xff0c;权当学习和练手。功能如下&#xff1a;用户登录、日志内容的查、增、删、改以及导出。 开发环境&#xff1a; windows 10&#xff0c;mysql 8&#xff0c;Hbuilder X&#xff08…

Linux -- 进度条小程序

目录 一、缓冲区 二、回车与换行 三、进度条 1、版本一 2、版本二 在写小程序之前先来了解两个知识点 一、缓冲区 缓冲区(buffer)&#xff0c;它是内存空间的一部分。也就是说在内存空间中预留了一定的存储空间&#xff0c;这些存储空间用来缓冲输入或者输出的数据&#…

【eNSP模拟实验】交换机调整stp根端口和配置边缘端口

拓扑 关闭提示和设备重命名 将S1~S4交换机都做如下相关对应的配置 <Huawei>sys [Huawei]un in en [Huawei]sys S1 调整根端口 当前S3交换机生成树简要信息&#xff0c;其中e0/0/1是阻塞端口&#xff0c;e0/0/5是根端口。如何让e0/0/5变成阻塞端口&#xff0c;让e0/0/1…

【面试题】IDEA实现Debug远程调试Linux中的系统

有朋友面试时被问到&#xff0c;怎么远程调试部署在Linux中的系统&#xff1f;听到这个问题&#xff0c;那位朋友直接懵了&#xff0c;第一反应是震惊&#xff0c;已经部署在Linux中的系统还能调试&#xff1f; 沉默了几秒&#xff0c;只好说没有远程调试过Linux中的系统&#…

Debian 12 Linux系统安装Mongodb服务器步骤

在本地或云中运行的 Debian12 或 11 Linux 发行版上设置 MongoDB 数据库服务器并不是一件困难的事情&#xff0c;但是&#xff0c;必须知道如何使用终端和 Linux 命令行。虽然 MongoDB 除了社区版之外还提供了企业版&#xff0c;但在这里我们将使用这个免费的开源 NoSQL 文档数…

Spring基础知识学习总结(四)

&#xff08;5&#xff09;Spring新注解 使用上面的注解还不能全部替代xml配置文件&#xff0c;还需要使用注解替代的配置如下&#xff1a; 非自定义的Bean的配置&#xff1a;<bean>加载properties文件的配置&#xff1a;<context:property-placeholder>组件扫描…

Linux 文件、重定向、缓冲区

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a; Linux 目录 一、文件 1、文件的理解&#xff08;浅层&#xff09; 1.文件是什么&#xff1f; 2.文件操作的前提 3.文件的存储 4.一个进程可以打开多个文件吗&#xff1f;如果可以怎么管理的&#xf…

【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式

文章目录 webView使用步骤示例 HttpURLConnection使用步骤示例GET请求POST请求 okHttp使用步骤1. 添加依赖2. 创建OkHttpClient实例3. 创建Request对象构建请求4. 发送请求5. 获取响应 Pull解析方式1. 准备XML数据2. 创建数据类3. 使用Pull解析器解析XML webView WebView 是 An…

三大浏览器Google Chrome、Edge、Firefox内存占用对比

问题 Chrome、Edg、Firefox三家究竟谁的占用少 结论 打开一个页面内存占用 Firefox>Edge>Chrome 打开打量页面内存占用 Firefox>Chrome>Edge 从监视器可以看到Edge增加一个页面增加一个页面不到100M而其它浏览器需要150M左右;Firefox浏览器主线程内存占用800M比…

Java之TCP网络编程

TCP网络编程 1 概述 在TCP通信协议下&#xff0c;计算机网络中不同设备上的应用程序之间可以通信&#xff0c;通信时需严格区分客户端&#xff08;Client&#xff09;与服务器端&#xff08;Server&#xff09;。 在Java中&#xff0c;对于这样基于TCP协议下连接通信的客户端…

数据结构-常见排序的七大排序

1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录…

Arkose 验证码:网络抓取工具的最佳实践

网络爬虫已经成为企业和开发人员最常用的工具&#xff0c;用于有效地从网络中收集数据。当然&#xff0c;大家都会遇到的最常见挑战是大量的 CAPTCHA 出现&#xff0c;这会使工作流程陷入停滞。其中&#xff0c;Arkose Labs Captcha&#xff08;称为 Funcaptcha&#xff09;以其…

探索N卡录制:游戏录屏工具会是网页录屏的最佳伴侣吗?

在这个数字时代&#xff0c;无论是游戏玩家、教育工作者还是内容创作者&#xff0c;高质量的录屏工具都是必不可少的。NVIDIA显卡&#xff08;简称N卡&#xff09;以其卓越的图形处理能力而闻名&#xff0c;而N卡录制功能则进一步扩展了其应用范围&#xff0c;特别是在游戏录屏…