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

动态代理简介
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…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...
