死磕JDK1.8动态代理原理及源码分析

2024-08-23 22:48

本文主要是介绍死磕JDK1.8动态代理原理及源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一节《设计模式之代理模式》中我们已经对设计模式中的代理模式做了一个简单的介绍,这一节将会对Java的动态的代理原理、源码进行深入的分析(注意:这里分析的是JDK1.8中的动态代理的源码)。篇幅有点长,花了好几天的时间才写完,感觉身体被掏空。。。。。。

一、概述

上节介绍过,Java的动态代理是在运行时动态产生的,其底层是通过反射实现的。Java的动态代理要求目标类必须实现接口,否则无法被代理。Java动态代理中最关键的有两个:

  • InvocationHandler接口:代理类不再实现目标类的接口,而是实现InvocationHandler接口,并且重写其 public Object invoke(Object proxy, Method method, Object[] args)  方法,参数proxy是产生的代理对象,method是要执行的目标类的接口中的方法,args是要执行的方法传入的参数。产生的代理类执行方法时,其实执行的就是invoke方法。
  • Proxy类:Proxy提供了静态方法用于生成代理类, public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),loader是目标类的类加载器,interfaces是目标类的全部接口,h是InvocationHandler接口的子类的实例。

上节测试代码如下,InvocationHandler是控制代理类的方法执行的,代理类的产生是Proxy.newProxyInstance方法产生的。

public class TestJavaDynamicProxy {@SuppressWarnings("resource")public static void main(String[] args) throws IOException {UserHandle target = new UserHandle();LogProxyHandle invocationHandler = new LogProxyHandle(target);Handle handle = (Handle) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),invocationHandler);handle.add();}
}

二、Proxy.newProxyInstance方法

  1. 可以看到newProxyInstance()方法中主要做了如下几件事:
  2. 对目标接口进行克隆
  3. 权限校验
  4. getProxyClass0(loader, intfs)方法获取代理类class对象,重点
  5. 获取代理类中参数为InvocationHandler.class的构造方法
  6. 执行构造方法返回实例对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);//对目标类接口进行克隆final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();//权限校验if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*///获取代理类clsss对象(重点)Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*///使用指定的invocation handler调用代理类的构造函数try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}//获取代理类中参数为InvocationHandler.class的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;// 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}//执行构造方法,返回代理类实例return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

下面我们重点看下getProxyClass0(loader, intfs)方法

三、Proxy.getProxyClass0方法

 private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {//判断接口数量									   if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactory//根据类加载器和接口数组,从缓存中获取代理类class对象return proxyClassCache.get(loader, interfaces);
}

