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

news2024/10/28 10:23:51

文章目录

  • 第十一章 基于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实现过程。

案例代码
  // 测试代理方法.
    @Test
    public 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.*(..))");

                    @Override
                    public 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);
        }
    
    
        @Override
        public boolean matches(Class<?> clazz) {
            return pointcutExpression.couldMatchJoinPointsInType(clazz);
        }
    
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
        }
    
        @Override
        public ClassFilter getClassFilter() {
            return this;
        }
    
        @Override
        public 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;
        }
    
    
        @Override
        public Object getProxy() {
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    advisedSupport.getTargetSource().getTargetClass(), this);
        }
    
        @Override
        public 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;
        }
    
    
        @Override
        public 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;
            }
    
            @Override
            public 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;
            }
    
            @Override
            public 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 {
    @Override
    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "小苏,111111,广州";
    }

    @Override
    public 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 {
    @Override
    public 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 {


    // 测试动态代理
    @Test
    public 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.coloradmin.cn/o/2225372.html

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

相关文章

Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之整体思路篇

系列文章 Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之图片分割篇 图片分类篇,执行计划生成篇,执行篇等后续篇章持续更新中,欢迎关注 文章目录 系列文章Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之整体思路篇构建二维数组构建消除时形态7个元素的…

【无标题】idea 一次性切换多个项目的分支

适合一个目录下面有多个子项目&#xff0c;维护着共同的分支 勾选Settings-Version Control->Git->Executor branch operations on all roots 勾选前&#xff08;是不能一次切换多个项目的分支为同一个分支的&#xff09; 勾选此选项 勾选后&#xff08;可以一次切换多个…

纯css实现瀑布流! 附源码!!!

瀑布流用于展示图片信息,我这里用的背景颜色来代替图片 PC端效果 源码(直接复制粘贴就可以运行了!!!) <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>PC端瀑布流</title><style>.box {w…

开发环境 —— Keil5 芯片包安装却找不到芯片

目 录 Keil 5添加芯片包一、查看芯片包名称二、下载芯片包三、安装芯片包四、安装成功却找不到芯片4.1 解压pack文件4.2 拷贝到指定路径 Keil 5添加芯片包 Keil只是一个开发平台&#xff0c;keil 5软件安装完成后&#xff0c;默认只能创建ARM芯片的工程。如果要创建其它芯片的…

网络编程_day3

#1024程序员节 #三次握手四次挥手#四次挥手#udp#recvfrom#sendto#服务器模型#客户端模型#Linux IO模型#阻塞式IO#非阻塞IO#设置非阻塞的方式 目录 【0】复习 【1】三次握手四次挥手 四次挥手 四次挥手既可以由客户端发起&#xff0c;也可以由服务器发起 【2】udp 1. 通信流程 2…

[OpenCV] 数字图像处理 C++ 学习——17轮廓发现详细讲解+附完整代码

文章目录 前言1.理论基础1.1轮廓发现1.2轮廓发现步骤1.3相关API(1)轮廓发现(find contour)(2)轮廓绘制(draw contour) 2.代码实现2.1图像预处理2.2轮廓发现2.3绘制轮廓2.4Trackbar 调整阈值 3.完整代码 前言 轮廓发现是图像处理中一个重要的操作&#xff0c;可以帮助找到图像中…

基于SSM+微信小程序的快递的管理系统(快递1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的快递管理平台&#xff0c;有管理员&#xff0c;用户&#xff0c;配送员&#xff0c; 1、管理员实现了个人中心&#xff0c;用户管理&#xff0c;配送员管理&#xff0…

IDM下载器 (Internet Download Manager) v6.42.2 中文免激活绿色版

Internet Download Manager (IDM下载器) 是一款先进的下载工具,可以提升您的下载速度高达5倍,支持续传&#xff0c;IDM可以让用户自动下载某些类型的文件&#xff0c;它可将文件划分为多个下载点以更快速度下载&#xff0c;并列出最近的下载&#xff0c;方便访问文件。相对于其…

如何通过sip信令以及抓包文件分析媒体发到哪个地方

前言 问题描述&#xff1a;A的媒体没转发到B&#xff0c;B只能听到回铃音&#xff0c;没有A的说话声音&#xff0c;并且fs这边按正常的信令发送了. 分析流程 分析早期媒体发送到哪一个IP 10.19.0.1发送了一个请求给10.19.0.157这个IP&#xff0c;然而这里的SDP媒体地址&am…

Leetcode 二叉树的右视图

好的&#xff0c;我来用中文详细解释这段代码的算法思想。 问题描述 题目要求给定一个二叉树的根节点&#xff0c;从树的右侧看过去&#xff0c;按从上到下的顺序返回看到的节点值。即&#xff0c;我们需要找到每一层的最右侧节点并将其加入结果中。 算法思想 这道题可以通…

【gRPC】什么是RPC——介绍一下RPC

说起RPC&#xff0c;博主使用CPP手搓了一个RPC项目&#xff0c;RPC简单来说&#xff0c;就是远程过程调用&#xff1a;我们一般在本地传入数据进行执行函数&#xff0c;然后返回一个结果&#xff1b;当我们使用RPC之后&#xff0c;我们可以将函数的执行过程放到另外一个服务器上…

go语言中的Scanf()输入函数

Scanf() 第一种情况 package mainimport "fmt"func main() {var a intfor {fmt.Println("请输入一个整数:")fmt.Scanf("%d", &a)fmt.Println("----------------", a)} }运行结果&#xff1a; 解释&#xff1a; 出现这种现象是因…

Docker下载途径

Docker不是Linux自带的&#xff0c;需要我们自己安装 官网&#xff1a;https://www.docker.com/ 安装步骤&#xff1a;https://docs.docker.com/engine/install/centos/ Docker Hub官网(镜像仓库)&#xff1a;https://hub.docker.com/ 在线安装docker 先卸载旧的docker s…

C/C++小宇宙代码

系列目录 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C圣诞树代码11C/C俄罗斯方块12C/C贪吃蛇13C/C小宇宙代码 目录 系列目录 写在前面 …

LVGL显示图片2——显示GIF图片,图片尺寸注意,图片太大无法显示

1进入图像转换网页中 https://lvgl.io/tools/imageconverter 2选择图片 3设置生成的格式 4点击生成 5复制文件&#xff0c;配置环境 6编写代码&#xff08;&#xff09; //显示一张动图 void demo_gif(){lv_obj_t* screenlv_scr_act();//声名对象LV_IMG_DECLARE(GIF_1);//创…

【数据集】NCEP辐射数据-用于计算漫射天窗比(diffuse skylight ration)

【数据集】NCEP辐射数据-用于计算漫射天窗比(diffuse skylight ration) 漫射天窗比(diffuse skylight ration)地表反射率计算漫射天窗比计算NCEP辐射数据数据下载参考漫射天窗比(diffuse skylight ration) 基于NCEP辐射数据利用Python代码计算漫射天窗比(diffuse skyli…

【实用知识】Spring Boot 优雅捕捉异常的几种姿势

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

ATom:加州理工学院化学电离质谱仪(CIT-CIMS)的现场数据,V2版

目录 简介 摘要 代码 引用 网址推荐 知识星球 机器学习 ATom: In Situ Data from Caltech Chemical Ionization Mass Spectrometer (CIT-CIMS), V2 ATom&#xff1a;加州理工学院化学电离质谱仪&#xff08;CIT-CIMS&#xff09;的现场数据&#xff0c;V2版 简介 该数…

Centos7.9安装MySQL(二进制)

安装包 https://downloads.mysql.com/archives/community/ mysql-8.0.39-linux-glibc2.17-x86_64.tar.xz 1.卸载MariaDB 查看 rpm -qa|grep mariadb卸载 可能名称不一样&#xff0c;记得替换 rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64rpm -qa|grep mariadb 执行…

leetcode228:汇总取件

步骤1&#xff1a;定义题目的计算问题性质 题目要求从一个无重复元素且有序的整数数组 nums 中&#xff0c;找出所有恰好覆盖数组中所有数字的最小区间范围列表。这意味着每个数字都必须被某个区间包含&#xff0c;且没有多余的数字在区间内。 输入&#xff1a;一个有序整数数…