《手写Spring渐进式源码实践》实践笔记(第十一章 AOP-基于JDK、Cglib实现对象动态代理)

文章目录

  • 第十一章 基于JDK、Cglib实现对象动态代理
    • 背景
    • 目标
    • 设计
    • 实现
      • 代码结构
      • 类图
      • 代理案例解析
        • 案例代码
        • 运行结果
        • 拆解案例
      • 实现步骤
    • 测试
      • 事先准备
      • 自定义拦截方法
      • 测试用例
      • 测试结果:
    • 总结


第十一章 基于JDK、Cglib实现对象动态代理

背景

  1. 到本章节我们将要从 IOC 的实现,转入到关于 AOP(Aspect Oriented Programming) 内容的开发。在软件行业,AOP 意为:面向切面编程,通过预编译的方式运行期间动态代理实现程序功能的统一维护。AOP 在 Spring 框架中是一个非常重要的内容,使用 AOP 可以对业务逻辑的各个部分进行隔离,从而使各模块间的业务逻辑耦合度降低,提高代码的可复用性,同时也能提高开发效率。

  2. 关于 AOP 的核心技术实现主要是动态代理的使用,就像你可以给一个接口的实现类,使用代理的方式替换掉这个实现类,使用代理类来处理你需要的逻辑。比如:

    @Test
    public void testProxyClass() {IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!");String result = userService.queryUserInfo();System.out.println("测试结果:" + result);
    }
    

    代理类的实现基本都大家都见过,那么有了一个基本的思路后,接下来就需要考虑下怎么给方法做代理呢,而不是代理类。另外怎么去代理所有符合某些规则的所有类中方法呢。如果可以代理掉所有类的方法,就可以做一个方法拦截器,给所有被代理的方法添加上一些自定义处理,比如打印日志、记录耗时、监控异常等。

目标

实现代理所有符合某些规则的所有类中的方法,并通过方法拦截器,进行自定义处理。

设计

  • 在把 AOP 整个切面设计融合到 Spring 前,我们需要解决两个问题,包括:如何给符合规则的方法做代理以及做完代理方法的案例后,把类的职责拆分出来。而这两个功能点的实现,都是以切面的思想进行设计和开发。如果不是很清楚 AOP 是啥,你可以把切面理解为用刀切韭菜,一根一根切总是有点慢,那么用手(代理)把韭菜捏成一把,用菜刀或者斧头这样不同的拦截操作来处理。而程序中其实也是一样,只不过韭菜变成了方法,菜刀变成了拦截方法。整体设计结构如下图:

image-20241023100604405

  • 就像我们在使用 Spring 的 AOP 一样,只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。

  • 先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。

  • 除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 "execution(* cn.suwg.test.bean.IUserService.*(..))",有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。

实现

代码结构

源码实现:https://github.com/swg209/spring-study/tree/main/step11-aop-method-proxy

类图

  1. 整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
  2. AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。
  3. AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。在前面章节关于对象的实例化 CglibSubclassingInstantiationStrategy,我们也使用过 Cglib 提供的功能。

代理案例解析

实现AOP核心功能之前,我们可以先通过一个代理案例,来熟悉代理方法的核心全貌,有助于我们理解后续拆解各个方法,设计解耦功能的AOP实现过程。

案例代码
  // 测试代理方法.@Testpublic void testProxyMethod() {// 目标对象(可以替换成任何的目标对象)Object targetObj = new UserService();// AOP 代理IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),targetObj.getClass().getInterfaces(), new InvocationHandler() {// 方法匹配器MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.suwg.springframework.test.bean.IUserService.*(..))");@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (methodMatcher.matches(method, targetObj.getClass())) {// 方法拦截器MethodInterceptor methodInterceptor = invocation -> {long start = System.currentTimeMillis();try {return invocation.proceed();} finally {System.out.println("监控 - Begin By AOP");System.out.println("方法名称:" + invocation.getMethod().getName());System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");System.out.println("监控 - End\r\n");}};// 反射调用return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));}return method.invoke(targetObj, args);}});String result = proxy.queryUserInfo();System.out.println("测试结果:" + result);}
  • 案例目标: 将一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。
  • 从案例中你可以看到有代理的实现 Proxy.newProxyInstance,有方法的匹配 MethodMatcher,有反射的调用 invoke(Object proxy, Method method, Object[] args),也有用户自己拦截方法后的操作。这样一看其实和我们使用的 AOP 就非常类似了,只不过你在使用 AOP 的时候是框架已经提供更好的功能,这里是把所有的核心过程给你展示出来了。