里面最重要的是这个方法:proxyClassCache.get(loader, interfaces),根据类加载器和接口数组,从proxyClassCache缓存中获取代理类class对象。proxyClassCache是什么呢?proxyClassCache是Proxy类中定义的一个静态成员变量,类型是WeakCache,缓存就是从WeakCache中获取的。

 /*** a cache of proxy classes*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

四、WeakCache类

WeakCache从字面意思看是虚缓存,缓存(键、子键)->值的映射对。WeakCache<K, P, V>中定义了三个泛型K、P、V:其中K是key也就是键;P是参数,可以通过K和P计算出子键;V是value值。通过proxyClassCache.get(loader, interfaces)方法可知:当前的K是类加载器,P是目标类的接口类数组,V就是要产生的代理类class对象。键和值是弱引用,但子键是强引用的。

强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。  强引用其实也就是我们平时A a = new A()这个意思。

弱引用(WeakReference)
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果在创建WeakReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给WeakReference的构造方法,当弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。

在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个WeakReference所弱引用的对象已经被回收。于是我们可以把这些失去所弱引用的对象的WeakReference对象清除掉。常用的方式为:

WeakReference ref = null;
while ((ref = (CacheKey) q.poll()) != null) {
     // 清除ref
}

具体的关于Java的四种引用类型,可以参考《Java四种引用类型: 强引用、弱引用、软引用、虚引用》,

主要成员变量如下:

refQueue:弱引用队列,在这里其中存放的是弱引用CacheKey,CacheKey其实就是对WeakCache中的K键的封装,如果CacheKey被垃圾回收了,则其会进入引用队列,我们通过refQueue.poll()获取到CacheKey,从缓存中删除被垃圾回收掉的CacheKey

map:其类型ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>>,把泛型用这样表示ConcurrentMap<A, ConcurrentMap<B, Supplier<V>>>:A即CacheKey,本例中也就是类加载器;B即subKey子键,是由KeyFactory(subKeyFactory.apply(key, parameter))获得的。

reverseMap:暂时不管

subKeyFactory:Proxy的内部类,子键工厂KeyFactory,其apply(key, parameter)方法计算subKey

valueFactory:Proxy的内部类,value工厂ProxyClassFactory,其public Class<?> apply(ClassLoader loader, Class<?>[] interfaces)生产代理类class对象。

final class WeakCache<K, P, V> {//引用队列private final ReferenceQueue<K> refQueue= new ReferenceQueue<>();// the key type is Object for supporting null key//key类型为Object,支持null key,这里的null key是指new Object();private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map= new ConcurrentHashMap<>();private final ConcurrentMap<Supplier<V>, Boolean> reverseMap= new ConcurrentHashMap<>();private final BiFunction<K, P, ?> subKeyFactory;private final BiFunction<K, P, V> valueFactory;public WeakCache(BiFunction<K, P, ?> subKeyFactory,BiFunction<K, P, V> valueFactory) {this.subKeyFactory = Objects.requireNonNull(subKeyFactory);this.valueFactory = Objects.requireNonNull(valueFactory);}public V get(K key, P parameter) {//省略,后面进行分析}
}

五、WeakCache.get方法

public V get(K key, P parameter) {Objects.requireNonNull(parameter);//清理过期对象expungeStaleEntries();//缓存key对象CacheKeyObject cacheKey = CacheKey.valueOf(key, refQueue);// lazily install the 2nd level valuesMap for the particular cacheKey//通过key:CacheKey对象获取vlaue ConcurrentMap<Object, Supplier<Class<?>>>,泛型Class<?>就是代理类Class对象ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//如果valuesMap为空if (valuesMap == null) {//如果cacheKey不存在则放进去一个new的Map,否则返回旧值ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//如果旧值map不为空,赋值给valusMap					  if (oldValuesMap != null) {valuesMap = oldValuesMap;}}// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap//通过类加载和接口class数组计算子键subKey,因为我们的目标类只实现了一个接口,所以这里返回的是Key1<接口Class>Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));//根据subKey从valuesMap中获取Supplier<代理类Class>Supplier<V> supplier = valuesMap.get(subKey);Factory factory = null;while (true) {//如果从valuesMap中获取的supplier不为空if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instance//4,通过get()方法返回代理类class对象V value = supplier.get();if (value != null) {//5,返回代理类class对象return value;}}// else no supplier in cache// or a supplier that returned null (could be a cleared CacheValue// or a Factory that wasn't successful in installing the CacheValue)// lazily construct a Factory//如果factory为空,则new一个Factory对象if (factory == null) {//1,Factory(实现了Supplier<V>接口):实现值的延迟同步构造并将其安装到缓存中的工厂供应商。      factory = new Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {//2,如果subKey不存在则将上面构造的factory对象放入valuesMap,否则返回旧值suppliersupplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {// successfully installed Factory//3,赋值supplier = factory;}// else retry with winning supplier//如果旧值supplier不为空,则继续重试} else {//从valuesMap中获取的supplier不为空,但是其get返回的代理类Class为空//用factory替换supplierif (valuesMap.replace(subKey, supplier, factory)) {// successfully replaced// cleared CacheEntry / unsuccessful Factory// with our Factorysupplier = factory;} else {// retry with current supplier//替换失败,继续重试supplier = valuesMap.get(subKey);}}}
}

进来会先执行expungeStaleEntries()方法清理过期对象,这里就是利用弱引用被垃圾回收后进入ReferenceQueue队列的特性,poll()出已经被回收的CacheKey,把它从map中清除,expungeStaleEntries内部实现如下

   private void expungeStaleEntries() {CacheKey<K> cacheKey;while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {//清除被垃圾回收的CacheKeycacheKey.expungeFrom(map, reverseMap);}}

while循环里面我在代码中标注了顺序,第一次进来的执行顺序为1,2,3,4,5

步骤1,Factory类是WeakCache的内部类,实现了Supplier<V>接口,其get方法返回代理类对象

步骤2,判断子键是否存在,如果不存在则放入1中构造的factory,并返回null,如果存在则只返回原先存放的Supplier的实现类对象

步骤3,步骤2中返回的是null,将factory赋值给supplier

步骤4,步骤3执行完成后进行下一次while循环,进入步骤4,实际上执行的就是步骤2构造的factory对象的get方法

步骤5,判断4中返回的value如果不为空则直接返回

六、Factory.get

Factory类是WeakCache的内部类,可以操作其成员变量,我们重点关注如下这行代码:其中的valueFactory就是WeakCache中在Proxy中传入的ProxyClassFactory对象,ProxyClassFactory这个类的apply方法是生成代理类的关键所在。

value = Objects.requireNonNull(valueFactory.apply(key, parameter));
public synchronized V get() { // serialize access// re-checkSupplier<V> supplier = valuesMap.get(subKey);//重复校验是否被修改过if (supplier != this) {// something changed while we were waiting:// might be that we were replaced by a CacheValue// or were removed because of failure ->// return null to signal WeakCache.get() to retry// the loopreturn null;}// else still us (supplier == this)// create new valueV value = null;try {//valueFactory(ProxyClassFactory)的apply方法,创建代理类value = Objects.requireNonNull(valueFactory.apply(key, parameter));} finally {if (value == null) { // remove us on failurevaluesMap.remove(subKey, this);}}// the only path to reach here is with non-null value//判断生成的value是否不为空assert value != null;// wrap value with CacheValue (WeakReference)//用CacheValue(实现了Supplier接口)包装valueCacheValue<V> cacheValue = new CacheValue<>(value);// try replacing us with CacheValue (this should always succeed)//用cacheVaule替换supplierif (valuesMap.replace(subKey, this, cacheValue)) {// put also in reverseMapreverseMap.put(cacheValue, Boolean.TRUE);} else {throw new AssertionError("Should not reach here");}// successfully replaced us with new CacheValue -> return the value// wrapped by itreturn value;
}

七、ProxyClassFactory.apply

该类是Proxy的内部类,其中定义了两个成员变量:所有代理类的前缀$Proxy的字符串,还有一个用于生成唯一代理类名称的下一个数字编号的AtomicLong类型的变量。

在apply方法中判断了接口中如果没有非公共接口,则生成的代理类包名用 com.sun.proxy,

代理类全名,如果没有非公共接口,则proxyName=com.sun.proxy.$Proxy0(1,2,3,4......)

最关键是这行byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags),用于生成代理类class文件。

private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{// prefix for all proxy class names//所有代理类的前缀private static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class names//用于生成唯一代理类名称的下一个数字编号private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {/** Verify that the class loader resolves the name of this* interface to the same Class object.*/Class<?> interfaceClass = null;try {interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}//验证类加载器是否将此接口的名称解析为同一类对象if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}/** Verify that the Class object actually represents an* interface.*///判断interfaceClass是否是接口if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** Verify that this interface is not a duplicate.*///验证此接口不是重复的if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}//代理类的包名String proxyPkg = null;     // package to define proxy class inint accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*///记录非公共代理接口的包,以便在同一包中定义代理类。验证所有非公共代理接口都在同一个包中for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {//目标类的非公共接口不在一个包里面throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy package//如果没有非公共接口,则包名用 com.sun.proxyproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*///代理类名自增序号long num = nextUniqueNumber.getAndIncrement();//代理类名,如果没有非公共接口,则proxyName=com.sun.proxy.$Proxy0(1,2,3,4......)String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Generate the specified proxy class.*///生成代理类class文件byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {//返回代理类对象return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}}
}

