AspectJ 中通知方法参数绑定

我们知道 AspectJ 中的通知方法可以携带参数,例如 @Before 前置通知方法可以携带一个 JoinPoint 类型参数,那么还可以携带其它参数吗?

示例一

@Before(value = "execution(* *..UserServiceImpl.doSome(String))", argNames = "joinPoint,name")
public void beforeSome(JoinPoint joinPoint, String name) {System.out.println("前置增强");Object[] args = joinPoint.getArgs();if (args.length > 0) {System.out.println("前置增强目标方法参数值为:");for (Object arg : args) {System.out.println(arg);}}
}

上面的代码不仅携带了 JoinPoint 类型的参数,还携带了一个 String 类型的参数。会正常执行吗?

// 将通知注解属性 argNames 处理成 argumentNames
public void setArgumentNamesFromStringArray(String... args) {this.argumentNames = new String[args.length];for (int i = 0; i < args.length; i++) {this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);// 校验是否是有效的 Java 标识符if (!isVariableName(this.argumentNames[i])) {throw new IllegalArgumentException("'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +this.argumentNames[i] + "' that is not a valid Java identifier");}}if (this.argumentNames != null) {// 校验数量,默认可以在 argNames 属性中不指定固定的三个参数类型对应的名称,如果指定了就不做处理if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {// May need to add implicit join point arg name...Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];if (firstArgType == JoinPoint.class ||firstArgType == ProceedingJoinPoint.class ||firstArgType == JoinPoint.StaticPart.class) {String[] oldNames = this.argumentNames;this.argumentNames = new String[oldNames.length + 1];this.argumentNames[0] = "THIS_JOIN_POINT";System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);}}}
}public final void calculateArgumentBindings() {// 校验标识为 true 或者没有参数,直接返回if (this.argumentsIntrospected || this.parameterTypes.length == 0) {return;}int numUnboundArgs = this.parameterTypes.length;// 获取实际的参数数量,也就是说,可以不在 argNames 中指定通知方法参数名称Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();// 处理三个固定参数,修改对应索引 joinPointArgumentIndex、joinPointStaticPartArgumentIndexif (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||maybeBindJoinPointStaticPart(parameterTypes[0])) {numUnboundArgs--;}// 除三个固定参数外 ,还存在其它参数if (numUnboundArgs > 0) {// need to bind arguments by name as returned from the pointcut matchbindArgumentsByName(numUnboundArgs);}this.argumentsIntrospected = true;
}// AbstractAspectJAdvice
private void bindArgumentsByName(int numArgumentsExpectingToBind) {// 未在 argNames 属性中指定通知方法参数名称if (this.argumentNames == null) {// 通过 ASM 获取指定通知方法参数名称this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod);}if (this.argumentNames != null) {// 绑定显式参数bindExplicitArguments(numArgumentsExpectingToBind);}else {throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +"requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +"the argument names were not specified and could not be discovered.");}
}
// AbstractAspectJAdvice
private void bindExplicitArguments(int numArgumentsLeftToBind) {Assert.state(this.argumentNames != null, "No argument names available");this.argumentBindings = new HashMap<>();// 获取方法参数长度,与注解属性 argNames 解析的字符串数组长度作比较,不一致抛出异常int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount();if (this.argumentNames.length != numExpectedArgumentNames) {throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames +" arguments to bind by name in advice, but actually found " +this.argumentNames.length + " arguments.");}// So we match in number...int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {this.argumentBindings.put(this.argumentNames[i], i);}// Check that returning and throwing were in the argument names list if// specified, and find the discovered argument types.if (this.returningName != null) {// 参数名称不一致,抛出异常if (!this.argumentBindings.containsKey(this.returningName)) {throw new IllegalStateException("Returning argument name '" + this.returningName +"' was not bound in advice arguments");}else {// 设置目标方法返回值类型Integer index = this.argumentBindings.get(this.returningName);this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index];}}if (this.throwingName != null) {if (!this.argumentBindings.containsKey(this.throwingName)) {throw new IllegalStateException("Throwing argument name '" + this.throwingName +"' was not bound in advice arguments");}else {Integer index = this.argumentBindings.get(this.throwingName);this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];}}// 为 AspectJExpressionPointcut 设置 pointcutParameterNames 和 pointcutParameterTypes 两个属性configurePointcutParameters(this.argumentNames, argumentIndexOffset);
}private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) {int numParametersToRemove = argumentIndexOffset;if (this.returningName != null) {numParametersToRemove++;}if (this.throwingName != null) {numParametersToRemove++;}String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();int index = 0;// 小于偏移量,非显式参数,参数名称和 returningName、throwingName 两个字段值一样,也过滤掉for (int i = 0; i < argumentNames.length; i++) {if (i < argumentIndexOffset) {continue;}if (argumentNames[i].equals(this.returningName) ||argumentNames[i].equals(this.throwingName)) {continue;}pointcutParameterNames[index] = argumentNames[i];pointcutParameterTypes[index] = methodParameterTypes[i];index++;}this.pointcut.setParameterNames(pointcutParameterNames);this.pointcut.setParameterTypes(pointcutParameterTypes);
}