运行结果

image-20241023105034799

  • 从测试结果可以看到我们已经对 UserService#queryUserInfo 方法进行了拦截监控操作,其实后面我们实现的 AOP 就是现在体现出的结果,只不过我们需要把这部分测试的案例解耦为更具有扩展性的各个模块实现。
拆解案例

image-20241023105930279

  • 拆解过程可以图5,我们需要把代理对象拆解出来,因为它可以是 JDK 的实现也可以是 Cglib 的处理。
  • 方法匹配器操作其实已经是一个单独的实现类了,不过我们还需要把传入的目标对象、方法匹配、拦截方法,都进行统一的包装,方便外部调用时进行一个入参透传。
  • 反射调用: ReflectiveMethodInvocation 的使用,它目前已经是实现 MethodInvocation 接口的一个包装后的类,参数信息包括:调用的对象、调用的方法、调用的入参。

实现步骤

  1. 切点表达式

    Pointcut

    • 切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。
    public interface Pointcut {/*** 获取类过滤器.** @return*/ClassFilter getClassFilter();/*** 获取方法匹配器.** @return*/MethodMatcher getMethodMatcher();}

    ClassFilter

    • 定义类匹配类,用于切点找到给定的接口和目标类。
    public interface ClassFilter {/*** Should the pointcut apply to the given interface or target class?** @param clazz the candidate target class*              * @return whether the advice should apply to the given target class* @param clazz* @return*/boolean matches(Class<?> clazz);}
    

    MethodMatcher

    • 方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
    public interface MethodMatcher {/*** Perform static checking whether the given method matches. If this** @param method* @param targetClass* @return whether or not this method matches statically* @return*/boolean matches(Method method, Class<?> targetClass);
    }

    AspectJExpressionPointcut 切点表达式类

    • 切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
    • matches:pointcutExpression.couldMatchJoinPointsInType(clazz)pointcutExpression.matchesMethodExecution(method).alwaysMatches(),这部分内容可以单独测试验证,参考ApiTest#testAop方法.
    public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {// 切点标记.private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();static {SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);}private final PointcutExpression pointcutExpression;public AspectJExpressionPointcut(String expression) {PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());pointcutExpression = pointcutParser.parsePointcutExpression(expression);}@Overridepublic boolean matches(Class<?> clazz) {return pointcutExpression.couldMatchJoinPointsInType(clazz);}@Overridepublic boolean matches(Method method, Class<?> targetClass) {return pointcutExpression.matchesMethodExecution(method).alwaysMatches();}@Overridepublic ClassFilter getClassFilter() {return this;}@Overridepublic MethodMatcher getMethodMatcher() {return this;}
    }
  2. 包装切面通知消息

    AdvisedSupport

    • AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理
    • TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
    • MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。像我们本文的案例中是做方法监控处理
    • MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务。
    public class AdvisedSupport {// 被代理对对象.private TargetSource targetSource;// 方法拦截器.private MethodInterceptor methodInterceptor;// 方法匹配器(检查目标方法是否符合通知条件).private MethodMatcher methodMatcher;public MethodInterceptor getMethodInterceptor() {return methodInterceptor;}public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}public MethodMatcher getMethodMatcher() {return methodMatcher;}public void setMethodMatcher(MethodMatcher methodMatcher) {this.methodMatcher = methodMatcher;}public TargetSource getTargetSource() {return targetSource;}public void setTargetSource(TargetSource targetSource) {this.targetSource = targetSource;}
    }
  3. 代理抽象实现(JDK & Cglig)

    AopProxy

    • 定义一个标准接口,用于获取代理类。因为具体实现代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定义接口会更加方便管理实现类。
    public interface AopProxy {Object getProxy();
    }

    JdkDynamicAopProxy

    • 基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。
    • getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了 invoke 方法。
    • invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。
    • 这里还有一个 ReflectiveMethodInvocation,其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。
    public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {private final AdvisedSupport advisedSupport;public JdkDynamicAopProxy(AdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}@Overridepublic Object getProxy() {return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),advisedSupport.getTargetSource().getTargetClass(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getTarget().getClass())) {MethodInterceptor methodInterceptor = advisedSupport.getMethodInterceptor();return methodInterceptor.invoke(new ReflectiveMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, args));}return method.invoke(advisedSupport.getTargetSource().getTarget(), args);}
    }

    Cglib2AopProxy

    • 基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
    • 关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。
    public class Cglib2AopProxy implements AopProxy {private final AdvisedSupport advisedSupport;public Cglib2AopProxy(AdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(advisedSupport.getTargetSource().getTarget().getClass());enhancer.setInterfaces(advisedSupport.getTargetSource().getTargetClass());enhancer.setCallback(new DynamicAdvisedInterceptor(advisedSupport));return enhancer.create();}private static class DynamicAdvisedInterceptor implements MethodInterceptor {private final AdvisedSupport advisedSupport;private DynamicAdvisedInterceptor(AdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, objects, methodProxy);if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getClass())) {return advisedSupport.getMethodInterceptor().invoke(methodInvocation);}return methodInvocation.proceed();}}private static class CglibMethodInvocation extends ReflectiveMethodInvocation {private final MethodProxy methodProxy;public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy) {super(target, method, arguments);this.methodProxy = methodProxy;}@Overridepublic Object proceed() throws Throwable {return this.methodProxy.invoke(this.target, this.arguments);}}
    }

