手写实现一个动态代理框架

news2025/3/10 18:55:11

手写实现一个动态代理框架

  • 什么是代理模式
  • 什么是动态代理
  • 动态代理中的编译、类加载与对象实例化
  • 手写实现一个动态代理框架
    • 实现细节
      • DynamicProxyHandler
      • Proxy
        • 生成代码
        • 写入代码到磁盘文件
        • 调用编译器进行编译
        • 调用类加载器进行类加载
        • 反射实例化
        • 删除前面生成的java文件和class文件
      • Coder
    • 来玩一把
  • JDK动态代理和我们的区别

最近写了一个动态代理框架,在这里分享一下,顺便分析一下动态代理的原理,当做复习。

在这里插入图片描述

什么是代理模式

首先我们来了解一下代理模式,代理模式是二十三种设计模式中的其中一种,用于对目标对象进行代理增强。

在这里插入图片描述

代理类通常和目标类实现同一个接口,这样我们就可以用一个接口类型变量去引用一个代理类对象,然后又可以当成目标对象去使用。

在这里插入图片描述

public interface Handler {
	void handle();
}
public class Proxy implements Handler {
	private Handler target;
	
	public Proxy(Handler target) {
		this.target = target;
	}
	...
}
public class Target implements Handler {
	...
}
public class Main {
	public static void main(String[] args) {
		Target target = new Target();
		Handler handler = new Proxy(target);
		...
	}
}

当我们对目标对象做了代理之后,就可以在调用目标对象方法的前后添加一些增强的处理逻辑。


public class Proxy implements Handler {

	private Handler target;
	
	public Proxy(Handler target) {
		this.target = target;
	}

	@Override
	public void handle() {
		pre(); // 前置增强逻辑
		target.handle();
		post(); // 后置增强逻辑
	}
}


public class Target implements Handler {
	@Override
	public void handle() {
		...
	}
}

在这里插入图片描述

比如我们要在目标方法的前后添加日志打印,那么我们可以通过代理模式实现。

public class Proxy implements Handler {
	
	private Handler target;

	@Override
	public void handle() {
		log.info(...);
		target.handle();
		log info(...);
	}
	
}

这样做的好处有两点:

  • 我们可以为目标类添加额外的处理逻辑,而不需要改动目标类原有的代码
  • 我们可以更换目标对象,但是代理类中的增强处理逻辑不用变,更换后的目标对象依然可以复用原先的增强逻辑

什么是动态代理

动态代理是对普通代理模式的优化,普通代理模式有一个缺点,那就是我们需要手动编写代理逻辑。

比如我们的代理逻辑就是在目标方法的前后添加日志打印。如果只有几个接口需要代理,那么我们可以手动编写这几个接口的代理实现类,那是没有问题的。

public interface HandlerA {
	void method1();
	void method2();
	void method3();
	
}
public interface HandlerB {
	void method1();
	void method2();
}
public interface HandlerC {
	void method1();
	void method2();
	void method3();
	void method4();
}

public class ProxyA implements HandlerA {
	
	private HandlerA target;

	@Override
	public void method1() {
		log.info(...);
		target.method1();
		log info(...);
	}
	@Override
	public void method2() {
		log.info(...);
		target.method2();
		log info(...);
	}
	@Override
	public void method3() {
		log.info(...);
		target.method3();
		log info(...);
	}
	
}

public class ProxyB implements HandlerB {
	
	private HandlerB target;

	@Override
	public void method1() {
		log.info(...);
		target.method1();
		log info(...);
	}
	@Override
	public void method2() {
		log.info(...);
		target.method2();
		log info(...);
	}
	
}
public class ProxyC implements HandlerC {
	...
}

但是我们发现,虽然我们编写的代理类实现了不同的接口,重写了不同的接口方法,但是前后添加的增强逻辑其实是一模一样的。而且如果接口比较多的话,我们一个个手写代理实现类也挺麻烦的。

在这里插入图片描述

于是就有了动态代理,动态代理的主要作用就是动态生成代理实现类,不需要我们手动编写。我们只需要定义好增强逻辑,以及需要代理的接口和接口方法,我们就可以通过动态代理框架生成代理类并实例化代理对象。