在 ReflectiveAspectJAdvisorFactory#getAdvice 中将 adviceMethod 封装成 springAdvice 时,得到注解属性 argNames,不为 null,将其赋值给 AbstractAspectJAdvice 中 arguementNames 字段,接着 AbstractAspectJAdvice#calculateArgumentBindings --> AbstractAspectJAdvice#bindArgumentsByName,此时由于 arguementNames 不为 null 并不会通过 ASM 去读取方法参数名称,执行AbstractAspectJAdvice#bindExplicitArguments 绑定显式参数,为 AbstractAspectJAdvice 中 argumentBindings 赋值,是一个 HashMap,key 为参数名称,value 为参数位置索引,之后执行 AbstractAspectJAdvice#configurePointcutParameters 配置切入点方法参数,固定的三个参数、returningName 和 throwingName 都不算作切入点方法显式参数,接着为 AspectJExpressionPointcut 设置 pointcutParameterNames 和 pointcutParameterTypes 两个属性。

到这里,其实上面的配置都不会报错。

// AspectJExpressionPointcut
private PointcutExpression buildPointcutExpression(@Nullable ClassLoader classLoader) {PointcutParser parser = initializePointcutParser(classLoader);// 封装 PointcutParameter 数组PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];for (int i = 0; i < pointcutParameters.length; i++) {pointcutParameters[i] = parser.createPointcutParameter(this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);}return parser.parsePointcutExpression(replaceBooleanOperators(resolveExpression()),this.pointcutDeclarationScope, pointcutParameters);
}
// PointcutParser
protected Pointcut resolvePointcutExpression(String expression, Class<?> inScope, PointcutParameter[] formalParameters) {try {PatternParser parser = new PatternParser(expression);parser.setPointcutDesignatorHandlers(pointcutDesignators, world);Pointcut pc = parser.parsePointcut(); // more correctly: parsePointcut(true)validateAgainstSupportedPrimitives(pc, expression);IScope resolutionScope = buildResolutionScope((inScope == null ? Object.class : inScope), formalParameters);pc = pc.resolve(resolutionScope);return pc;} catch (ParserException pEx) {throw new IllegalArgumentException(buildUserMessageFromParserException(expression, pEx));}
}
private IScope buildResolutionScope(Class<?> inScope, PointcutParameter[] formalParameters) {if (formalParameters == null) {formalParameters = new PointcutParameter[0];}// 封装 FormalBinding 数组FormalBinding[] formalBindings = new FormalBinding[formalParameters.length];for (int i = 0; i < formalBindings.length; i++) {formalBindings[i] = new FormalBinding(toUnresolvedType(formalParameters[i].getType()), formalParameters[i].getName(), i);}if (inScope == null) {return new SimpleScope(getWorld(), formalBindings);} else {ResolvedType inType = getWorld().resolve(inScope.getName());ISourceContext sourceContext = new ISourceContext() {public ISourceLocation makeSourceLocation(IHasPosition position) {return new SourceLocation(new File(""), 0);}public ISourceLocation makeSourceLocation(int line, int offset) {return new SourceLocation(new File(""), line);}public int getOffset() {return 0;}public void tidy() {}};return new BindingScope(inType, sourceContext, formalBindings);}
}
// org.aspectj.weaver.patterns.Pointcut
public final Pointcut resolve(IScope scope) {assertState(SYMBOLIC);Bindings bindingTable = new Bindings(scope.getFormalCount());IScope bindingResolutionScope = scope;if (typeVariablesInScope.length > 0) {bindingResolutionScope = new ScopeWithTypeVariables(typeVariablesInScope, scope);}this.resolveBindings(bindingResolutionScope, bindingTable);bindingTable.checkAllBound(bindingResolutionScope);this.state = RESOLVED;return this;
}
// SimpleScope bindings 就是构造 BindingScope 时传入的 formalBindings
public int getFormalCount() {return bindings.length;
}
public Bindings(int count) {this(new BindingPattern[count]);
}
public Bindings(BindingPattern[] bindings) {// BindingPattern 数组this.bindings = bindings;
}
// 校验所有绑定
public void checkAllBound(IScope scope) {// 遍历 BindingPattern 数组,由前面可知数组中元素值为 nullfor (int i = 0, len = bindings.length; i < len; i++) {if (bindings[i] == null) {// 当 FormalBingding 为 隐式类型时,才避免抛出异常if (scope.getFormal(i) instanceof FormalBinding.ImplicitFormalBinding) {bindings[i] = new BindingTypePattern(scope.getFormal(i), false);} else {scope.message(IMessage.ERROR, scope.getFormal(i), "formal unbound in pointcut ");}}}}// SimpleScope
public FormalBinding getFormal(int i) {// 返回构造 BindingScope 时传入的 formalBindings 数组按指定索引对应的元素return bindings[i];
}

接着执行到创建代理,匹配目标对象对应的 Advisor 时,执行 AspectJExpressionPointcut#obtainPointcutExpression,开始对切入点表达式进行解析。先创建一个 PointcutParser,接着利用
前面设置的 pointcutParameterNames 和 pointcutParameterTypes 封装 org.aspectj.weaver.tools.PointcutParameter 数组,然后对切入点表达式进行解析。在 org.aspectj.weaver.tools.PointcutParser#resolvePointcutExpression 中执行到 buildResolutionScope 时,利用 PointcutParameter 数组,封装 FormalBinding 数组,之后执行
Pointcut#resolve,在 checkAllBound 方法中,可以看到,凡是自定义的显式参数,都会报错,抛出异常。

示例二

@Before(value = "execution(* *..UserServiceImpl.doSome(String))")
public void beforeSome(JoinPoint joinPoint, String name) {System.out.println("前置增强");Object[] args = joinPoint.getArgs();if (args.length > 0) {System.out.println("前置增强目标方法参数值为:");for (Object arg : args) {System.out.println(arg);}}
}

通过前面的介绍,未指定 argNames 注解属性时,在 calculateArgumentBindings --> bindArgumentsByName 中,由于 argumentNames 为 null,通过 ASM 读取参数名称,紧接着执行 bindExplicitArguments --> configurePointcutParameters,为 AspectJExpressionPointcut 中 pointcutParameterNames 和 pointcutParameterTypes 赋值。

也就是说,一直到 springAdvice 封装完成,也不会报错。

接着创建代理,AopUtils#canApply 过滤需要的 Advisor,解析切入点表达式,org.springframework.aop.aspectj.AspectJExpressionPointcut#buildPointcutExpression 创建
org.aspectj.weaver.tools.PointcutExpression。由于存在 pointcutParameterNames,会为创建的 PointcutParameter 数组赋值。在 org.aspectj.weaver.tools.PointcutParser#resolvePointcutExpression 中执行具体的解析。buildResolutionScope 方法中,由于存在 PointcutParameter 数组,进一步将其
封装为 FormalBinding 数组,之后封装一个 BindingScope 对象,作为 IScope 返回,之后将 BindingScope 作为参数,执行 Pointcut#resolve --> checkAllBound,开始为 
BindingPattern 数组赋值,因为存在显式参数,直接报错。

示例三

@AfterReturning(value = "execution(* *..UserServiceImpl.doThird())", returning = "result", argNames = "obj")
public void afterReturning(Object result) {System.out.println("后置增强: 方法返回值为 --> " + result.toString());
}

为 AbstractAspectJAdvice 设置完 returningName 属性之后,calculateArgumentBindings --> bindArgumentsByName,此时由于 argumentNames 不为 null,在执行 bindExplicitArguments 方法时,发现 returningName 和 argumentNames 对应索引下的名称不一致,抛出异常。

也就是说,当注解 returning 属性和 argNames 属性一致时,即使和方法实际参数名称不一致,也能正常执行。

如果不设置 argNames 属性, 即 AbstractAspectJAdvice 中 argumentNames 字段为 null,此时通过 ASM 读取方法实际参数名称,为 argumentNames 赋值。接着在 bindExplicitArguments 方法中,发现 returning 注解属性配置的名称和方法参数实际参数名称不一致,抛出异常。

@AfterReturning(value = "execution(* *..UserServiceImpl.doThird())", returning = "result")
public void afterReturning(Object result) {System.out.println("后置增强: 方法返回值为 --> " + result.toString());
}

正常情况下, 未指定 argNames 属性,ASM 读取方法实际参数名称,为 argumentNames 赋值。接着执行 bindExplicitArguments --> configurePointcutParameters,过滤掉 returningName 和 throwingName 之后,将 AspectJExpressionPointcut 中 pointcutParameterNames 和 pointcutParameterTypes 都赋值为长度是0的数组。
所以在之后执行切点表达式解析时,PointcutParameter 数组长度为 0,org.aspectj.weaver.patterns.Bindings#checkAllBound 时按不存在显式参数处理。

示例四

@AfterReturning(value = "execution(* *..UserServiceImpl.doThird())", returning = "result")
public void afterReturning(Object result) {System.out.println("后置增强: 方法返回值为 --> " + result.toString());
}

下来看下方法调用时,目标方法的执行结果,是怎么传递给通知方法的?

// AfterReturningAdviceInterceptor
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {// 调用连接点方法,得到返回值Object retVal = mi.proceed();// AspectJAfterReturningAdvicethis.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;
}
@Override
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {// 校验返回值参数类型if (shouldInvokeOnReturnValueOf(method, returnValue)) {invokeAdviceMethod(getJoinPointMatch(), returnValue, null);}
}protected Object invokeAdviceMethod(@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)throws Throwable {// 在 argBinding 中完成通知方法调用参数的封装,之后调用通知方法return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
// AbstractAspectJAdvice#getJoinPoint
public static JoinPoint currentJoinPoint() {MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();if (!(mi instanceof ProxyMethodInvocation)) {throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);}ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);if (jp == null) {// 创建 JoinPoint 对象jp = new MethodInvocationProceedingJoinPoint(pmi);pmi.setUserAttribute(JOIN_POINT_KEY, jp);}return jp;
}
// AbstractAspectJAdvice
protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch,@Nullable Object returnValue, @Nullable Throwable ex) {// 校验过会直接返回calculateArgumentBindings();// AMC start// 封装通知方法调用参数Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];int numBound = 0;// 不存在固定参数,初始值为 -1if (this.joinPointArgumentIndex != -1) {adviceInvocationArgs[this.joinPointArgumentIndex] = jp;numBound++;}else if (this.joinPointStaticPartArgumentIndex != -1) {adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();numBound++;}if (!CollectionUtils.isEmpty(this.argumentBindings)) {// binding from pointcut matchif (jpMatch != null) {PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();for (PointcutParameter parameter : parameterBindings) {String name = parameter.getName();Integer index = this.argumentBindings.get(name);adviceInvocationArgs[index] = parameter.getBinding();numBound++;}}// binding from returning clauseif (this.returningName != null) {Integer index = this.argumentBindings.get(this.returningName);// 赋值adviceInvocationArgs[index] = returnValue;numBound++;}// binding from thrown exceptionif (this.throwingName != null) {Integer index = this.argumentBindings.get(this.throwingName);adviceInvocationArgs[index] = ex;numBound++;}}if (numBound != this.parameterTypes.length) {throw new IllegalStateException("Required to bind " + this.parameterTypes.length +" arguments, but only bound " + numBound + " (JoinPointMatch " +(jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");}return adviceInvocationArgs;
}protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {Object[] actualArgs = args;if (this.aspectJAdviceMethod.getParameterCount() == 0) {actualArgs = null;}try {ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);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();}
}

