手写实现一个动态代理框架
手写实现一个动态代理框架
- 什么是代理模式
- 什么是动态代理
- 动态代理中的编译、类加载与对象实例化
- 手写实现一个动态代理框架
- 实现细节
- 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;}@Overridepublic void handle() {pre(); // 前置增强逻辑target.handle();post(); // 后置增强逻辑}
}public class Target implements Handler {@Overridepublic void handle() {...}
}

比如我们要在目标方法的前后添加日志打印,那么我们可以通过代理模式实现。
public class Proxy implements Handler {private Handler target;@Overridepublic 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;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}@Overridepublic void method3() {log.info(...);target.method3();log info(...);}}public class ProxyB implements HandlerB {private HandlerB target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic 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();// packagePackage aPackage = interfaceClass.getPackage();String packageName = aPackage.getName();codeBuilder.append("package ").append(packageName).append(";").append("\n");codeBuilder.append("\n");// importMethod[] 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;");// classcodeBuilder.append("\n\npublic class ").append(interfaceClass.getSimpleName()).append("$$").append(" implements").append(" ").append(interfaceClass.getSimpleName()).append(" {").append("\n\n");// fieldcodeBuilder.append("\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n");// ConstructorcodeBuilder.append("\tpublic ") .append(interfaceClass.getSimpleName()).append("$$").append("(").append("DynamicProxyHandler dynamicProxyHandler) {\n").append("\t\tthis.dynamicProxyHandler = dynamicProxyHandler;").append("\n").append("\t}\n\n");// methodfor (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 parameterClass<?>[] 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 bodycodeBuilder.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;}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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 {@Testpublic 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文件写到磁盘)、编译、调用类加载器进行类加载的这几步。

全文完。

相关文章:
手写实现一个动态代理框架
手写实现一个动态代理框架 什么是代理模式什么是动态代理动态代理中的编译、类加载与对象实例化手写实现一个动态代理框架实现细节DynamicProxyHandlerProxy生成代码写入代码到磁盘文件调用编译器进行编译调用类加载器进行类加载反射实例化删除前面生成的java文件和class文件 C…...
Leetcode226. 翻转二叉树
文章目录 题目介绍题目分析解题思路边界条件:节点为空时返回空子问题:交换左右子节点 整体代码 题目介绍 题目分析 题目要求我们将树中每个节点的左右子节点全部交换,最后返回交换后的树的根节点。 解题思路 这题是比较常见的递归,直接找边…...
使用WalletConnect Web3Modal v3 链接钱包基础教程
我使用的是vueethers 官方文档:WalletConnect 1.安装 yarn add web3modal/ethers ethers 或者 npm install web3modal/ethers ethers2.引用 新建一个js文件,在main.js中引入,初始化配置sdk import {createWeb3Modal,defaultConfig, } from…...
黄金比例设计软件Goldie App mac中文版介绍
Goldie App mac是一款测量可视化黄金比例的工具。专门为设计师打造,可以帮助他们在Mac上测量和可视化黄金比例,从而轻松创建出完美、平衡的设计。 Goldie App mac体积小巧,可以驻留在系统的菜单栏之上,随时提供给用户调用。 拥有独…...
el-select实现可复制一段“关键词“(多个)实现搜索 以及 回车选中搜索项
el-select实现可复制一段"关键词"(多个)实现搜索 以及 回车选中搜索项 <el-selectref"productRef"filterableclearablev-model"fItem.productName"multiple:reserve-keyword"true"remote:filter-method&quo…...
C++解析xml示例
C解析xml示例 1. Xml文档介绍1.1 特点及作用1.2 Xml优点1.2.1 良好的可拓展性1.2.2 内容与形式分离 1.3 Xml组成1.3.1 Xml声明1.3.2 根元素1.3.3 元素1.3.4 属性1.3.5 实体1.3.6 注释 2 C解析Xml2.1 tinyXml2类库2.2 关键接口2.2.1 LoadFile2.2.2 RootElement2.2.3 FirstChildE…...
记录 | linux find+rm查找并直接删除
findrm查找并直接删除: find ./ -name "xx_name" |xargs rm -rf...
24.有哪些生命周期回调方法?有哪几种实现方式?
有哪些生命周期回调方法?有哪几种实现方式? 有两个重要的bean 生命周期方法, 第一个是init , 它是在容器加载bean的时候被调用。第二个方法是 destroy 它是在容器卸载类的时候被调用。bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始…...
C++详解
//7.用new建立一个动态一维数组,并初始化int[10]{1,2,3,4,5,6,7,8,9,10},用指针输出,最后销毁数组所占空间。 #include<iostream> #include<string> using namespace std; int main() { int *p; pnew int[10]; for(i…...
mybatis数据输入-实体类型的参数
1、建库建表 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO t_emp(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO…...
Java-接口
目录 定义 格式 使用 接口中成员的特点 成员变量 构造方法 成员方法 JDK8新特性:可以定义有方法体的方法 默认方法 作用 定义格式 注意事项 静态方法 定义格式 注意事项 JDK9新特性:可以定义私有方法 私有方法的定义格式 接口和接口之…...
mysql常用命令行代码
连接到 MySQL 服务器: mysql -u your_username -p替换 your_username 为你的 MySQL 用户名。系统会提示你输入密码。 退出 MySQL 命令行: EXIT;或者按 Ctrl D。 显示所有数据库: SHOW DATABASES;选择数据库: USE your_database…...
Python压缩、解压文件
#!/usr/bin/python3 # -*- coding:utf-8 -*- """ author: JHC file: util_compress.py time: 2023/5/28 14:58 desc: rarfile 使用需要安装 rarfile 和 unrar 并且将 unrar.exe 复制到venv/Scrpits目录下 (从WinRar安装目录下白嫖的) 下载…...
面试就是这么简单,offer拿到手软(一)—— 常见非技术问题回答思路
面试系列: 面试就是这么简单,offer拿到手软(一)—— 常见非技术问题回答思路 面试就是这么简单,offer拿到手软(二)—— 常见65道非技术面试问题 文章目录 一、前言二、常见面试问题回答思路问…...
134. 加油站(贪心算法)
根据题解 这道题使用贪心算法,找到当前可解决问题的状态即可 「贪心算法」的问题需要满足的条件: 最优子结构:规模较大的问题的解由规模较小的子问题的解组成,规模较大的问题的解只由其中一个规模较小的子问题的解决定ÿ…...
Springboot3+vue3从0到1开发实战项目(二)
前面完成了注册功能这次就来写登录功能, 还是按照这个方式来 明确需求: 登录接口 前置工作 : 想象一下登录界面(随便在百度上找一张) 看前端的能力咋样了, 现在我们不管后端看要什么参数就好 阅读接口文档…...
Spring中Bean的生命周期
1.生命周期 Spring应用中容器管理了我们每一个bean的生命周期,为了保证系统的可扩展性,同时为用户提供自定义的能力,Spring提供了大量的扩展点。完整的Spring生命周期如下图所示,绿色背景的节点是ApplictionContext生命周期特有的…...
IndexOutOfBoundsException: Index: 2048, Size: 2048] Controller接收对象集合长度超过2048错误
完整异常信息: org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [spring] in context with path [/jsgc] threw exception [Request processing failed; nested exception is org.springframework.beans.InvalidPropertyExce…...
2023年中国消费金融行业研究报告
第一章 行业概况 1.1 定义 中国消费金融行业,作为国家金融体系的重要组成部分,旨在为消费者提供多样化的金融产品和服务,以满足其消费需求。这一行业包括银行、消费金融公司、小额贷款公司等多种金融机构,涵盖了包括消费贷款在内…...
深度学习:什么是知识蒸馏(Knowledge Distillation)
1 概况 1.1 定义 知识蒸馏(Knowledge Distillation)是一种深度学习技术,旨在将一个复杂模型(通常称为“教师模型”)的知识转移到一个更简单、更小的模型(称为“学生模型”)中。这一技术由Hint…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...
