时隔多年,这次我终于把动态代理的源码翻了个地儿朝天
本文内容整理自 博学谷狂野架构师

动态代理简介
Proxy模式是常用的设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
用户可以更加结构图,自己编码完成Proxy模式。这种实现称为静态代理。
Java提供了java.lang.reflect.Proxy类与InvocationHandler接口,配合反射,可以实现动态代理。静态代理的代理类与代理操作,都是事先编码,运行过程种无法修改代理结构。动态代理的代理与代理操作,都是在运行过程中,动态生成,可以在运行过程中,修改代理结构,符合面向对象的开闭原则。
最最最主要的原因就是,在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截,等等...
动态代理用于将在不需要修改原代码的情况下进行代码的增加,spring中的AOP,事务,都是使用动态代理来实现的,我们天天都在使用动态代理只是自己不知道而已。
动态代理三大要素
需要定义一个接口,java动态代理类只能代理接口(不支持抽象类),如果没有接口就要使用cjlib
需要一个实现类继承这个接口
编写一个增强类实现 InvocationHandler接口,代理类都需要实现InvocationHandler接口的invoke方法
一个例子
先定义一个接口
定义一个海外代购的接口
/*** 海外代购*/
public interface Buying {public String buy();
}
编写一个实现类
实现类实现接口
public class BuyingImpl implements Buying {@Overridepublic String buy() {System.out.println("开始逻辑处理");return "买了个锤子";}
}
编写一个增将类
编写一个增强类,主要要包裹一个需要需要增强的对象也就是我们的BuyingImpl,并实现InvocationHandler接口,在invoke方法中写增强实现
/*** 海外代购增强类* 注意实现 InvocationHandler* 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。* 该invoke方法就是调用被代理接口的所有方法时需要调用的 。*/
public class BuingHandler implements InvocationHandler {/*** 包裹一个需要增强的目标对象*/private Object targetObject;public BuingHandler(Object targetObject){this.targetObject = targetObject;}/*** 获取代理类** @return*/public Object getProxy() {/*** 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例* 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器* 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口* 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法* 根据传入的目标返回一个代理对象*/return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);}/*** 关联的这个实现类的方法被调用时将被执行* InvocationHandler接口的方法** @param proxy 表示代理对象* @param method 示原对象被调用的方法* @param args 表示方法的参数* @return 返回的是对象的一个接口* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("前置增强");//反射调用原始的需要增强的方法Object value = method.invoke(targetObject, args);System.out.println("后置增强");return value;}
}
这里面要注意 method 是我们需要增强的方法,args 是我们需要增强的参数数组
编写Main方法
public static void main(String[] args) {//创建BuingHandler 类BuingHandler buingHandler = new BuingHandler(new BuyingImpl());//获取代理对象Buying buying = (Buying) buingHandler.getProxy();//调用具体接口String value = buying.buy();System.out.println(value);}
输出
前置增强
开始逻辑处理
后置增强
买了个锤子
我们就这样实现了动态代理,我们没有修改原有代码的情况下做了增强
我们实现了 其那只以及后置增强
我们运行下看下接口对象
我们看到实际对象是$Proxy0,我们发现动态代理给我们换了一个对象,我们要研究下他是怎么实现的
源码实现
读源码首先找到入口,没有不得入口就像无头的苍蝇,苍蝇还不叮无缝的蛋呢
下面内容有点多,也有点绕,请跟着思路来一点点解析
1、首先找到入口
我们创建代理对象调用的是
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
所以我们先从Proxy.newProxyInstance开始入手
2、newProxyInstance方法
进入newProxyInstance方法内部
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {//增强实现不能为空,为空就抛出异常Objects.requireNonNull(h);//对接口数组进行clonefinal 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.* ********核心代码入口************ 查找或者是生成一个特定的代理类对象*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.* 使用指定的调用处理程序调用其构造函数*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 从代理类对象中查找参数为InvocationHandler的构造器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;}});}//通过反射,将h作为参数,实例化代理类,返回代理类实例。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);}}
上面代码的核心方法是
Class<?> cl = getProxyClass0(loader, intfs);
找到了核心方法继续深入
3、getProxyClass0方法入口
生成一个代理对象的方法
/*** 生成一个代理对象* Generate a proxy class. Must call the checkProxyAccess method* to perform permission checks before calling this.*/private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {//接口数量不能大于65535 否则报错 具体为什么 不太清楚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;//否则,它将通过proxyClassFactory创建代理类// otherwise, it will create the proxy class via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces);}
这一段代码是从缓存中获取代理对象,核心的代码还在里面 proxyClassCache.get(loader, interfaces);
因为 proxyClassCache 是一个WeakCache 的类,所以我们先来学习下WeakCache
4、WeakCache类
WeakCache 方法声明
在这个方法中,是直接从一个叫proxyClassCache缓存中读取的,来看一下这个缓存的声明:
/*** a cache of proxy classes* 缓存代理的class字节码文件,如果没有则使用ProxyClassFactory创建*/private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
里涉及到三个类:WeakCache,KeyFactory,ProxyClassFactory,其中后面两个类都是Proxy类的静态内部类,从类名可以大概猜测到,keyFactory是用来生产key的,ProxyClassFactory是用来生产代理类对象的,这个稍后会提到。
WeakCache类的大概结构
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并不是真的可以使用null最为key,而是一个new Objdec()对象实例。ConcurrentHashMap,不允许键或值null,而HashMap可以。ConcurrentHashMap是线程安全的,HashMap不是。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) {}...
上面的源代码中写明,代理对象的核心方法是get , 我们结合上下文 发现 key是loader 类加载器,parameter是接口数组interfaces
5、proxyClassCache.get
这个对象是从缓存中获取字节码对象,key是接口,value是对象的字节码文件,如果给定的接口存在则返回字节码文件,如果不存在则调用proxyClassFactory创建代理类进行创建
/*** return proxyClassCache.get(loader, interfaces);* <p>* 获取代理对象的核心方法** @param key 类加载器 loader* @param parameter 接口的数组 interfaces* @return*/public V get(K key, P parameter) {//接口数组不能为空,否则抛出异常Objects.requireNonNull(parameter);// 删除过时的条目expungeStaleEntries();// 生成缓存key对象实例,如果key = null,cacheKey = new Object();Object cacheKey = WeakCache.CacheKey.valueOf(key, refQueue);// lazily install the 2nd level valuesMap for the particular cacheKey// 从缓存map中读取指定cacheKey的缓存数据valuesMapConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);if (valuesMap == null) {//如果valuesMap为null,则新增// putIfAbsent方法解释:如果值存在则返回值,并且不对原来的值做任何更改,如果不存在则新增,并返回null//map.putIfAbsent 是map中新增的一个方法 存在则返回,不存在put然后在返回ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//赋值if (oldValuesMap != null) {valuesMap = oldValuesMap;}}// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap//获取subKey,这里用到了上面提到的Proxy的静态内部类 KeyFactory:subKeyFactory.apply(ket,parameter)Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));// 从valuesMap中获取supplierSupplier<V> supplier = valuesMap.get(subKey);WeakCache.Factory factory = null;while (true) {if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instance// 4、从工厂中获取代理类对象V value = supplier.get();if (value != null) {//5、返回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//1、实例化工厂if (factory == null) {factory = new WeakCache.Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {//2、将supplier保存到valuesMap中supplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {// successfully installed Factory// 3、赋值supplier = factory;}// else retry with winning supplier} else {//如果subKey和supplier都匹配则则将supplier替换为新生成的factoryif (valuesMap.replace(subKey, supplier, factory)) {// successfully replaced// cleared CacheEntry / unsuccessful Factory// with our Factory//替换成功赋值supplier = factory;} else {// retry with current supplier//使用当前的supplier进行重试supplier = valuesMap.get(subKey);}}}}
因为程序中Proxy.newProxyInstance是第一次执行,所以while循环开始的时候,supplier,valuesMap都是null。在这个前提下,我为代码的执行顺序做了一个编号,从1-5执行。
可以看到第5步,也就是源代码的第47行将结果返回,那么,代理类对象就是在第4步,也就是第43行生成的。而且也可以从第3步,也就是第65行发现supplier就是factory。
那么接下来,就分析一下Factory.get方法。
6、Factory.get方法
Factory类是WeakCache的内部类。这个类中除去构造方法外,就是get方法了,下面是这个代码的实现:
/*** Factory 实现类Supplier 接口*/private final class Factory implements Supplier<V> {//类加载器 loaderprivate final K key;接口的数组 interfacesprivate final P parameter;//这里的subkey 就是上面的 KeyFactory 可以会看 WeakCache 方法声明private final Object subKey;//提供者的MAP key是KeyFactory ,value 是 Factory 本身private final ConcurrentMap<Object, Supplier<V>> valuesMap;//构造方法Factory(K key, P parameter, Object subKey,ConcurrentMap<Object, Supplier<V>> valuesMap) {this.key = key;this.parameter = parameter;this.subKey = subKey;this.valuesMap = valuesMap;}@Overridepublic synchronized V get() { // serialize access// re-check//检查 如果 supplier不是自己 返回Supplier<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 value//定义一个新的对象V value = null;try {/*** valueFactory就是WeakCache的valueFactory属性,因为Factory是WeakCache的内部类,所以可以直接访问WeakCache的valueFactory属性* 我们可以回去看看第四第五 proxyClassCache.get 以及 WeakCache 的简单结构 注意valueFactory 发现就是 ProxyClassFactory* 就在这一步生成了 代理对象*/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//校验对象不为空assert value != null;// wrap value with CacheValue (WeakReference)WeakCache.CacheValue<V> cacheValue = new WeakCache.CacheValue<>(value);// put into reverseMap//缓存代理对象reverseMap.put(cacheValue, Boolean.TRUE);// try replacing us with CacheValue (this should always succeed)//并将valuesMap替换为最新生成的对象if (!valuesMap.replace(subKey, this, cacheValue)) {throw new AssertionError("Should not reach here");}// successfully replaced us with new CacheValue -> return the value// wrapped by it//返回对象return value;}}
我们核心注意的是
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
这里的valueFactory就是Proxy的静态内部类ProxyClassFactory,上面也提到过,那么就接着分析ProxyClassFactory的apply方法吧。
7、ProxyClassFactory.apply方法
/*** 一个利用给定的类加载器和接口类数组生成,定义并返回代理类对象的工厂方法* A factory function that generates, defines and returns the proxy class given* the ClassLoader and array of interfaces.*/private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>{// prefix for all proxy class names//所有代理类对象的前缀 这个就回答了为什么代理类都带有$Proxyprivate static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class names//用于生成唯一代理类名称的下一个数字private static final AtomicLong nextUniqueNumber = new AtomicLong();/*** 开始我们的核心方法apply* @param loader 类加载器* @param interfaces 接口数组* @return*/@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 {//加载接口类,获得接口类的类对象,第二个参数为false表示不进行实例化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.* 验证是否是接口 不是接口报错*/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 in//访问权限int 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();//如果接口是public就跳过 我们的接口基本上不会走这里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//如果没有public的接口 就是用 com.sun.proxy 的包前缀//类似于com.sun.proxy.$Proxy0proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.* 生成代理类的类名*///生成代理类的序号long num = nextUniqueNumber.getAndIncrement();//生成代理类的完全限定名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());}}}
在代码的第111行,生成了代理类的class文件,并且在115行返回了我们需要的代理类对象。那么怎么找到这个生成的代理类class文件呢?
到这里 我们就跟完了动态代理的核心流程,我们解释了为什么 代理类都带有$Proxy,以及后面的序号是怎么来的。
生成代码的核心代码是
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
ProxyGenerator是根据代理名称接口生成代理类的核心代码,我们就不跟进去了,以后有时间再进去,里面都是字节码操作的知识了,也是在sun.misc包下,一般是不开源的,如果需要可以去下载sun包的源码,1.8之后就不开源了。
查看生成的代理类
我们上面最终跟到了ProxyGenerator类,ProxyGenerator是生成字节码文件的核心代码,我们想看下生成的字节码怎么办呢,我们自己去生成并且输出出来。
看代码
//生成代理字节码数组文件 传入一个接口数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass("com.sun.proxy", new Class[]{Buying.class}, 1);
//将字节数组转换成class文件并输出到本地FileOutputStream fos = new FileOutputStream(new File("d:/com.sun.proxy.class"));fos.write(proxyClassFile);fos.flush();fos.close();
我们反编译以下 com.sun.proxy.class
//继承了Proxy类,实现了Buying接口
public class proxy extends Proxy implements Buying {private static Method m1;private static Method m2;private static Method m3;private static Method m0;//构造方法,直接调用了父类,也就是Proxy的构造方法,参数paramInvocationHandler就是我们的BuingHandler实例化对象handlerpublic proxy(InvocationHandler paramInvocationHandler) {super(paramInvocationHandler);}/*** 实现equals 方法* @param var1* @return*/public final boolean equals(Object var1) {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}/*** 实现toString方法* @return*/public final String toString() {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}//实现了Buying 接口的 buypublic final String buy() {try {/*** 这里的h就是我们的BuingHandler 实例* 调用 父类 Proxy 里面我们传入的 BuingHandler 对象*/return (String)super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}/*** 实现了hashCode方法* @return*/public final int hashCode() {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}//静态代码块,做初始化操作static {try {//通过反射,获取Object对象方法对象的equals 方法m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));//通过反射,获取Object对象方法对象的toString 方法m2 = Class.forName("java.lang.Object").getMethod("toString");//通过反射,获取Buying对象方法对象的buy 方法m3 = Class.forName("com.test.proxy.Buying").getMethod("buy");//通过反射,获取Object对象方法对象的hashCode 方法m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}
代理类实例化的代码是:cons.newInstance(new Object[]{h})。这里是通过反射调用代理类对象的构造方法,传入了参数h(我们的BuingHandler实例化对象handler)。
这个构造方法,就是上述反编译代码里的构造方法,而上述反编译代码里的构造方法调用了Proxy类的构造方法,来看一下Proxy类的构造方法:
protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}
这里将我们传入的handler直接赋值给了InvocationHandler h。上述反编译代码中的super.h 就是我们传入的handler。
所以proxy.buy();方法在执行的时候会去调用BuingHandler类的invoke方法。
好了到这里我们的源码解析已经完了。
本文由
传智教育博学谷狂野架构师教研团队发布。如果本文对您有帮助,欢迎
关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。转载请注明出处!
相关文章:
时隔多年,这次我终于把动态代理的源码翻了个地儿朝天
本文内容整理自 博学谷狂野架构师 动态代理简介 Proxy模式是常用的设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。 用户可以更加结构图࿰…...
数据分析-深度学习 Tensorflow Day6
我们需要解决的问题:1: 什么是bp 神经网络?2:理解bp神经网络需要哪些数学知识?3:梯度下降的原理4: 激活函数5:bp的推导。1.什么是bp网络?引用百度知道回复:“我们最常用的…...
leaflet 设置多个marker,导出为一个geojson文件(066)
第066个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中使用L.marker设置多个markers, 通过数据重组,导出为geojson文件。 这里面 ayer instanceof L.Marker 是一个很重要的判断条件,可以灵活地去运用。 直接复制下面的 vue+openlayers源代码,操作2分钟即可…...
企业与第三方供应商合作时,会存在哪些安全风险?
随着现代社会的发展,企业供应链、产业供应链已日渐成熟。其中,供应商与企业的关系也由最初的纯粹买卖关系发展成了合作伙伴关系。在整个供应链体系中,供应商与其受众承担着供应链中环环相扣的责任,可以说,企业安全的薄…...
技术源自洛克希德·马丁,光场XR眼镜FYR解析
专注于医疗场景的一家XR眼镜厂商FYR(全称:FYR Medical)近期亮相,并宣布完成了260万美元A轮融资,本轮融资由NuVasive领投,资金将用于开发世界上第一个XR光场“放大镜”类产品。据青亭网了解,NuVa…...
剑指 Offer 10- II. 青蛙跳台阶问题(LeetCode 70. 爬楼梯)(动态规划打表)
题目: 链接:剑指 Offer 10- II. 青蛙跳台阶问题;LeetCode 70. 爬楼梯 难度:简单 相关博文:剑指 Offer 10- I. 斐波那契数列(动态规划打表) 一只青蛙一次可以跳上1级台阶,也可以跳上…...
webpack(高级)--文件的压缩Terser(js/css/html) Tree Shaking
webpack Terser Terser是一个javascript的解释(Parser),Mangler(绞肉机) /Compressor(压缩机)的工具集 早期我们会使用uglify-js来压缩,丑化我们的javascript代码 但是目前已经不在维护 并且不支持ES6语法 Terser是从uglify-es fork 过来的 也就是说 Terser可以帮…...
做软文发布需要注意哪些细节?
软文发布是一种有效的网络营销和推广活动,它以媒体等形式把产品信息植入到软文报道或新闻中,进行心理暗示和引导销售,进行正面宣传以及促进销售的新型网络营销方式,它不但能够有效地推行产品宣传、也能有效地提高网络曝光率&#…...
【Python】一篇文章读懂yield基本用法
这一次,田辛老师想通俗易懂地解释一下Python中的yield功能。 本文要说明以下四个问题: yield是什么什么是迭代器和生成器yield的基本用法如何使用yield from 用真正简单的方法讲解yield并不容易。 我想,就算你不懂yield语句,也…...
Docker getting started
系列文章目录 Docker 概述 Docker getting started 文章目录系列文章目录前言一、容器及镜像的概念二、容器化一个应用三、更新应用四、分享应用五、持久化数据存储volume mount 和 bind mount比较Container volumesbind mounts六、跨多容器的应用七、Docker 其它八、Docker 图…...
【Uniapp使用遇到问题合集】
Uniapp使用遇到问题合集问题一跳转页面后无法进行滑动/滚动的操作描述解决方法问题一 跳转页面后无法进行滑动/滚动的操作 描述 如题,实际操作是我在uniapp自带的组件uni-popup弹出层中加入了一个点击事件,点击后可跳转到指定的页面 但实际运行中出现了跳转过后页面过长时无…...
宝塔面板破解最新教程
宝塔,让运维简单高效。面板支持Linux与Windows系统。一键配置:LAMP/LNMP、网站、数据库、FTP、SSL,通过Web端轻松管理服务器。今天考高分网就简单说一下BT宝塔面板专业版最新破解教程。 网地址:https://www.bt.cn/ 网上的破解版一般分为两种,一种是直接…...
基于zookeeper的Hadoop集群搭建详细步骤
目录 一、一些基本概念 二、集群配置图 三、Hadoop高可用集群配置步骤 1.在第一台虚拟机解压hadoop-3.1.3.tar.gz到/opt/soft/目录 2.修改文件名、属主和属组 3.配置windows四台虚拟机的ip映射 4.修改hadoop配置文件 (1)hadoop-env.sh (2)workers (3)crore-site.xml …...
职称有哪些意义?如何提升职称?
每年我们会看到很多人都会努力地提升自己的职称,那么为什么大家都想要晋升职称?在这里余老师说说他的作用,您可以参考一下。 一、个人金钱方面的提升 工资。职称直接关联的就是涨工资了。正常情况下,职称和工资是一一对应的了,…...
mulesoft MCIA 破釜沉舟备考 2023.02.15.09
mulesoft MCIA 破釜沉舟备考 2023.02.15.09 1. According to MuleSoft, which deployment characteristic applies to a microservices application architecture?2. Refer to the exhibit.3. Mule application A receives a request Anypoint MQ message REQU with a payload…...
【项目实战】@ConditionalOnProperty注解让我少写了一些if判断
一、需求说明 本机启动含有XXL-job的工程,发现每次都会进行XXL-job的init的动作。这会导致本机每次启动都会把自己注册到XXL-job的服务端。但是我明明本地调试的功能不想要是编写定时任务,于是想了下,是否可以设计一个开关,让本机…...
SQL中的游标、异常处理、存储函数及总结
目录 一.游标 格式 操作 演示 二.异常处理—handler句柄 格式 演示 三.存储函数 格式 参数说明 演示 四.存储过程总结 一.游标 游标(cursor)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、…...
Splashtop:支持M1/M2芯片 Mac 电脑的远程控制软件
M1和M1芯片的Mac电脑现在越来越多了。M1和M2的强大性能,让使用者们办公、娱乐如虎添翼。 M1 芯片于2020年11月11日推出,是Apple 首款专为Mac打造的芯片,拥有格外出色的性能、众多的功能,以及令人惊叹的能效表现。M1 也是Apple 首款…...
实验十三、阻容耦合共射放大电路的频率响应
一、题目 利用 Multism 从以下几个方面研究图1所示的阻容耦合共射放大电路的频率响应。图1阻容耦合共射放大电路图1\,\,阻容耦合共射放大电路图1阻容耦合共射放大电路(1)设 C1C210μFC_1C_210\,\textrm{μF}C1C210μF,分别测试它们所确定…...
【每天进步一点点】函数表达式和函数声明
函数声明 function 函数名(){} 函数声明会被率先读取。 函数声明后不会立即执行,会在我们需要的时候调用到。 由于函数声明不是一个可执行语句,所以不以分号结束。 函数表达式 表达式赋值给了一个变量 const 变量名 functi…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