测试

事先准备

IUserService接口.

public interface IUserService {String queryUserInfo();String register(String userName);}

UserService

  • 在 UserService 中提供了2个不同方法,另外你还可以增加新的类来加入测试。后面我们的测试过程,会给这个两个方法添加我们的拦截处理,打印方法执行耗时。
public class UserService implements IUserService {@Overridepublic String queryUserInfo() {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "小苏,111111,广州";}@Overridepublic String register(String userName) {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "注册用户: " + userName + " success!";}
}

注意pom中要引入额外的依赖。

   <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.7</version><scope>test</scope></dependency><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency><!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency><!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli --><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-cli</artifactId><version>0.14</version></dependency>

自定义拦截方法

  • 用户自定义的拦截方法需要实现 MethodInterceptor 接口的 invoke 方法,使用方式与 Spring AOP 非常相似,也是包装 invocation.proceed() 放行,并在 finally 中添加监控信息。
public class UserServiceInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {long start = System.currentTimeMillis();try {return methodInvocation.proceed();} finally {System.out.println("监控 - Begin By AOP");System.out.println("方法名称:" + methodInvocation.getMethod());System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");System.out.println("监控 - End\r\n");}}
}

测试用例

  • 整个案例测试了 AOP 在于 Spring 结合前的核心代码,包括什么是目标对象、怎么组装代理信息、如何调用代理对象。
  • AdvisedSupport,包装了目标对象、用户自己实现的拦截方法以及方法匹配表达式。
  • 之后就是分别调用 JdkDynamicAopProxy、Cglib2AopProxy,两个不同方式实现的代理类,看看是否可以成功拦截方法
public class ApiTest {// 测试动态代理@Testpublic void testDynamic() {// 目标对象IUserService userService = new UserService();// 组装代理信息AdvisedSupport advisedSupport = new AdvisedSupport();advisedSupport.setTargetSource(new TargetSource(userService));advisedSupport.setMethodInterceptor(new UserServiceInterceptor());advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.suwg.springframework.test.bean.IUserService.*(..))"));// 代理对象(JdkDynamicAopProxy)IUserService proxyJdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();// 测试调用System.out.println("测试结果:" + proxyJdk.queryUserInfo());// 代理对象(Cglib2AopProxy)IUserService proxyCglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();// 测试调用System.out.println("测试结果:" + proxyCglib.register("小苏苏"));}

测试结果:

测试通过,从日志可以看到,通过代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印,内容都可以在控制台完整输出。

image-20241023110823196

总结

  • 从本文对 Proxy#newProxyInstance、MethodInterceptor#invoke,的使用验证切面核心原理以及再把功能拆解到 Spring 框架实现中,可以看到一个貌似复杂的技术其实核心内容往往没有太多,但因为需要为了满足后续更多的扩展就需要进行职责解耦和包装,通过这样设计模式的使用,以此让调用方能更加简化,自身也可以不断按需扩展。