在这里插入图片描述

比如JDK的动态代理,我们定义好我们的接口比如Handler,然后实现JDK动态代理提供的接口InvocationHandler在里面编写增强逻辑,就可以作为输入参数调用JDK动态代理提供的工具类Proxy生成代理实现类并反射实例化代理对象。

在这里插入图片描述

这样就不需要我们自己编写那么多的代理实现类了,省掉了一大波繁琐的工作,一片岁月静好。

在这里插入图片描述

动态代理中的编译、类加载与对象实例化

我们自己手动编写代理类时,从代理类代码的编写到代理对象的创建,整个流程是这样子:

在这里插入图片描述

代码编写和编译器编译是静态的,因为是在JVM启动之前提前做好的。

动态代理相当于是就是在JVM运行的时候,由动态代理框架根据我们给定的接口和代理逻辑动态编写代理实现类,然后调用编译器进行动态编译,通过类加载器加载到内存中,然后反射实例化。

在这里插入图片描述

在动态代理的情况下,代码是在JVM运行时动态生成的,比如用StringBuilder拼接(也可以是别的方式生成),然后通过调用JDK提供的API进行运行时编译,类加载器加载也是通过调用JDK提供的API进行动态的加载。也就是说从代码生成、编译、类加载、到反射实例化的这一切,都是在JVM已经运行的情况下,通过Java代码动态实现的,因此称为动态代理

手写实现一个动态代理框架

那么,我们就来实现一个我们自己的动态代理框架,非常简单,三个类:Coder、DynamicProxyHandler、Proxy

  • Coder:代理类代码的生成器类,接收一个Class<?>类型的参数interfaceClass,表示代理类要实现interfaceClass这个接口,Coder根据这个接口生成代理类的代码
  • DynamicProxyHandler:留给用户实现的处理器接口,用户需要实现DynamicProxyHandler接口并重新invoke以声明动态代理的增强逻辑
  • Proxy:生成代理对象的工具类,调用Coder生成代理类的代码, 通过编译器动态编译,然后通过类加载器动态加载编译出来的代理类class,最后通过反射创建代理对象返回给用户

在这里插入图片描述

实现细节

DynamicProxyHandler

DynamicProxyHandler就是预留给用户实现的用于编写增强逻辑的接口,用户需要实现DynamicProxyHandler接口,并重写invoke方法,在invoke方法中编写增加逻辑。

/**
 * @author huangjunyi
 * @date 2023/11/28 19:00
 * @desc
 */
public interface DynamicProxyHandler {

    Object invoke(Object proxy, Method method, Object[] args);

}

invoke方法接收三个参数,proxy是代理对象本身,method是当前正在调用的方法的Method对象,args是当前方法接收的参数。

Proxy

Proxy中的代码稍稍有点复杂,不用马上全部看完,先有个大概印象,下面一步步分析里面干了些啥。

/**
 * @author huangjunyi
 * @date 2023/11/28 19:00
 * @desc
 */
public class Proxy {

    private static boolean enablePrintCode = false;

    public static void printCode() {
        enablePrintCode = true;
    }

    public static <T> T  newInstance(ClassLoader classLoader, Class<?> interfaceClass, DynamicProxyHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        String code = Coder.generateCode(interfaceClass);
        if (enablePrintCode) {
            System.out.println(code);
        }
        String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";
        path = path.replace('\\', '/');
        File file = new File(path);
        if (file.getParentFile().exists()) {
            file.getParentFile().mkdir();
        }
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(code);
        fileWriter.close();
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int result = compiler.run(null, null, null, path);
        String parentPath = path.substring(0, path.lastIndexOf("/"));
        URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};
        URLClassLoader loader = new URLClassLoader(urls);
        Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");
        Constructor constructor = c.getConstructor(DynamicProxyHandler.class);
        Object o = constructor.newInstance(handler);
        new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();
        file.delete();
        return (T) o;
    }

}
生成代码

首先调用Coder生成代理类的代码,接收一个接口类型参数interfaceClass,返回生成的代码字符串。

        String code = Coder.generateCode(interfaceClass);