可以看到,由于是后置通知,在调用完连接点方法,即目标方法之后,发起后置通知方法的调用,会创建一个 MethodInvocationProceedingJoinPoint,作为 JoinPoint 对象,由于通知方法中并无 JoinPoint 参数,所以在 argBinding 方法中封装通知方法调用参数时,并不会加入这个 JoinPoint 对象。封装完通知方法参数数组,利用反射调用通知方法。

这样,就完成了 @AfterReturning 注解下通知方法可以携带一个目标方法返回值参数的实现。 

 如果是 @Before 注解,就会用到这个 MethodInvocationProceedingJoinPoint 对象,在第一次调用 calculateArgumentBindings 方法时,对三个固定参数的判断,就会修改 joinPointArgumentIndex 和 joinPointStaticPartArgumentIndex 索引。所以封装通知方法参数数组时就会在第一个位置加上这个 MethodInvocationProceedingJoinPoint  对象。

// MethodInvocationProceedingJoinPoint
@Override
public Object[] getArgs() {if (this.args == null) {this.args = this.methodInvocation.getArguments().clone();}return this.args;
}

可以看到,当获取目标方法参数时,其实还是委托给调用时封装的 MethodInvocation 来获取。

总结

总结一下,就是 AspectJ 中通知方法虽然可以携带参数,但是携带的参数是有限制的,自己随意指定的参数,运行时会报错。从调用的角度考虑,通知方法的调用,是通过拦截器来实现的,目标方法发起调用后,并不会传递通知方法参数,所以这里显式指定的参数就失去了意义。