  • AOP 的功能实现目前还没有与 Spring 结合,只是对切面技术的一个具体实现,你可以先学习到如何处理代理对象、过滤方法、拦截方法,以及使用 Cglib 和 JDK 代理的区别,其实这与的技术不只是在 Spring 框架中有所体现,在其他各类需要减少人工硬编码的场景下,都会用到。比如RPC、Mybatis、MQ、分布式任务

  • 一些核心技术的使用上,都是具有很强的关联性的,能把整个技术栈串联起来的过程,需要你来大量的学习、积累、由点到面的铺设,才能在一个知识点的学习拓展到一个知识面和知识体系的建设。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring

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

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

相关文章

今日头条APP移动手机端留痕脚本

这两个的脚本目的是什么呢&#xff1f; 很简单&#xff0c;就是批量访问指定用户的首页&#xff0c;在他人访客记录里面留下你的账户信息&#xff0c;可以让对方访问你的头条&#xff0c;概率下会关注你的头条&#xff0c;目的嘛&#xff0c;这个自己细想&#xff01; 第1个是…

网页上的视频怎么下载下来?三种方法

分享三个简单好用的网页视频下载工具&#xff0c;值得使用&#xff01; 1.IDM IDM 是一款可以提高下载速度达5倍的工具&#xff0c;同时具有恢复、调度和组织下载的功能。如果由于网络问题或意外的电源中断&#xff0c;程序将恢复未完成的下载。 IDM 还具有一个完全功能的站点…

张驰咨询:六西格玛培训费用,到底值不值得花?

六西格玛作为一种先进的管理理念和统计方法&#xff0c;已经在全球范围内得到了广泛的应用和认可。它旨在通过减少流程变异&#xff0c;提高产品质量和客户满意度&#xff0c;从而为企业带来持续的改进和盈利增长。随着六西格玛理念的普及&#xff0c;越来越多的人和企业开始寻…

spark on kubernetes运行测试

测试环境 ● kubernetes 1.20.15 ● default命名空间 ● spark 3.1.2 ● kubectl 运行架构 构建镜像 配置JAVA_HOME下载spark二进制包spark-3.1.2-bin-hadoop3.2.tgz并解压修改kubernetes/dockerfiles/spark/Dockerfile文件 ARG java_image_tag11-jre-slimFROM openjdk:${j…

HBuilder X 中Vue.js基础使用2(三)

一、条件渲染 1、条件判断 v-if &#xff1a; 表达式返回真值时才被渲染 v-else &#xff1a;表达式返回为假时不被渲染 2、 分支条件判断 v-else-if &#xff1a;使用v-if , v-else-if 和 v-else 来表示其他的条件分支 3、显示隐藏 v-show v-show true 把节点显示 …

持续深化信创布局,途普科技与统信软件完成产品兼容性互认证

近日&#xff0c;由北京途普科技有限公司&#xff08;以下简称“途普科技”&#xff09;自主研发的TopGraph图数据库及知识图谱构建平台已成功完成统信服务器操作系统V20的兼容性互认证&#xff0c;标志着途普科技在国产自控技术上又迈出了坚实的一步。 在各项严格的测试环节中…

技术成神之路:设计模式(二十一)外观模式

相关文章&#xff1a;技术成神之路&#xff1a;二十三种设计模式(导航页) 介绍 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供一个统一的接口。外观模式定义了一个高层接口&#xff0c;使得子系统更容易使用。 …

XJ02、消费金融|消费金融业务模式中的主要主体

根据所持有牌照类型的不同,消费金融服务供给方主要分为商业银行、汽车金融公司、消费金融公司和小贷公司,不同类型机构定位不同、提供消费金融服务与产品类型也各不相同。此外,互联网金融平台也成为中国消费金融业务最重要的参与方之一,虽其并非持牌金融机构,但借助其流量…

D50【python 接口自动化学习】- python基础之类

day50 init方法 学习日期&#xff1a;20241027 学习目标&#xff1a;类 -- 64 init方法&#xff1a;如何为对象传递参数&#xff1f; 学习笔记&#xff1a; 魔术方法 init方法 class Klass(object):# 定义初始化方法&#xff0c;类实例化时自动进行初始化def __init__(self…

AGI 之 【Dify】 之 Dify 在 Windows 端本地部署调用 Ollama 本地下载的大模型,实现 API 形式进行聊天对话

AGI 之 【Dify】 之 Dify 在 Windows 端本地部署调用 Ollama 本地下载的大模型&#xff0c;实现 API 形式进行聊天对话 目录 AGI 之 【Dify】 之 Dify 在 Windows 端本地部署调用 Ollama 本地下载的大模型&#xff0c;实现 API 形式进行聊天对话 一、简单介绍 二、创建一个聊…

基于SSM+小程序的旅游社交登录管理系统(旅游4)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 ​ 本旅游社交小程序功能有管理员和用户。管理员有个人中心&#xff0c;用户管理&#xff0c;每日签到管理&#xff0c;景点推荐管理&#xff0c;景点分类管理&#xff0c;防疫查询管理&a…

洞察前沿趋势!2024深圳国际金融科技大赛——西丽湖金融科技大学生挑战赛技术公开课指南

在当前信息技术与“互联网”深度融合的背景下&#xff0c;金融行业的转型升级是热门话题&#xff0c;创新与发展成为金融科技主旋律。随着区块链技术、人工智能技术、5G通信技术、大数据技术等前沿科技的飞速发展&#xff0c;它们与金融领域的深度融合&#xff0c;正引领着新型…

Golang 怎么高效处理ACM模式输入输出

文章目录 问题bufio.NewReader高效的原理 再次提交 问题 最近在练习牛客上单调栈题目时&#xff0c;要求自己处理出入输出&#xff0c;也就是读题库要求的输入&#xff0c;计算最终结果&#xff0c;并打印输出 当我用fmt.Scan处理输入&#xff0c;用fmt.Println处理输出时&am…

R语言笔记(五):Apply函数

文章目录 一、Apply Family二、apply(): rows or columns of a matrix or data frame三、Applying a custom function四、Applying a custom function "on-the-fly"五、Applying a function that takes extra arguments六、Whats the return argument?七、Optimized…

linux开机自启动三种方式

方式一、 1&#xff1a;rc.local 文件 1、执行命令&#xff1a;编辑 “/etc/rc.local” vi /ect/rc.local 2、然后在文件最后一行添加要执行程序的全路径。 例如&#xff0c;每次开机时要执行一个 hello.sh&#xff0c;这个脚本放在 / usr 下面&#xff0c;那就可以在 “/et…

深入了解 Android 中的命名空间:`xmlns:tools` 和其他常见命名空间

在 Android 开发中&#xff0c;xmlns &#xff08;.xml的namespace&#xff09;命名空间是一个非常重要的概念。通过引入不同的命名空间&#xff0c;可以使用不同的属性来设计布局、设置工具属性或者支持自定义视图等。除了 xmlns:tools 以外&#xff0c;还有很多常见的命名空间…

动态IP是什么?

随着互联网成为人们生活的重要组成部分&#xff0c;以信息传递为主导的时代种&#xff0c;网络连接质量对我们的工作效率、学习进度以及娱乐体验等方面都有很大影响。 动态IP&#xff0c;作为网络连接中的一种重要IP代理形式&#xff0c;越来越受到用户的欢迎。本文将深入解析…

计算机网络-CSMA/CD协议笔记及“争用期”的理解

假设a和b是总线型网络上相距最远的两个节点。 从零这个时刻a节点会往信道上发送数据&#xff0c;那么a节点发送的第一个比特&#xff0c;需要经过τ这么长的时间&#xff0c;也就是经过一个单向的传播时延之后。它的这个信号才可以被最远的这个节点检测到。那如果b结点在τ这个…

以bat脚本实现自动识别盘符名称

以bat脚本实现自动识别盘符名称 引言以bat脚本实现自动识别盘符名称运行结果 引言 请听题&#xff0c;如何自动识别电脑盘符的名称&#xff0c;比如&#xff0c;F盘的盘符名称为office&#xff0c;我应该如何自动识别呢&#xff1f; 这里我是以bat脚本实现 以bat脚本实现自动…

平均误差ME、均方误差MSE、均方根误差RMSE、平均均方根误差ARMSE辨析

四个性能指标的定义和作用的解释 ME(k) - 平均误差(Mean Error) 公式: M E ( k ) = ( 1 / M ) ∗ Σ ( x k − x ^ k ) , m = 1 , . . . , M ME(k) = (1/M) * Σ(xk - x̂k), m = 1, ..., M ME(k)=(1/M)∗Σ(xk−