Proxy里面有个boolean类型的enablePrintCode属性,把它设置为true,会打印动态生成的代理类的代码。

        if (enablePrintCode) {
            System.out.println(code);
        }
写入代码到磁盘文件

然后把生成的代码写入到磁盘文件

        String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";
        path = path.replace('\\', '/');
        File file = new File(path);
        if (file.getParentFile().exists()) {
            file.getParentFile().mkdir();
        }
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(code);
        fileWriter.close();

磁盘文件的路径必须在类加载器加载类的目录下,通过 classLoader.getResource(“”).getPath() 取得类加载器加载类的对应目录。

然后把类的包名中的“.”替换成“/”,创建目录。

最后创建文件,通过FileWriter把代码写入到文件中。

调用编译器进行编译

代码写到文件后,就要调用编译器把这个java文件编译成class文件。

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int result = compiler.run(null, null, null, path);

JavaCompiler就是编译器对象,调用JavaCompiler的run方法进行编译,path参数是需要编译的java文件的路径。

调用类加载器进行类加载

编译出class文件后,就要调用类加载器把这个class文件加载到内存中,生成一个Class对象。

        String parentPath = path.substring(0, path.lastIndexOf("/"));
        URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};
        URLClassLoader loader = new URLClassLoader(urls);
        Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");

String parentPath = path.substring(0, path.lastIndexOf(“/”)); 这一行是取得java文件所在目录的路径,编译出来的class文件与java文件是同处一个目录下的,这样我们就可以找到class文件。

然后就是创建URLClassLoader对象,调用URLClassLoader的loadClass方法进行类加载,获得代理类的Class对象。

反射实例化

获得代理类的Class对象后,就要反射进行实例化。

        Constructor constructor = c.getConstructor(DynamicProxyHandler.class);
        Object o = constructor.newInstance(handler);

获得构造器对象constructor,调用构造器的newInstance方法进行实例化。

删除前面生成的java文件和class文件

最后要把生成的文件删除掉,以免留下垃圾。

        new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();
        file.delete();

第一行是删除class文件,第二行是删除java文件。

Coder

接下来看一下Coder类。这个Coder类不必细看,代码虽然多,但是都是使用StringBuilder进行字符串拼接,根据给定的接口拼出一个代理类,没什么技术含量。

/**
 * @author huangjunyi
 * @date 2023/11/28 19:00
 * @desc
 */
public class Coder {

    private static Map<String, String> codeCache = new HashMap<>();

    public static String generateCode(Class<?> interfaceClass) {
        String interfaceClassName = interfaceClass.getName();
        if (codeCache.containsKey(interfaceClassName)) {
            return codeCache.get(interfaceClassName);
        }

        StringBuilder codeBuilder = new StringBuilder();

        // package
        Package aPackage = interfaceClass.getPackage();
        String packageName = aPackage.getName();
        codeBuilder.append("package ").append(packageName).append(";").append("\n");
        codeBuilder.append("\n");

        // import
        Method[] methods = interfaceClass.getDeclaredMethods();
        Set<String> importTypeSet = new HashSet<>();
        for (Method method : methods) {
            Class<?> returnType = method.getReturnType();
            importTypeSet.add(returnType.getName());
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                importTypeSet.add(parameterType.getName());
            }
        }
        for (String importType : importTypeSet) {
            if ("void".equals(importType) ||
                    "long".equals(importType) ||
                    "int".equals(importType) ||
                    "short".equals(importType) ||
                    "byte".equals(importType) ||
                    "char".equals(importType) ||
                    "float".equals(importType) ||
                    "double".equals(importType) ||
                    "boolean".equals(importType)) {
                continue;
            }
            codeBuilder.append("import ").append(importType).append(";").append("\n");
        }
        codeBuilder.append("import java.lang.reflect.Method;");
        codeBuilder.append("import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;");

        // class
        codeBuilder.append("\n\npublic class ")
                .append(interfaceClass.getSimpleName()).append("$$")
                .append(" implements").append(" ")
                .append(interfaceClass.getSimpleName()).append(" {").append("\n\n");

        // field
        codeBuilder.append("\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n");