修改三个固定参数、过滤 returningName 和 throwingName 都是在 spring 中完成的。AspectJExpressionPointcut 是连接 spring 和 AspectJ 的桥梁,如果 AspectJExpressionPointcut
中设置了 pointcutParameterNames 和 pointcutParameterTypes 两个属性,则在解析切入点表达式时,会将这两个属性封装成 org.aspectj.weaver.tools.PointcutParameter 数组,供
AspectJ 使用。
MethodInvocationProceedingJoinPoint 是 spring 对 org.aspectj.lang.JoinPoint 的实现。调用通知方法时参数的封装也是在 spring 中完成的。也就是说,AspectJ 中通知注解提供了 argNames 和 returning
等属性,但是怎么使用这些属性是由 spring 来实现的。

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

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

相关文章

bat脚本实现自动化漏洞挖掘

bat脚本 BAT脚本是一种批处理文件&#xff0c;可以在Windows操作系统中自动执行一系列命令。它们可以简化许多日常任务&#xff0c;如文件操作、系统配置等。 bat脚本执行命令 echo off#下面写要执行的命令 httpx 自动存活探测 echo off httpx.exe -l url.txt -o 0.txt nuc…

Golang 并发机制-6:掌握优雅的错误处理艺术

并发编程可能是提高软件系统效率和响应能力的一种强有力的技术。它允许多个工作负载同时运行&#xff0c;充分利用现代多核cpu。然而&#xff0c;巨大的能力带来巨大的责任&#xff0c;良好的错误管理是并发编程的主要任务之一。 并发代码的复杂性 并发编程增加了顺序程序所不…