八、ProxyGenerator.generateProxyClass方法

ProxyGenerator类的关键代码如下, 如果想查看源码的话可以通过Idea工具打开看下,eclipse中是无法打开的,还是Idea这个工具厉害啊,哈哈!这个源码就不继续深究了,感兴趣的话可以自己看看哈。

private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); 这行代码会读取sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true,则会写到磁盘上

public class ProxyGenerator {//省略了很多成员变量private static final String superclassName = "java/lang/reflect/Proxy";private static final String handlerFieldName = "h";private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));public static byte[] generateProxyClass(String var0, Class<?>[] var1) {return generateProxyClass(var0, var1, 49);}public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);final byte[] var4 = var3.generateClassFile();//如果为true则将文件写入磁盘if (saveGeneratedFiles) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {try {int var1 = var0.lastIndexOf(46);Path var2;if (var1 > 0) {Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));Files.createDirectories(var3);var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");} else {var2 = Paths.get(var0 + ".class");}//写文件Files.write(var2, var4, new OpenOption[0]);return null;} catch (IOException var4x) {throw new InternalError("I/O exception saving generated file: " + var4x);}}});}return var4;}
}

 八,获取生成的代理类$Proxy0.class文件

第一种方式:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true),ProxyGenerator.generateProxyClass方法内部会读取这个变量,如果为true,则会把代理类class文件写到磁盘上,我试了一下没有成功,不知道什么原因。。。。。。

第二种方式:byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Handle.class})生产代理类class字节数组,然后通过文件流写入磁盘,这个是可以正常写入的。