        // Constructor
        codeBuilder.append("\tpublic ") .append(interfaceClass.getSimpleName()).append("$$")
                .append("(").append("DynamicProxyHandler dynamicProxyHandler) {\n")
                .append("\t\tthis.dynamicProxyHandler = dynamicProxyHandler;").append("\n")
                .append("\t}\n\n");

        // method
        for (Method method : methods) {
            Class<?> returnType = method.getReturnType();
            importTypeSet.add(returnType.getName());
            codeBuilder.append("\t@Override\n");
            codeBuilder.append("\t").append("public ")
                    .append(returnType.getSimpleName()).append(" ")
                    .append(method.getName()).append("(");

            // method parameter
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                codeBuilder.append(parameterType.getSimpleName()).append(" ").append("var").append(i);
                if (i < parameterTypes.length - 1) {
                    codeBuilder.append(", ");
                }
            }

            codeBuilder.append(") ").append(" {").append("\n");

            // method body
            codeBuilder.append("\n\t\tMethod method = null;");
            codeBuilder.append("\n\t\ttry {");
            codeBuilder.append("\n\t\t\tmethod = Class.forName(")
                    .append("\"").append(interfaceClass.getName()).append("\"").append(")")
                    .append(".getDeclaredMethod(").append("\"").append(method.getName()).append("\"");
            if (parameterTypes.length != 0) {
                codeBuilder.append(", ");
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    codeBuilder.append(parameterType.getSimpleName()).append(".").append("class");
                    if (i < parameterTypes.length - 1) {
                        codeBuilder.append(", ");
                    }
                }
            }
            codeBuilder.append(");");
            codeBuilder.append("\n\t\t\tmethod.setAccessible(true);");
            codeBuilder.append("\n\t\t} catch (ClassNotFoundException|NoSuchMethodException e) {");
            codeBuilder.append("\n\t\t\tthrow new RuntimeException(\"error when generate code\");");
            codeBuilder.append("\n\t\t}");
            if (!"void".equals(returnType.getSimpleName())) {
                codeBuilder.append("\n\t\treturn (").append(returnType.getSimpleName()).append(") ")
                        .append("dynamicProxyHandler.invoke(this, method, new Object[]{");
            } else {
                codeBuilder.append("dynamicProxyHandler.invoke(this, method, new Object[]{");
            }


            for (int i = 0; i < parameterTypes.length; i++) {
                codeBuilder.append("var").append(i);
                if (i < parameterTypes.length - 1) {
                    codeBuilder.append(", ");
                }
            }
            codeBuilder.append("});");
            codeBuilder.append("\n").append("\t").append("}");
            codeBuilder.append("\n\n");
        }

        codeBuilder.append("}");
        String code = codeBuilder.toString();

        codeCache.put(interfaceClassName, code);

        return code;
    }

}

Coder 有一个Map<String, String>类型的缓存codeCache,用于缓存曾经生成过的代码,如果下一次再调用Coder的generateCode,传入的参数interfaceClass是同一个接口类型,那么直接取缓存中的结果返回,不再重复生成。

        String interfaceClassName = interfaceClass.getName();
        if (codeCache.containsKey(interfaceClassName)) {
            return codeCache.get(interfaceClassName);
        }

如果缓存中没有,就要通过StringBuilder动态拼接代理类的代码了。

        StringBuilder codeBuilder = new StringBuilder();
        ...

这个拼接的逻辑就不用细看了,只是繁琐的工作,只要知道怎么通过Class对象获取到类信息和方法信息,就可以根据这些信息拼出一个代理实现类。

  • Package aPackage = interfaceClass.getPackage(); 获取包路径。
  • Method[] methods = interfaceClass.getDeclaredMethods(); 获取接口定义的方法。
  • Class<?> returnType = method.getReturnType(); 获取方法返回值类型。
  • Class<?>[] parameterTypes = method.getParameterTypes(); 获取方法参数类型。

我们看看生成的代理类是怎么样的。比如我们有一个Hello接口:

/**
 * @author huangjunyi
 * @date 2023/11/28 19:58
 * @desc
 */
public interface Hello {

    String sayHi(int count);

}

生成的代理类就是这样:

package com.huangjunyi1993.simple.dynamic.proxy.test;

