动态代理JDK与cglib的区别之:如何处理被代理方法中的This调用

关于JDKcglib动态代理的使用不是本文关注的重点,如有不清楚的同学可以查询相关资料进行了解。本文主要是要讲一下在面对方法存在嵌套调用时JDKcglib动态代理的区别以及原因。

先看下测试代码,注意下TestProxyJDKImpltest1方法调用了test2方法即可。

        public class Test {static interface TestProxyJDK{void test1();void test2();}static class TestProxyJDKImp implements TestProxyJDK{@Overridepublic void test1() {System.out.println("执行test1");test2();}@Overridepublic void test2() {System.out.println("执行test2");}}public static void main(String[] args) throws NoSuchMethodException {//输出代理类System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//创建JDK动态代理对象TestProxyJDKImp testProxyJDKImp = new TestProxyJDKImp();TestProxyJDK testProxyJDK = (TestProxyJDK) Proxy.newProxyInstance(Test.class.getClassLoader(), TestProxyJDKImp.class.getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();System.out.println(name + "方法被拦截前执行方法");method.invoke(testProxyJDKImp, args);System.out.println(name + "方法被拦截后执行方法");return null;}});//调用test1方法testProxyJDK.test1();//输出代理类System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\cglib");//创建cglib代理对象Enhancer enhancer = new Enhancer();enhancer.setSuperclass(TestProxyJDKImp.class);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("CGLIB拦截前");proxy.invokeSuper(obj,args);System.out.println("CGLIB拦截后");return null;}});TestProxyJDK testProxyCglib = (TestProxyJDK) enhancer.create();//调用test1f方法testProxyCglib.test1();}}

为了更好说明情况,我们先把生成的代理类以文件的形式保存下来。对于JDK动态代理,只需要在代码里添加如下一行代码即可。

//设置系统属性  1.8还是这种写法
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 

生成类如下,主要看下test1test2方法的代码即可:

final class $Proxy0 extends Proxy implements TestProxyJDK {private static Method m1;private static Method m2;private static Method m3;private static Method m4;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {// 这里的h就是我们创建的InvocationHandler对象return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void test1() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void test2() throws  {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {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 {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.xxx.xxx.Test$TestProxyJDK").getMethod("test1");m4 = Class.forName("com.xxx.xxx.Test$TestProxyJDK").getMethod("test2");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

如果是cglib那么可以使用下面的方式输出代理类

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "your path");

cglib生成的相关类不止一个,这里就贴一下生成的代理类,主要看下test1test2方法的代码即可:

public class Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c extends TestProxyJDKImp implements Factory {private boolean CGLIB$BOUND;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static final Method CGLIB$test2$0$Method;private static final MethodProxy CGLIB$test2$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$test1$1$Method;private static final MethodProxy CGLIB$test1$1$Proxy;private static final Method CGLIB$finalize$2$Method;private static final MethodProxy CGLIB$finalize$2$Proxy;private static final Method CGLIB$equals$3$Method;private static final MethodProxy CGLIB$equals$3$Proxy;private static final Method CGLIB$toString$4$Method;private static final MethodProxy CGLIB$toString$4$Proxy;private static final Method CGLIB$hashCode$5$Method;private static final MethodProxy CGLIB$hashCode$5$Proxy;private static final Method CGLIB$clone$6$Method;private static final MethodProxy CGLIB$clone$6$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.xxx.xxx.Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$finalize$2$Method = var10000[0];CGLIB$finalize$2$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$2");CGLIB$equals$3$Method = var10000[1];CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");CGLIB$toString$4$Method = var10000[2];CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");CGLIB$hashCode$5$Method = var10000[3];CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5");CGLIB$clone$6$Method = var10000[4];CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");var10000 = ReflectUtils.findMethods(new String[]{"test2", "()V", "test1", "()V"}, (var1 = Class.forName("com.xxx.xxx.Test$TestProxyJDKImp")).getDeclaredMethods());CGLIB$test2$0$Method = var10000[0];CGLIB$test2$0$Proxy = MethodProxy.create(var1, var0, "()V", "test2", "CGLIB$test2$0");CGLIB$test1$1$Method = var10000[1];CGLIB$test1$1$Proxy = MethodProxy.create(var1, var0, "()V", "test1", "CGLIB$test1$1");}final void CGLIB$test2$0() {super.test2();}public final void test2() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$test2$0$Method, CGLIB$emptyArgs, CGLIB$test2$0$Proxy);} else {super.test2();}}final void CGLIB$test1$1() {super.test1();}public final void test1() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$test1$1$Method, CGLIB$emptyArgs, CGLIB$test1$1$Proxy);} else {super.test1();}}final void CGLIB$finalize$2() throws Throwable {super.finalize();}protected final void finalize() throws Throwable {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$finalize$2$Method, CGLIB$emptyArgs, CGLIB$finalize$2$Proxy);} else {super.finalize();}}final boolean CGLIB$equals$3(Object var1) {return super.equals(var1);}public final boolean equals(Object var1) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {Object var2 = var10000.intercept(this, CGLIB$equals$3$Method, new Object[]{var1}, CGLIB$equals$3$Proxy);return var2 == null ? false : (Boolean)var2;} else {return super.equals(var1);}}final String CGLIB$toString$4() {return super.toString();}public final String toString() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$4$Method, CGLIB$emptyArgs, CGLIB$toString$4$Proxy) : super.toString();}final int CGLIB$hashCode$5() {return super.hashCode();}public final int hashCode() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);return var1 == null ? 0 : ((Number)var1).intValue();} else {return super.hashCode();}}final Object CGLIB$clone$6() throws CloneNotSupportedException {return super.clone();}protected final Object clone() throws CloneNotSupportedException {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? var10000.intercept(this, CGLIB$clone$6$Method, CGLIB$emptyArgs, CGLIB$clone$6$Proxy) : super.clone();}public static MethodProxy CGLIB$findMethodProxy(Signature var0) {String var10000 = var0.toString();switch(var10000.hashCode()) {case -1574182249:if (var10000.equals("finalize()V")) {return CGLIB$finalize$2$Proxy;}break;case -1147892426:if (var10000.equals("test1()V")) {return CGLIB$test1$1$Proxy;}break;case -1147862635:if (var10000.equals("test2()V")) {return CGLIB$test2$0$Proxy;}break;case -508378822:if (var10000.equals("clone()Ljava/lang/Object;")) {return CGLIB$clone$6$Proxy;}break;case 1826985398:if (var10000.equals("equals(Ljava/lang/Object;)Z")) {return CGLIB$equals$3$Proxy;}break;case 1913648695:if (var10000.equals("toString()Ljava/lang/String;")) {return CGLIB$toString$4$Proxy;}break;case 1984935277:if (var10000.equals("hashCode()I")) {return CGLIB$hashCode$5$Proxy;}}return null;}public Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c() {CGLIB$BIND_CALLBACKS(this);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {CGLIB$THREAD_CALLBACKS.set(var0);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {CGLIB$STATIC_CALLBACKS = var0;}private static final void CGLIB$BIND_CALLBACKS(Object var0) {Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c var1 = (Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c)var0;if (!var1.CGLIB$BOUND) {var1.CGLIB$BOUND = true;Object var10000 = CGLIB$THREAD_CALLBACKS.get();if (var10000 == null) {var10000 = CGLIB$STATIC_CALLBACKS;if (var10000 == null) {return;}}var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];}}public Object newInstance(Callback[] var1) {CGLIB$SET_THREAD_CALLBACKS(var1);Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c var10000 = new Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Callback var1) {CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c var10000 = new Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {CGLIB$SET_THREAD_CALLBACKS(var3);Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c var10000 = new Test$TestProxyJDKImp$$EnhancerByCGLIB$$62be832c;switch(var1.length) {case 0:var10000.<init>();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;default:throw new IllegalArgumentException("Constructor not found");}}public Callback getCallback(int var1) {CGLIB$BIND_CALLBACKS(this);MethodInterceptor var10000;switch(var1) {case 0:var10000 = this.CGLIB$CALLBACK_0;break;default:var10000 = null;}return var10000;}public void setCallback(int var1, Callback var2) {switch(var1) {case 0:this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;default:}}public Callback[] getCallbacks() {CGLIB$BIND_CALLBACKS(this);return new Callback[]{this.CGLIB$CALLBACK_0};}public void setCallbacks(Callback[] var1) {this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];}static {CGLIB$STATICHOOK1();}
}

在方法1中调用方法2使用JDK动态代理和使用cglib动态代理有什么区别?

执行上面测试代码的main方法,打印结果如下:

test1方法被拦截前执行方法
执行test1
执行test2
test1方法被拦截后执行方法CGLIB拦截前
执行test1
CGLIB拦截前
执行test2
CGLIB拦截后
CGLIB拦截后

可以看到,使用JDK代理的时候,在test1方法里调用test2时,test2方法并未被拦截,调用的还是被代理类的test2方法。使用cglib代理的时候,在test1方法里调用test2时,test2也被拦截,调用的是生成的代理类的test2方法。

这是为什么呢?

回到我们生成的代理类,使用JDK代理的时候,调用test1方法,最终执行的是InvocationHandlerinvoke方法.

super.h.invoke(this, m3, (Object[])null);

invoke方法里调用被代理的test1方法

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();System.out.println(name + "方法被拦截前执行方法");method.invoke(testProxyJDKImp, args);System.out.println(name + "方法被拦截后执行方法");return null;}