public class TestJavaDynamicProxy {@SuppressWarnings("resource")public static void main(String[] args) throws IOException {UserHandle target = new UserHandle();LogProxyHandle invocationHandler = new LogProxyHandle(target);
//		writeProxyClassToHardDisk1();Handle handle = (Handle) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),invocationHandler);handle.add();writeProxyClassToHardDisk2();}/*** 第一种方法,添加这一句是生成代理类的class文件,前提是你需要在工程根目录下创建com/sun/proxy目录*/public static void writeProxyClassToHardDisk1() {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);}/*** 第二种方法,获取代理类的字节码,然后通过写文件的方式写到磁盘上*/public static void writeProxyClassToHardDisk2() {byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Handle.class});FileOutputStream out = null;try {out = new FileOutputStream(TestJavaDynamicProxy.class.getResource("").getPath()+"$Proxy0.class");out.write(classFile);out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {out.close();} catch (IOException e) {e.printStackTrace();}}}
}

我们可以在当前类的.class文件所在目录下看的代理类$Proxy0.class

 然后通过JD-JUI对$Proxy0.class进行反编译,可以看到如下源码(省略了equals,toString,hashCode三个方法):

import com.wkp.designpattern.proxy.dynamic.Handle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;//代理类继承了Proxy,并实现了我们目标类同样实现的Handle接口
public final class $Proxy0 extends Proxy implements Handle
{private static Method m1;private static Method m2;private static Method m3;private static Method m0;//构造方法传入的就是我们自己写的InvocationHandler接口的实现类LogProxyHandle的实例,public $Proxy0(InvocationHandler paramInvocationHandler){//调用父类Proxy中的构造方法super(paramInvocationHandler);}//省略了equals,toString,hashCode三个方法public final void add(){try{//h是当前代理类的父类Proxy中定义的成员变量,其实就是LogProxyHandle的实例this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}static{try{//通过反射获取方法m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.wkp.designpattern.proxy.dynamic.Handle").getMethod("add", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}

我们可以看下第一步分析时如下的代码,cl.getConstructor(constructorParams)获取的就是$Proxy0类的构造方法,而$Proxy0类的构造方法又会调用Proxy的构造方法,将InvocationHandler的实现类LogProxyHandle的实例引用赋值给Proxy中的成员变量h,所以代理类中的this.h.invoke(this, m3, null)方法的h就是我们实现的LogProxyHandle的实例对象。

protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;
}
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams ={ InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{//......Class<?> cl = getProxyClass0(loader, intfs);//......final Constructor<?> cons = cl.getConstructor(constructorParams);//......return cons.newInstance(new Object[]{h});
}

 

这篇关于死磕JDK1.8动态代理原理及源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1100712

相关文章

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

PostgreSQL 序列(Sequence) 与 Oracle 序列对比差异分析

《PostgreSQL序列(Sequence)与Oracle序列对比差异分析》PostgreSQL和Oracle都提供了序列(Sequence)功能,但在实现细节和使用方式上存在一些重要差异,... 目录PostgreSQL 序列(Sequence) 与 oracle 序列对比一 基本语法对比1.1 创建序

Python Selenium动态渲染页面和抓取的使用指南

《PythonSelenium动态渲染页面和抓取的使用指南》在Web数据采集领域,动态渲染页面已成为现代网站的主流形式,本文将从技术原理,环境配置,核心功能系统讲解Selenium在Python动态... 目录一、Selenium技术架构解析二、环境搭建与基础配置1. 组件安装2. 驱动配置3. 基础操作模

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Spring框架中@Lazy延迟加载原理和使用详解

《Spring框架中@Lazy延迟加载原理和使用详解》:本文主要介绍Spring框架中@Lazy延迟加载原理和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、@Lazy延迟加载原理1.延迟加载原理1.1 @Lazy三种配置方法1.2 @Component

spring IOC的理解之原理和实现过程

《springIOC的理解之原理和实现过程》:本文主要介绍springIOC的理解之原理和实现过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、IoC 核心概念二、核心原理1. 容器架构2. 核心组件3. 工作流程三、关键实现机制1. Bean生命周期2.

Redis实现分布式锁全解析之从原理到实践过程

《Redis实现分布式锁全解析之从原理到实践过程》:本文主要介绍Redis实现分布式锁全解析之从原理到实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景介绍二、解决方案(一)使用 SETNX 命令(二)设置锁的过期时间(三)解决锁的误删问题(四)Re

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

慢sql提前分析预警和动态sql替换-Mybatis-SQL

《慢sql提前分析预警和动态sql替换-Mybatis-SQL》为防止慢SQL问题而开发的MyBatis组件,该组件能够在开发、测试阶段自动分析SQL语句,并在出现慢SQL问题时通过Ducc配置实现动... 目录背景解决思路开源方案调研设计方案详细设计使用方法1、引入依赖jar包2、配置组件XML3、核心配