import java.lang.String;
import java.lang.reflect.Method;import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;

public class Hello$$ implements Hello {

	private DynamicProxyHandler dynamicProxyHandler;

	public Hello$$(DynamicProxyHandler dynamicProxyHandler) {
		this.dynamicProxyHandler = dynamicProxyHandler;
	}

	@Override
	public String sayHi(int var0)  {

		Method method = null;
		try {
			method = Class.forName("com.huangjunyi1993.simple.dynamic.proxy.test.Hello").getDeclaredMethod("sayHi", int.class);
			method.setAccessible(true);
		} catch (ClassNotFoundException|NoSuchMethodException e) {
			throw new RuntimeException("error when generate code");
		}
		return (String) dynamicProxyHandler.invoke(this, method, new Object[]{var0});
	}

}

生成的代理类的类名是接口名后加两个“$”,比如这里的接口是Hello,那么生成的代理类就是Hello$$。

public class Hello$$ implements Hello

生成的代理类重写了给定接口定义的所有方法,方法里面的逻辑都是反射获取当前方法的Method对象,然后调用DynamicProxyHandler的invoke方法,把当前代理对象this、当前方法的Method对象method、当前方法参数作为invoke方法的入参。

来玩一把

定义一个Hello接口,就是前面的Hello接口:

/**
 * @author huangjunyi
 * @date 2023/11/28 19:58
 * @desc
 */
public interface Hello {

    String sayHi(int count);

}

Hello接口实现类HelloImpl,目标类(被代理类):

/**
 * @author huangjunyi
 * @date 2023/11/28 20:00
 * @desc
 */
public class HelloImpl implements Hello {
    @Override
    public String sayHi(int count) {
        String result = "";
        for (int i = 0; i < count; i++) {
            result += "hi ";
        }
        return result;
    }
}

实现DynamicProxyHandler接口,编写增强逻辑:

/**
 * @author huangjunyi
 * @date 2023/11/28 20:02
 * @desc
 */
public class HelloHandler implements DynamicProxyHandler {

    private Hello target;

    public HelloHandler(Hello target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        System.out.println("HelloHandler invoke");
        try {
            Object invoke = method.invoke(target, args);
            System.out.println(invoke);
            return invoke;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试类:

/**
 * @author huangjunyi
 * @date 2023/11/24 10:05
 * @desc
 */
public class A {

    @Test
    public void test() throws Exception {
        HelloImpl hello = new HelloImpl();
        HelloHandler helloHandler = new HelloHandler(hello);
        // 调用这个方法,可以打印生成的代理类的代码
        // Proxy.printCode();
        Hello o = Proxy.newInstance(hello.getClass().getClassLoader(), Hello.class, helloHandler);
        o.sayHi(5);
    }

}

运行测试类,控制台输出:

HelloHandler invoke
hi hi hi hi hi 

JDK动态代理和我们的区别

JDK动态代理大体流程和我们的思路是相同的,我们看看JDK的Proxy类的newProxyInstance方法:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {

        final Class<?>[] intfs = interfaces.clone();

        // 生成代理类的Class对象
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
        	// 获取构造器对象
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            
            cons.setAccessible(true);
            
            // 构造器反射实例化
            return cons.newInstance(new Object[]{h});
        } catch (...) {...}
    }

里面我省略了许多杂七杂八的代码,只留下核心流程,可以看到也是生成一个代理类的Class对象,然后反射调用它的构造器生成代理对象。

getProxyClass0(loader, intfs)最终会调到ProxyClassFactory的apply方法:

		public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
			// 。。。。。。
			// 生成的不是字符串,而是二进制byte数组,class字节码文件里面的内容
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
            	// 调用native方法直接进行类的初始化,生成Class对象
            	// 没有写盘、编译、类加载的这几步动作
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                ...
            }
		}

这里就是和我们区别最大的地方,JDK动态代理不是通过字符串拼接生成的代理类代码,而是生成的byte[]类型,里面的内容也不是java文件里面的代码,而是class字节码文件里面的内容。

然后直接调用defineClass0方法(defineClass0方法是native方法)进行类的初始化,省略了写入磁盘(我们也可以设置要保存生成的class文件,这样会把class文件写到磁盘)、编译、调用类加载器进行类加载的这几步。