注意这里使用 method.invoke(testProxyJDKImp, args)使用的是被代理对象,那么在test1方法里调用this.test2时,这个this自然就是被代理对象,那么test2走的就是原始的方法;

而使用cglib代理的时候,调用test1方法最终执行的是

    public final void test1() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {//这个this就是下面的obj,是代理对象var10000.intercept(this, CGLIB$test1$1$Method, CGLIB$emptyArgs, CGLIB$test1$1$Proxy);} else {super.test1();}}

也就是我们实现的MethodInterceptorintercept方法里

            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("CGLIB拦截前");proxy.invokeSuper(obj,args);System.out.println("CGLIB拦截后");return null;}

注意,cglib代理时我们从头到尾并不需要被代理类的对象,这里的obj是代理对象,那么自然在调用test1方法时,它里面的this.test2方法里的this就是代理对象,自然执行的是被代理后的test2方法。

说到这里,你有没有想起@Transactional的一种失效的使用场景呢?其实道理都是一样的。

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

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

相关文章

Python入门学习13(面向对象)

一、类的定义和使用 类的使用语法&#xff1a; 创建类对象的语法&#xff1a; ​​​​​​​ class Student:name None #学生的名字age None #学生的年龄def say_hi(self):print(f"Hi大家好&#xff0c;我是{self.name}")stu Student() stu.name &q…

