SpringAOP源码解析之advice执行顺序(三)

上一章我们分析了Aspect中advice的排序为Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class,然后advice真正的执行顺序是什么?多个Aspect之间的执行顺序又是什么?就是我们本章探讨的问题。

准备工作

既然需要知道advide的执行顺序,那么我们就得有Aspect。我们还是使用之前创建的那个ThamNotVeryUsefulAspect,代码内容如下:

@Component
@Aspect
public class ThamNotVeryUsefulAspect {@Pointcut("execution(* com.qhyu.cloud.aop.service.QhyuAspectService.*(..))") // the pointcut expressionprivate void thamAnyOldTransfer() {} // the pointcut signature@Before("thamAnyOldTransfer()")public void before(){System.out.println("tham Before 方法调用前");}@After("thamAnyOldTransfer()")public void after(){System.out.println("tham After 方法调用前");}@AfterReturning("thamAnyOldTransfer()")public void afterReturning(){System.out.println("tham afterReturning");}@AfterThrowing("thamAnyOldTransfer()")public void afterThrowing(){System.out.println("tham AfterThrowing");}@Around("thamAnyOldTransfer()")public Object  around(ProceedingJoinPoint pjp) throws Throwable{// start stopwatchSystem.out.println("tham around before");Object retVal = pjp.proceed();// stop stopwatchSystem.out.println("tham around after");return retVal;}
}

Pointcut(官网使用地址)指向的是执行QhyuAspectService接口定义的任意方法。

public interface QhyuAspectService {void test();
}@Component
public class QhyuAspectServiceImpl implements QhyuAspectService {@Overridepublic void test() {System.out.println("执行我的方法");}
}

然后就是启动类调用一下