在这里插入图片描述

全文完。

在这里插入图片描述

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

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

相关文章

LeetCode - 100. 相同的树 (C语言,二叉树,配图,简单)

利用分治思想&#xff0c;将大问题化解成为小问题&#xff0c;我们只需要比较&#xff1a; 1. 根节点的同时为空/不为空&#xff0c;不为空时值相等。 2. 左子树是否一样。 3. 右子树是否一样。 可以看出&#xff0c;这道题非常简单&#xff0c;但是为什么我们还要将它呢&#…

Linux 上的容器技术

容器实现封闭的环境主要要靠两种技术&#xff0c;一种是看起来是隔离的技术&#xff0c;称为 namespace&#xff08;命名空间&#xff09;。在每个 namespace 中的应用看到的&#xff0c;都是不同的 IP 地址、用户空间、进程 ID 等。另一种是用起来是隔离的技术&#xff0c;称为…

【系统运维】Centos部署Haproxy+Keepalived+RabbitMQ高可用集群

1.RabbitMQ高可用集群方案 &#xff08;1&#xff09;RabbitMQ搭建集群的作用&#xff1a;提高可用性、可靠性和处理能力&#xff0c;确保系统提供高效的消息传递服务 高可用性&#xff1a;通过集群&#xff0c;即使其中一个节点发生故障&#xff0c;其他节点仍然可以继续提供…

Elasticsearch:什么是向量数据库?

向量数据库定义 向量数据库是将信息存储为向量的数据库&#xff0c;向量是数据对象的数值表示&#xff0c;也称为向量嵌入。 它利用这些向量嵌入的强大功能来对非结构化数据和半结构化数据&#xff08;例如图像、文本或传感器数据&#xff09;的海量数据集进行索引和搜索。 向…

简明指南:使用Kotlin和Fuel库构建JD.com爬虫

概述 爬虫&#xff0c;作为一种自动化从网络上抓取数据的程序&#xff0c;广泛应用于数据分析、信息提取以及竞争对手监控等领域。不同的实现方式和编程语言都能构建出高效的爬虫工具。在本文中&#xff0c;我们将深入介绍如何充分利用Kotlin和Fuel库&#xff0c;构建一个简单…

Autosar COM通信PDU

文章目录 Autosar 中各个PDU所在示意图PDU的分类PDU 和 SDU 的关系I-PDUN-PDUL-PDU相关协议其他参考 Autosar 中各个PDU所在示意图 PDU的分类 在Autosar 中&#xff0c;主要有 I-PDU、N-PDU和 L-PDU 三种。 L-PDU&#xff1a;Data Link Layer PDU&#xff0c;数据链路层PDUN-…

Qt/QML编程学习之心得:如何添加资源文件到QML工程(十一)

Qt作为一种GUI界面编辑工具&#xff0c;在嵌入式编程中也大受欢迎&#xff0c;而进一步QML出现了&#xff0c;QML我理解也是一种资源文件&#xff0c;因为像其他资源文件一样添加进工程的。那么一个图片如何增加进资源文件呢&#xff1f;这个的确很基础&#xff0c;就是把资源文…

JavaEE 多线程

JavaEE 多线程 文章目录 JavaEE 多线程引子多线程1. 特性2. Thread类2.1 概念2.2 Thread的常见构造方法2.3 Thread的几个常见属性2.4 启动一个线程2.5 中断一个线程2.6 等待一个线程2.7 获取当前线程引用2.8 休眠当前线程 3. 线程状态 引子 当进入多线程这一块内容时&#xff…

Redis中分布式锁的使用

在分布式系统中&#xff0c;如果使用JVM中的同步锁在高并发的场景下仍然会产生线程安全问题。首先我们来查看在多个服务器时为什么会产生线程安全问题&#xff0c;有这样一个案例&#xff0c;有一件商品购买规则为一个用户只能购买一次&#xff0c;如果使用同步锁锁住用户id&am…

vue3中自定义hook函数

使用Vue3的组合API封装的可复用的功能函数 自定义hook的作用类似于vue2中的mixin技术 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂 案例: 收集用户鼠标点击的页面坐标 hooks/useMousePosition.ts文件代码&#xff1a; import { ref, onMounted, onUnmounted …