数据分析系列--[11] RapidMiner,K-Means聚类分析(含数据集)

一、数据集 二、导入数据 三、K-Means聚类 数据说明:提供一组数据,含体重、胆固醇、性别。 分析目标:找到这组数据中需要治疗的群体供后续使用。 一、数据集 点击下载数据集 二、导入数据 三、K-Means聚类 Ending, congratulations, youre done.

71.StackPanel黑白棋盘 WPF例子 C#例子

就是生成黑白棋盘&#xff0c;利用该控件能自动排列的功能。用一个横向的StackPanel嵌套纵向的StackPanel&#xff0c;然后在里面添加设定好长和高的矩形。 因为StackPanel是按照控件的大小展示的。所以如果不设置长和宽。就会显示不出矩形。 <StackPanel Orientation"…

【吾爱出品】开源桌面组件:widgets

widgets 桌面组件 链接&#xff1a;https://pan.xunlei.com/s/VOIQXVWeQIXS_K7NRvVHun_7A1?pwdgq4j# 一款用 vue3 构建的Windows桌面小部件。 这是桌面组件前端开源组件&#xff0c;作者称&#xff1a;项目还在持续完善中&#xff0c;目前包含键盘演示、抖音热榜、喝水提醒…

【QT笔记】使用QScrollArea实现多行文本样式显示