public class QhyuApplication {public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext =new AnnotationConfigApplicationContext(AopConfig.class);//test(annotationConfigApplicationContext);aspectTest(annotationConfigApplicationContext);//eventTest(annotationConfigApplicationContext);//transactionTest(annotationConfigApplicationContext);}private static void aspectTest(AnnotationConfigApplicationContext annotationConfigApplicationContext) {QhyuAspectService bean1 = annotationConfigApplicationContext.getBean(QhyuAspectService.class);bean1.test();}}

到此我们就准备就绪了,接下来就开发源码层面的分析。

JdkDynamicAopProxy

wrapIfNecessary的时候会为我们的类创建代理对象,这边没有强制使用cglib,@EnableAspectJAutoProxy中proxyTargetClass default false。

由于我的QhyuAspectServiceImpl实现了QhyuAspectService接口,所以我这里就是用JdkDynamicAopProxy来创建代理对象。所以QhyuAspectServiceImpl的test方法调用的时候会进入JdkDynamicAopProxy的invoke方法。源码如下:

@Nullablepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;try {if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {// The target does not implement the equals(Object) method itself.return equals(args[0]);}else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {// The target does not implement the hashCode() method itself.return hashCode();}else if (method.getDeclaringClass() == DecoratingProxy.class) {// There is only getDecoratedClass() declared -> dispatch to proxy config.return AopProxyUtils.ultimateTargetClass(this.advised);}else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal;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);// Get the interception chain for this method.List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct// reflective invocation of the target, and avoid creating a MethodInvocation.if (chain.isEmpty()) {// 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 = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {// We need to create a method invocation...MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.// 执行拦截器链retVal = invocation.proceed();}// Massage return value if necessary.Class<?> returnType = method.getReturnType();if (retVal != null && retVal == target &&returnType != Object.class && returnType.isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this" and the return type of the method// is type-compatible. Note that we can't help if the target sets// a reference to itself in another returned object.retVal = proxy;}else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}

this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 就是获取到我们之前排序的Aspect的advice,顺序就是之前排序的顺序。

在这里插入图片描述

ExposeInvocationInterceptor是Spring AOP框架中的一个拦截器(Interceptor),用于在方法调用期间将当前代理对象暴露给AopContext。

AopContext是Spring提供的一个工具类,用于获取当前代理对象。在使用AOP进行方法拦截时,Spring会为每个代理对象创建一个AopContext实例,并在方法调用前将当前代理对象设置到AopContext中。这样,在方法内部可以通过AopContext来获取当前代理对象,进而在方法内部调用代理的其他方法,实现方法间的相互调用。

ExposeInvocationInterceptor的作用就是在方法调用期间将当前代理对象设置到AopContext中。它是整个AOP拦截器链中的第一个拦截器,确保在后续的拦截器或切面中可以通过AopContext获取到当前代理对象。

为什么每个AOP都需要ExposeInvocationInterceptor呢?这是因为AOP框架需要保证在方法调用期间能够正确地处理代理对象。由于AOP是通过动态代理机制实现的,代理对象会被包装在一个拦截器链中,并在方法调用时依次通过这个链进行处理。为了能够正确地传递当前代理对象,需要借助ExposeInvocationInterceptor来在方法调用前将代理对象设置到AopContext中。

需要注意的是,ExposeInvocationInterceptor是Spring AOP框架特有的拦截器,在其他AOP框架中可能没有相应的实现。它的存在是为了支持Spring AOP中的AopContext功能,以便在AOP拦截器链中获取当前代理对象。

AspectJAroundAdvice、MethodBeforeAdviceInterceptor、AspectJAfterAdvice、AfterReturningAdviceInterceptor、AspectJAfterThrowingAdvice就是我们的@Around、@Before、@After、@AfterReturning、@AfterThrowing。

重点来了,接下来将逐一分析ReflectiveMethodInvocation、AspectJAroundAdvice、MethodBeforeAdviceInterceptor、AspectJAfterAdvice、AfterReturningAdviceInterceptor、AspectJAfterThrowingAdvice。

ReflectiveMethodInvocation

这段代码是ReflectiveMethodInvocation类中的proceed方法,这个proceed()方法是Spring框架中的ReflectiveMethodInvocation类的一个重要方法。在AOP(面向切面编程)中,拦截器用于在方法调用之前、之后或者周围执行特定的操作。这个proceed()方法在方法调用过程中起到关键作用。我来分解一下提供的代码:

  1. this.currentInterceptorIndex表示当前拦截器的索引。开始时,索引值为-1,然后提前递增。

  2. 这个条件语句检查当前拦截器索引是否等于拦截器列表的最后一个索引。如果是,说明已经到达了拦截器链的末尾,调用invokeJoinpoint()方法执行实际的方法调用,并返回其结果。

  3. 如果当前拦截器索引不是最后一个,就获取下一个拦截器或者拦截器通知(interceptorOrInterceptionAdvice)。

  4. 接下来的条件语句检查interceptorOrInterceptionAdvice的类型。如果是InterceptorAndDynamicMethodMatcher类型,说明它是一个动态方法匹配的拦截器。

    • 该拦截器的动态方法匹配器在这里被评估(已经在静态部分被评估并确定是匹配的)。
    • 如果方法匹配成功,调用该拦截器的invoke(this)方法,并返回结果。
    • 如果方法匹配失败,跳过当前拦截器,继续调用下一个拦截器,通过递归调用proceed()方法实现。
  5. 如果interceptorOrInterceptionAdvice是普通的拦截器(MethodInterceptor类型),直接调用它的invoke(this)方法。

这个方法的逻辑实现了拦截器链的调用和动态方法匹配的过程,确保在方法调用前后可以执行相应的逻辑。

@Override@Nullablepublic Object proceed() throws Throwable {// We start with an index of -1 and increment early.if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}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.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.return proceed();}}else {// It's an interceptor, so we just invoke it: The pointcut will have// been evaluated statically before this object was constructed.return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}}

AspectJAroundAdvice

AspectJAroundAdvice类的invoke方法是一个用于执行环绕通知(around advice)的方法。环绕通知是AOP中一种类型的通知,在目标方法调用前后都能执行特定逻辑。

下面是对AspectJAroundAdviceinvoke方法进行的分析:

@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {// 检查传入的MethodInvocation是否是Spring的ProxyMethodInvocation的实例if (!(mi instanceof ProxyMethodInvocation)) {// 如果不是,抛出IllegalStateException异常throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);}// 将传入的MethodInvocation强制转换为ProxyMethodInvocation类型ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;// 获取ProceedingJoinPoint对象,这是Spring AOP中特定于环绕通知的接口ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);// 获取JoinPointMatch对象,用于匹配切点JoinPointMatch jpm = getJoinPointMatch(pmi);// 调用invokeAdviceMethod方法执行环绕通知的逻辑// 参数为ProceedingJoinPoint、JoinPointMatch以及null、nullreturn invokeAdviceMethod(pjp, jpm, null, null);
}

分析:

  1. 类型检查:首先,方法检查传入的MethodInvocation对象是否是ProxyMethodInvocation的实例。如果不是,说明传入的对象不是Spring代理方法调用的实例,抛出IllegalStateException异常。

  2. 类型转换:如果MethodInvocation对象是ProxyMethodInvocation的实例,就将其强制转换为ProxyMethodInvocation类型,为后续的操作做准备。

  3. 获取ProceedingJoinPoint和JoinPointMatch:通过pmi对象获取ProceedingJoinPoint对象和JoinPointMatch对象。ProceedingJoinPoint是Spring AOP中特定于环绕通知的接口,它包含了目标方法的信息。JoinPointMatch对象用于匹配切点。

  4. 调用环绕通知逻辑:最后,调用invokeAdviceMethod方法,执行环绕通知的逻辑。invokeAdviceMethod方法可能包含了环绕通知的具体实现,它接受ProceedingJoinPointJoinPointMatch以及额外的参数,然后执行通知逻辑并返回结果。

总的来说,这个invoke方法的目的是将Spring AOP中的代理方法调用转换为AspectJ中的ProceedingJoinPoint对象,并执行相应的环绕通知逻辑。

MethodBeforeAdviceInterceptor

MethodBeforeAdviceInterceptorinvoke方法是用于执行"前置通知"(before advice)的关键部分。前置通知是AOP中的一种通知类型,在目标方法执行之前执行特定逻辑。以下是该方法的分析:

public Object invoke(MethodInvocation mi) throws Throwable {// 在目标方法执行前调用通知的before方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());// 继续执行目标方法return mi.proceed();
}

分析:

  1. 调用前置通知的before方法:首先,在目标方法执行前,通过this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis())调用了前置通知(MethodBeforeAdvice接口的实现类)的before方法。这个方法通常用于执行前置逻辑,比如日志记录、权限检查等。before方法接受目标方法(mi.getMethod())、方法参数(mi.getArguments())和目标对象(mi.getThis())作为参数。

  2. 继续执行目标方法:之后,通过mi.proceed()方法继续执行目标方法。这个方法调用会使得程序流程进入被代理的实际目标方法。

这个invoke方法的逻辑非常简单,但是非常重要。它确保了前置通知在目标方法执行前被触发,然后再允许目标方法继续执行。这种机制允许开发者在不修改目标方法的情况下,在方法执行前插入自定义逻辑。

AspectJAfterAdvice

AspectJAfterAdviceinvoke方法是用于执行"后置通知"(after advice)的关键部分。后置通知是AOP中的一种通知类型,在目标方法执行之后执行特定逻辑。以下是该方法的分析:

public Object invoke(MethodInvocation mi) throws Throwable {try {// 继续执行目标方法return mi.proceed();} finally {// 在目标方法执行后,通过invokeAdviceMethod执行后置通知逻辑invokeAdviceMethod(getJoinPointMatch(), null, null);}
}

分析:

  1. 继续执行目标方法:首先,使用mi.proceed()方法继续执行目标方法。这个方法调用会使得程序流程进入被代理的实际目标方法。

  2. 执行后置通知逻辑:使用invokeAdviceMethod(getJoinPointMatch(), null, null)finally块中执行后置通知的逻辑。getJoinPointMatch()方法用于获取匹配的连接点(Join Point)。在这里,AspectJAfterAdvice可能会使用该连接点信息执行后置逻辑。finally块保证无论目标方法是否抛出异常,后置通知的逻辑都会被执行。

这个invoke方法的逻辑非常清晰:它确保了在目标方法执行前后分别执行前置和后置逻辑。在这个方法中,后置通知的逻辑被放在了finally块中,以确保在目标方法执行后无论是否发生异常,都会执行后置通知。这样,开发者可以在方法执行后插入自定义的逻辑,而不需要修改目标方法的代码。

AfterReturningAdviceInterceptor

AfterReturningAdviceInterceptorinvoke方法是用于执行"返回后通知"(after returning advice)的关键部分。返回后通知是AOP中的一种通知类型,在目标方法正常执行并返回后执行特定逻辑。以下是该方法的分析:

public Object invoke(MethodInvocation mi) throws Throwable {// 调用目标方法,并获取其返回值Object returnValue = mi.proceed();// 在目标方法返回后,调用通知的afterReturning方法this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());// 返回目标方法的返回值return returnValue;
}

分析:

  1. 调用目标方法:首先,使用mi.proceed()方法调用目标方法。这个方法调用会使得程序流程进入被代理的实际目标方法,并获取其返回值。

  2. 执行返回后通知逻辑:在目标方法返回后,使用this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis())调用通知的afterReturning方法。这个方法通常用于执行返回后的逻辑,比如日志记录、结果处理等。afterReturning方法接受目标方法的返回值(retVal)、目标方法(mi.getMethod())、方法参数(mi.getArguments())和目标对象(mi.getThis())作为参数。

  3. 返回目标方法的返回值:最后,将目标方法的返回值返回给调用者。

这个invoke方法的逻辑确保了在目标方法正常返回后,执行自定义的返回后逻辑。这种机制允许开发者在目标方法执行后对其返回值进行处理或记录。

AspectJAfterThrowingAdvice

AspectJAfterThrowingAdviceinvoke方法是用于执行"异常抛出后通知"(after throwing advice)的关键部分。异常抛出后通知是AOP中的一种通知类型,在目标方法抛出异常后执行特定逻辑。以下是该方法的分析:

public Object invoke(MethodInvocation mi) throws Throwable {try {// 调用目标方法return mi.proceed();} catch (Throwable ex) {// 捕获目标方法抛出的异常// 如果满足特定条件,调用通知的invokeAdviceMethod方法if (shouldInvokeOnThrowing(ex)) {invokeAdviceMethod(getJoinPointMatch(), null, ex);}// 重新抛出捕获到的异常throw ex;}
}

分析:

  1. 调用目标方法:首先,使用mi.proceed()方法调用目标方法。这个方法调用会使得程序流程进入被代理的实际目标方法。

  2. 捕获异常:使用try-catch块捕获目标方法抛出的异常(Throwable ex)。

  3. 判断是否调用通知:在catch块内部,通过shouldInvokeOnThrowing(ex)方法判断是否满足特定条件,如果满足,调用通知的invokeAdviceMethod方法。这个方法可能会包含了异常抛出后通知的具体逻辑。如果不满足条件,则不执行通知的逻辑。

  4. 重新抛出异常:最后,无论是否调用了通知,都会重新抛出捕获到的异常。这样做是为了保持异常的传播,使得上层调用者可以处理该异常或者继续传播异常。

总的来说,这个invoke方法的逻辑确保了在目标方法抛出异常后,根据特定条件执行相应的通知逻辑,并且保持异常的传播。这种机制允许开发者在目标方法抛出异常时执行自定义的逻辑。

ThamNotVeryUsefulAspect

QhyuAspectService配合ThamNotVeryUsefulAspect,查看所有advice的执行顺序。直接启动QhyuApplication的main方法。

tham around before
tham Before 方法调用前
执行我的方法
tham afterReturning
tham After 方法调用前
tham around after

下面这个图整理的就是整个调用逻辑。

在这里插入图片描述

然后我创建了一个NotVeryUsefulAspect,@Order让其先执行,查看整体执行流程

@Component
@Aspect
@Order(99)
public class NotVeryUsefulAspect {@Pointcut("execution(* com.qhyu.cloud.aop.service.QhyuAspectService.*(..))") // the pointcut expressionprivate void anyOldTransfer() {} // the pointcut signature@Before("anyOldTransfer()")public void before(){System.out.println("not Before 方法调用前");}@After("anyOldTransfer()")public void after(){System.out.println("not After 方法调用前");}@AfterReturning("anyOldTransfer()")public void afterReturning(){System.out.println("not afterReturning");}@AfterThrowing("anyOldTransfer()")public void afterThrowing(){System.out.println("not AfterThrowing");}@Around("anyOldTransfer()")public Object  around(ProceedingJoinPoint pjp) throws Throwable{// start stopwatchSystem.out.println("not around before");Object retVal = pjp.proceed();// stop stopwatchSystem.out.println("not around after");return retVal;}
}
not around before
not Before 方法调用前
tham around before
tham Before 方法调用前
执行我的方法
tham afterReturning
tham After 方法调用前
tham around after
not afterReturning
not After 方法调用前
not around after

注意:

@AfterReturning@AfterThrowing 通知确实是互斥的,它们中只有一个会在目标方法执行完成后执行。具体取决于目标方法的执行结果:

  • @AfterReturning 通知:会在目标方法成功返回时执行,即使目标方法返回值为 null 也会执行。

  • @AfterThrowing 通知:会在目标方法抛出异常时执行。

如果目标方法成功返回,@AfterReturning 通知会被执行;如果目标方法抛出异常,@AfterThrowing 通知会被执行。这两者之间不会同时执行,只有其中一个会根据目标方法的结果被触发。

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

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

相关文章

基于Python Django 的微博舆论、微博情感分析可视化系统(V2.0)

文章目录 1 简介2 意义3 技术栈Django 4 效果图微博首页情感分析关键词分析热门评论舆情预测 5 推荐阅读 1 简介 基于Python的微博舆论分析&#xff0c;微博情感分析可视化系统&#xff0c;项目后端分爬虫模块、数据分析模块、数据存储模块、业务逻辑模块组成。 Python基于微博…

第八节——Vue渲染列表+key作用

一、列表渲染 vue中使用v-for指令进行列表 <template><div><!-- item 代表 当前循环的每一项 --><!-- index 代表 当前循环的下标--><!-- 注意&#xff1a;必须要加key--><div v-for"(item, index) in arr" :key"index"…

UE5 Blueprint发送http请求

一、下载插件HttpBlueprint、Json Blueprint Utilities两个插件是互相依赖的&#xff0c;启用&#xff0c;重启项目 目前两个是Beta的状态&#xff0c;如果你使用的平台支持就可以使用&#xff0c;我们的项目因为需要取Header的值&#xff0c;所有没法使用这两个插件&#xff0…

DBeaver安装与使用教程(超详细安装与使用教程),好用免费的数据库管理工具

DBeaver安装步骤 资源下载&#xff1a; https://download.csdn.net/download/qq_37181642/88479235 官网地址&#xff1a; https://dbeaver.io/ 安装dbeaver 点击上图.exe安装工具&#xff0c;安装完成后不要打开 。 windows配置hosts 在hosts文件中加入&#xff1a; 127.0.0…

基于SSM民宿预订及个性化服务系统-计算机毕设 附源码 04846

SSM民宿预订及个性化服务系统 摘 要 伴随着国内旅游经济的迅猛发展民宿住宿行在国内也迎来了前所未有的发展机遇。传统的旅游模式已难以满足游客日益多元化的需求&#xff0c;随着人们外出度假的时间越来越长&#xff0c;导致人们在住宿的选择上更加追求舒适、个性化的住宿体验…

Kafka - 3.x 副本不完全指北

文章目录 kafka 副本的基本信息Leader选举过程Kafka Controllerkafka 分区副本Leader的选举流程实际演示① 查看first的详细信息&#xff0c;注意观察副本分布情况② 停掉hadoop103上的kafka进程③ 再次查看first的相信信息&#xff0c;观察副本分布④ 处理分区leader分布不均匀…

Spring Cloud之微服务

目录 微服务 微服务架构 微服务架构与单体架构 特点 框架 总结 SpringCloud 常用组件 与SpringBoot关系 版本 微服务 微服务&#xff1a;从字面上理解即&#xff1a;微小的服务&#xff1b; 微小&#xff1a;微服务体积小&#xff0c;复杂度低&#xff0c;一个微服…

网络协议--TCP:传输控制协议

17.1 引言 本章将介绍TCP为应用层提供的服务&#xff0c;以及TCP首部中的各个字段。随后的几章我们在了解TCP的工作过程中将对这些字段作详细介绍。 对TCP的介绍将由本章开始&#xff0c;并一直包括随后的7章。第18章描述如何建立和终止一个TCP连接&#xff0c;第19和第20章将…

macOS鼠标管理操作增强BetterMouse简体中文

BetterMouse是一款专为Mac用户设计的鼠标增强工具&#xff0c;旨在帮助用户更好地掌握和管理鼠标操作。它提供了全局鼠标手势、高度可定制的鼠标设置选项以及一些有用的鼠标增强功能&#xff0c;如鼠标放大镜、鼠标轨迹和应用程序切换功能。这些功能可以大大提高用户的工作效率…

HarmonyOS鸿蒙原生应用开发设计- 流转图标

HarmonyOS设计文档中&#xff0c;为大家提供了独特的流转图标&#xff0c;开发者可以根据需要直接引用。 开发者直接使用官方提供的流转图标内容&#xff0c;既可以符合HarmonyOS原生应用的开发上架运营规范&#xff0c;又可以防止使用别人的图标侵权意外情况等&#xff0c;减…

基于机器视觉的火车票识别系统 计算机竞赛

文章目录 0 前言1 课题意义课题难点&#xff1a; 2 实现方法2.1 图像预处理2.2 字符分割2.3 字符识别部分实现代码 3 实现效果最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器视觉的火车票识别系统 该项目较为新颖&#xff0c;适合作为竞赛…

[蓝桥杯-610]分数

题面 解答 这一题如果不知道数论结论的话&#xff0c;做这个题会有两种天壤之别的体验 此题包含以下两个数论知识 1. 2^02^12^2...2^(n-1)2^n-1 2. 较大的数如果比较小的数的两倍大1或者小1&#xff0c;则两者互质 所以答案就是2^n-1/2^(n-1) 标程1 我的初次解答 #in…

【html】图片多矩形框裁剪

说明 由于项目中需要对一个图片进行多选择框进行裁剪&#xff0c;所以特写当前的示例代码。 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><base href"/"><title>图片裁剪</tit…

【点云学习PCL 】一

点云学习 说明&#xff1a;仅做学习使用&#xff0c;侵删 参考网址1 一、点云基础 0 概述 PCL&#xff08;Point Cloud Library&#xff09;是用于 2D/3D 图像和点云处理的大型开源跨平台的 C 编程库&#xff0c;PCL 框架实现了大量点云相关的通用算法和高效的数据结构&…

基于XML的Web服务Java接口(JAX-WS)、Jakarta XML Web Services Eclipse 实现

简介 JAX-WS&#xff08;Java API for XML-Based Web Services&#xff09;&#xff0c;是创建web服务的Java编程接口&#xff0c;特别是SOAP服务。是Java XML编程接口之一&#xff0c;是Java SE 和Java EE 平台的一部分。 JAX-WS 2.0 规范是代替JAX-RPC 1.0的下一代Web服务AP…

YOLOv5 onnx \tensorrt 推理

一、yolov5 pt模型转onnx code: https://github.com/ultralytics/yolov5 python export.py --weights yolov5s.pt --include onnx二、onnx 推理 import os import cv2 import numpy as np import onnxruntime import timeCLASSES [person, bicycle, car, motorcycle, airpl…

stable diffusion简介和原理

Stable Diffusion中文的意思是稳定扩散&#xff0c;本质上是基于AI的图像扩散生成模型。 Stable Diffusion是一个引人注目的深度学习模型&#xff0c;它使用潜在扩散过程来生成图像&#xff0c;允许模型在生成图像时考虑到文本的描述。这个模型的出现引起了广泛的关注和讨论&am…

IP 地址查询,快速查询自己的 IP 地址

文章目录 在线结果 在线 http://myip.top/ 结果

【计算机网络笔记】Cookie技术

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

Redis(04)| 数据结构-压缩列表

压缩列表的最大特点&#xff0c;就是它被设计成一种内存紧凑型的数据结构&#xff0c;占用一块连续的内存空间&#xff0c;不仅可以利用 CPU 缓存&#xff0c;而且会针对不同长度的数据&#xff0c;进行相应编码&#xff0c;这种方法可以有效地节省内存开销。 但是&#xff0c;…