【读书笔记】微习惯

周日晚上尝试速读一本书《微习惯》&#xff0c;共七章看了下目录结构并不复杂&#xff0c;计划每章7-8分钟读完&#xff0c; 从20:15-21:00。读的时候&#xff0c;订下闹钟&#xff0c;催促着自己的进度。边读边记了一些要点和微信读书里面的划线。 第六章实践内容最为丰富&…

1949-2021年全国31省铁路里程数据

1949-2021年全国31省铁路里程数据 1、时间&#xff1a;1949-2021年 2、指标&#xff1a;时间、省份、铁路里程 3、范围&#xff1a;包括31省 4、数据缺失情况说明&#xff1a;西藏2005年之前存在缺失&#xff0c;其余30省份1978-2020年无缺失 5、来源&#xff1a;各省统计…

C++实现DFS、BFS、Kruskal算法和Prim算法、拓扑排序、Dijkstra算法

背景&#xff1a; 实现要求&#xff1a; 根据图的抽象数据类型的定义&#xff0c;请采用邻接矩阵来存储图1&#xff0c;采用邻接表来存储图2&#xff0c;并完成如下操作&#xff1a;对图1无向图进行深度优先遍历和广度优先遍历。对图1无向图采用Kruskal算法和Prim算法得出最小…

uni-app 微信小程序之自定义navigationBar顶部导航栏

文章目录 1. 实现效果2. App.vue3. pages.json 配置自定义4. 顶部导航栏 使用 微信小程序自定义 navigationBar 顶部导航栏&#xff0c;兼容适配所有机型 1. 实现效果 2. App.vue 在App.vue 中&#xff0c;设置获取的 StatusBar&#xff0c;CustomBar 高度&#xff08;实现适配…

【云原生Prometheus篇】Prometheus PromQL语句详解 1.0

文章目录 一、前言1.1 Prometheus的时间序列1.1.1 指标名称1.1.2 标签1.1.3 使用的注意事项 1.2 样本数据格式1.3 Prometheus 的聚合函数 二 、PromQL 理论部分2.1 PromQL简介2.2 PromQL的数据类型2.3 时间序列选择器2.3.1 瞬时向量选择器 &#xff08;Instant Vector Selector…

python装饰器解析(关键点:高阶函数、嵌套函数)(参数化装饰器、类装饰器)

文章目录 Python装饰器解析什么是Python装饰器基础理解 如何创建装饰器&#xff08;关键点&#xff1a;高阶函数、嵌套函数&#xff09;创建基础装饰器 使用装饰器使用示例 装饰器的返回值参数化装饰器创建参数化装饰器语法示例使用示例 类装饰器创建类装饰器语法示例使用示例 …

使用postman请求x5接口

x5接口简介 1.接口样例 {"header"{"appid":"bpmnew_fanwei","sign":"C033162E86E4CADE80C7EB44D68A5AD2","sign_type":"md5","url":"https://oa.mioffice.cn/api/bpm/xm/app/show/tod…

MySQL索引优化实战二

分页查询优化 很多时候我们业务中实现分页功能时可能会用如下SQL来实现&#xff1a; select * from employees LIMIT 10000,10表示从表中中区从10001行开始的10行记录&#xff0c;看似只查了10条记录&#xff0c;但是这条SQL是先读取10010条记录&#xff0c;然后抛弃前10000条…

【个人笔记】-python-强化学习-类-在内存中的值

{int} 数值 {int} 200 {float} 数值 {float} 0.9 {narray:维度} 数值 {narray:(1,)} [2.] {bool} True {bool} False {类名} 对象1 {类名} 对象2

LLM 开发模式 RAG,MRKL,Re-Act,Plan-Execute 模式对比

本心、输入输出、结果 文章目录 LLM 开发模式 RAG&#xff0c;MRKL&#xff0c;Re-Act&#xff0c;Plan-Execute 模式对比前言RAG、MRKL、Re-Act和Plan-Execute模式的一些对比花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 LLM 开发模式 RAG&#xff0c;MRKL&…