ElementUI浅尝辄止36:Input 输入框

通过鼠标或键盘输入字符 1.如何使用&#xff1f; Input 为受控组件&#xff0c;它总会显示 Vue 绑定值。 通常情况下&#xff0c;应当处理 input 事件&#xff0c;并更新组件的绑定值&#xff08;或使用v-model&#xff09;。否则&#xff0c;输入框内显示的值将不会改变。不支…

设置vue 项目浏览器界面关闭或者刷新,询问是否保存内容

可以结合使用JavaScript的window.beforeunload事件和Vue的生命周期钩子函数。 <template> <!-- 组件模板 --> </template> <script> export default { data() { return { // 组件数据 }; }, beforeMount() { window.addEventListener(be…

FFMPEG视频压缩与Python使用方法

一、简介 FFMPEG 是一个完整的&#xff0c;跨平台的解决方案&#xff0c;记录&#xff0c;转换和流音频和视频。 官网&#xff1a;https://ffmpeg.org/ 二、安装 1、Linux&#xff1a; sudo apt install ffmpeg 2、Mac: brew install ffmpeg 3、Windows: 下载文件&#…

基于googlenet网络的动物种类识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................. % 获取输入层的尺寸 Inp…

Pytorch Advanced(一) Generative Adversarial Networks

生成对抗神经网络GAN&#xff0c;发挥神经网络的想象力&#xff0c;可以说是十分厉害了 参考 1、AI作家 2、将模糊图变清晰(去雨&#xff0c;去雾&#xff0c;去抖动&#xff0c;去马赛克等)&#xff0c;这需要AI具有“想象力”&#xff0c;能脑补情节&#xff1b; 3、进行数…

linux命令-split 命令

split 命令 split命令用于将文件分割成较小的部分。它可以按照文件大小、行数或特定的分隔符来划分文件 [duserxxxx dir2]$ split --help Usage: split [OPTION]... [INPUT [PREFIX]] Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000 l…

centos 下 Makefile 独立模块编译ko

1、安装编译内核环境包 编译需要用到kernel 源码&#xff0c;centos 下需先安装 kernel-devel 包&#xff0c;要下与自己kernel 对应版本 yum install kernel-devel 2、首先从内核或自己写的模块&#xff0c;发到编译环境中 注&#xff1a;就像我自己拷贝一个 bcache 驱动的目…

从零开始在树莓派上搭建WordPress博客网站并实现公网访问

文章目录 序幕概述1. 安装 PHP2. 安装MySQL数据库3. 安装 Wordpress4. 设置您的 WordPress 数据库设置 MySQL/MariaDB创建 WordPress 数据库 5. WordPress configuration6. 将WordPress站点发布到公网安装相对URL插件修改config.php配置 7. 支持好友链接样式8. 定制主题 序幕 …

算法基础:归并排序(超详细)

归并排序 题目1&#xff1a;归并排序 给定你一个长度为 n 的整数数列。 请你使用归并排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行&#xff0c;第一行包含整数 n。 第二行包含 n 个整数&#xff08;所有整数均在 1∼109 范围…

时序预测 | MATLAB实现LSSVM最小二乘支持向量机时间序列预测未来

时序预测 | MATLAB实现LSSVM最小二乘支持向量机时间序列预测未来 目录 时序预测 | MATLAB实现LSSVM最小二乘支持向量机时间序列预测未来预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现LSSVM时间序列预测未来(最小二乘支持向量机)&#xff1b; 2.运行环境Mat…

Scala在大数据领域的崛起:当前趋势和未来前景

文章首发地址 Scala在大数据领域有着广阔的前景和现状。以下是一些关键点&#xff1a; Scala是一种具有强大静态类型系统的多范式编程语言&#xff0c;它结合了面向对象编程和函数式编程的特性。这使得Scala非常适合处理大数据&#xff0c;因为它能够处理并发、高吞吐量和复杂…

LabVIEW更改Tab所选标签的颜色

LabVIEW更改Tab所选标签的颜色 在开发过程中&#xff0c;有时会出现要将不同tab页设置不同颜色的情况。此VI允许编程方式更改前面板选项卡控件上选项卡的颜色。它是突出显示所选选项卡的理想选择 在某些应用程序中&#xff0c;用户希望在按下时突出显示选项卡控件。此VI使用事…

借助ChatGPT使用Pandas实现Excel数据汇总

一、问题的提出 现在有如下一个Excel表&#xff1a; 上述Excel表中8万多条数据&#xff0c;记录的都是三年以来花菜类的销量&#xff0c;现在要求按月汇总实现统计每个月花菜类的销量总和&#xff0c;如果使用Python的话要给出代码。 二、问题的解决 1.首先可以用透视表的方…

信息化发展36

IT审计底稿 1 、审计工作底稿是审计证据的载体&#xff0c; 是审计人员在审计过程中形成的审计工作记录和获取的资料。它形成于审计过程&#xff0c; 也反映整个审计过程。 2 、审计底稿的作用表现在&#xff1a; 是形成审计结论、发表审计意见的直接依据&#xff1b; 是评价考…

idea配置git(gitee)并提交(commit)推送(push)

Intellij Idea VCS | 版本控制 - 知乎 IDEA项目上传到gitee仓库_idea上传代码到gitee_robin19712的博客-CSDN博客 git程序下载国内镜像地址&#xff1a; https://registry.npmmirror.com/binary.html?pathgit-for-windows/v2.42.0.windows.2/ 解压后放到固定路径&#xff1a…

软件测开记录(一)

知识点汇总 14&#xff1a;00面试&#xff0c;14&#xff1a;06就出来了&#xff0c;问的问题有点变态。。。 python自动化测试学习路线&#xff08;从入门到精通&#xff09; 单元知识点 测试常用工具 python自动化测试学习路线&#xff08;从入门到精通&#xff09; 服务…

css溢出隐藏的五种方法

一、文本溢出 当容器中的文本内容超出容器的宽度或高度时&#xff0c;就会出现文本溢出的情况。下面介绍几种CSS实现文本溢出的方法。 单行文本溢出省略&#xff1a; 单行文本溢出省略通常用于标题等文本显示&#xff0c;可以通过设置white-space和text-overflow属性实现。w…

ADW300物联网电表支持MODBUSTCP协议、MQTT协议-安科瑞黄安南

摘要 随着通信技术的应用越来越广泛&#xff0c;具有通信功能的电子产品越来越多&#xff0c;同时也随着Wi-Fi无线覆盖网络区域的形成&#xff0c;如何利用无线网络覆盖广、带宽高、低使用费率的优势组建物联网系统&#xff0c;变成了一个很实际的问题。 安科瑞也紧跟趋势推出…

antv-G6知识图谱安装--使用(实例)--连接线修改成动态,并添加跟随线移动的光圈,设置分支跟踪定位功能

这系列文章主要是完成一个图谱的自定义修改&#xff08;最近太忙了长篇分段更新自己使用流程&#xff09; 1. 连接线修改成动态&#xff0c;并添加跟随线移动的光圈 2. 自定义卡片样式和文字内容 3. 自定义伸缩节点的样式&#xff0c;并添加动画样式 3. 自定义弹窗样式 4. 自定…