目录 一、QScrollArea 的基本概念 二、demo代码 三、实现效果 1、页面空间足够&#xff0c;无滚动条时显示效果 2、有滚动条时显示效果 一、QScrollArea 的基本概念 QScrollArea 是 Qt 框架中用于提供一个滚动条区域&#xff0c;允许用户滚动查看比当前可视区域更大的内容…

【CPP】CPP经典面试题

文章目录 引言1. C 基础1.1 C 中的 const 关键字1.2 C 中的 static 关键字 2. 内存管理2.1 C 中的 new 和 delete2.2 内存泄漏 3. 面向对象编程3.1 继承和多态3.2 多重继承 4. 模板和泛型编程4.1 函数模板4.2 类模板 5. STL 和标准库5.1 容器5.2 迭代器 6. 高级特性6.1 移动语义…

vs code 使用教程

一、定义 多行注释vs 找不到上层文件路径选择 或 创建python 虚拟环境git 远程克隆及推送vs code 文件路径vs 使用tensorboard 二、使用 学习网站&#xff1a;https://learn.microsoft.com/zh-cn/visualstudio/python/?viewvs-2022性能分析&#xff1a;https://learn.micros…

Verilog基础(一):基础元素

verilog基础 我先说,看了肯定会忘,但是重要的是这个过程,我们知道了概念,知道了以后在哪里查询。语法都是术,通用的概念是术。所以如果你有相关的软件编程经验,那么其实开启这个学习之旅,你会感受到熟悉,也会感受到别致。 入门 - 如何开始 欢迎来到二进制的世界,数字…

LabVIEW与PLC交互

一、写法 写命令立即读出 写命令后立即读出&#xff0c;在同一时间不能有多个地方写入&#xff0c;因此需要在整个写入后读出过程加锁 项目中会存在多个循环并行执行该VI&#xff0c;轮询PLC指令 在锁内耗时&#xff0c;就是TCP读写的实际耗时为5-8ms&#xff0c;在主VI六个…

接口对象封装思想及实现-笔记

目录 接口对象封装代码分层思想 封装案例封装Tpshop商城登录Tpshop商城登录参数化 接口对象封装 代码分层思想 分层思想&#xff1a;将普通思想分为两层&#xff0c;分为接口对象层和测试脚本层 接口对象层&#xff1a; 对接口进行封装&#xff0c;封装好之后&#xff0c;给测…

Javascript 日期计算如何实现当前日期加一天或者减去一天

• 1. Javascript 如何计算当前日期加一天或者减去一天的返回值 • 1.1. 加一天 • 1.2. 减一天 • 1.3. 解释 1. Javascript 如何计算当前日期加一天或者减去一天的返回值 在JavaScript中&#xff0c;可以通过Date对象来计算当前日期加一天或减去一天。 以下是一个简单的…

C_位运算符及其在单片机寄存器的操作

C语言的位运算符用于直接操作二进制位&#xff0c;本篇简单结束各个位运算符的作业及其在操作寄存器的应用场景。 一、位运算符的简单说明 1、按位与运算符&#xff08;&&#xff09; 功能&#xff1a;按位与运算符对两个操作数的每一位执行与操作。如果两个对应的二进制…

109,【1】攻防世界 web 题目名称-文件包含

进入靶场 直接显示源代码 提示我们通过get方式传递名为filename的参数&#xff0c;同时给出了文件名check.php filenamecheck.php 显示使用了正确的用法&#xff0c;错误的方法 filename./check.php 还是一样的回显 傻了&#xff0c;题目名称是文件包含&#xff0c;需要用到…

算法日记12:SC40树状数组(单点修改)

一、题目 二、题解&#xff1a; 2.1&#xff1a;题目的修改/查询交替进行&#xff0c;一眼就是树状数组的模板题目(当先修改最后查询可以使用前缀和/差分实现)&#xff0c; 2.2&#xff1a;树状数组结构&#xff1a;每一个节点都有其管辖区间 2.2.1:lowbit()函数 : l o w b i…

6 加密技术与认证技术

6 加密技术与认证技术 6.1:对称加密与非对称加密技术 6.1.1:对称加密 对称加密:; 特点: 1、加密强度不高&#xff0c;但效率高;2、密钥分发困难。 常见对称密钥&#xff08;共享秘钥&#xff09;加密算法:DES、3DES(三重DES)、RC-5、IDEA算法。 6.1.1.2非对称加密技术 非对称…

安卓开发,Reason: java.net.SocketTimeoutException: Connect timed out

错误提示&#xff1a; Could not install Gradle distribution from https://services.gradle.org/distributions/gradle-8.9-bin.zip. Reason: java.net.SocketTimeoutException: Connect timed out 解决办法&#xff1a; 1、打开gradle\wrapper\gradle-wrapper.properties …

【Linux】24.进程间通信(3)

文章目录 3.6 systemv共享内存3.6.1 共享内存函数3.6.3 一个简单的共享内存代码实现3.6.4 一个复杂的共享内存代码实现3.6.4 key和shmid的主要区别: 3.7 systemv消息队列&#xff08;了解&#xff09;3.8 systemv信号量&#xff08;了解&#xff09;进程互斥四个问题理解信号量…

2.Mkdocs配置说明(mkdocs.yml)【最新版】

官方文件&#xff1a;Changing the colors - Material for MkDocs 建议详细学习一下上面的官方网站↑↑↑ 我把我目前的配置文件mkdocs.yml代码写在下面&#x1f447;&#x1f3fb; #[Info] site_name: Mkdocs教程 #your site name 显示在左上角 site_url: http://wcowin.wo…

AI大模型:本地部署deepseek

一、安装lmstudio 1、下载网站&#xff1a; LM Studio - Discover, download, and run local LLMs 2、直接安装即可&#xff0c;记住安装的路径 二、下载deepseek模型 2.1、下载的流程 1、下载网站 https://huggingface.co/models 2、在搜索框输入&#xff1a;deepseek …