Cglib动态代理从入门到掌握

news2024/11/24 0:50:44

Cglib 动态代理

本文的写作目的是为了探究 Spring 框架中在使用@Transactional标注的方法中使用 this 进行自调用时事务失效的原因,各种视频教程中只是简单指出 this 指向的不是代理类对象,而是目标类对象,但是并没有解释为什么 this 不是代理类对象?

在学习完 JDK 动态代理之后,我认为是动态代理的原因。虽然知道 Cglib Proxy 和 JDK Proxy 的实现原理不同,但当时认为方法调用只能通过 invoke 进行反射调用(错误依据),而传递给 invoke 方法的对象就是目标类对象,因此 this 指向的就是传递过来的目标类对象。具体可以查看另一篇博客。

最近学习完 Cglib 动态代理之后,发现动态代理类进行方法调用并不是只能依靠反射调用的,因此那一篇博客的分析也就不成立了。先说结论,在 Cglib 动态代理中,由于绕开了反射调用方法,所以 this 既可以指向代理类对象,也可以和 JDK 动态代理一样指向目标类对象,而 Spring 框架中选择了后者,从而有了 this 造成事务失效的情况。但是就 Cglib 本身实现动态代理而言,这个问题是可以避免的。

依赖环境

下面两个 pom 依赖二选一即可

<!--spring-core中包含cglib-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.30</version>
</dependency>

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

案例演示

public class CglibProxyTest {
    private static final String CLASSPATH = ClassLoader.getSystemResource("").getPath().substring(1);

    public static void main(String[] args) {
        // 设置生成字节码文件
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, CLASSPATH);

        MethodInterceptor methodInterceptor = new MyMethodInterceptor();
        Enhancer enhancer = new Enhancer();
        // 设置父类字节码
        enhancer.setSuperclass(ServiceImpl.class);
        // 设置增强方法(即方法拦截器)
        // TODO: 从代理类的源码中可以看出来,setCallbacks只会使用到第一个拦截器,那么setCallbacks方法有什么意义呢?
        enhancer.setCallback(methodInterceptor);

        ServiceImpl serviceImpl = (ServiceImpl) enhancer.create();
        serviceImpl.show("Hello World");
        // 测试cglib代理方式下的this自调用和Spring事务的this自调用的区别
        //serviceImpl.getMsg(1, 2);
    }
}

class MyMethodInterceptor implements MethodInterceptor {


    /**
     * 拦截方法
     *
     * @param proxy        代理类对象
     * @param targetMethod 目标类中的方法对象
     * @param args         方法参数
     * @param methodProxy  Cglib底层使用到的MethodProxy对象,并不是代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("proxy.getClass() = " + proxy.getClass());
        System.out.println("targetMethod.getName() = " + targetMethod.getName());
        System.out.println("targetMethod.getDeclaringClass().getName() = " + targetMethod.getDeclaringClass().getName());

        System.out.println("===========targetMethod before==========");

        // 可能出现的情况
        //  情况一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的无限递归中
        //

        //  情况二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循环中。
        //  这是因为targetMethod是一个Method方法对象,只有invoke方法,此时没有涉及到MethodProxy的invoke和invokeSuper方法
        //  而proxy是targetClass的一个子类对象,因此targetMethod.invoke(proxy, args)相当于在调用proxy对象中的同名targetMethod方法,即增强方法。所以陷入死循环

        // Spring使用this无法增强的原因,使用下面的情况三方案,其中target对象是外部传入的,和MethodInvocationHandler一样
        // 情况三:methodProxy.invoke(target, args)
        //
        Object result = methodProxy.invokeSuper(proxy, args);

        System.out.println("===========targetMethod after===========");

        return result;
    }
}


/**
 * 目标类(被代理类)不存在接口
 */
class ServiceImpl {
    public void show(String msg) {
        System.out.println(msg);
    }

    public String getMsg(int x, int y) {
        this.show("Hello World");
        return String.valueOf(x + y);
    }
}

代理类和目标类的关系结构图

在这里插入图片描述

图:Cglib动态代理中代理类与目标类之间的关系

代理类字节码

对反编译后的源代码文件进行变量名的调整,删减一些非核心的细节内容。

从重写的增强方法中可以看出来,传递给 intercept() 方法的参数的含义分别是:

  1. Object:(ServiceImplCglibProxy)代理类对象(this)
  2. Method:代理类中对于目标类方法的对象引用,即上图中的 Method01Method02Method03
  3. Object[]:方法参数数组(args)
  4. MethodProxy:根据代理类中的 cglibMethod0x()method0x() 生成的 MethodProxy 对象
public class ServiceImplCglibProxy
        extends ServiceImpl
        implements Factory {

    // 目标类中的的方法对象(show)
    private static Method showMethod;

    // 代理类中show方法和cglibShow方法共同构建的MethodProxy对象
    private static MethodProxy showMethodProxy;

    // 目标类中的的方法对象(show)
    private static Method getMsgMethod;

    // 代理类中getMsg方法和cglibGetMsg方法共同构建的MethodProxy对象
    private static MethodProxy getMsgMethodProxy;

    // 用来包装Object[0]
    private static final Object[] emptyArgs = new Object[0];

    // Object类中的方法
    private static Method finalizeMethod;
    private static MethodProxy finalizeMethodProxy;


    // 判断是否绑定过ThreadLocal中的Callback,如果绑定过,那么之后就不需要绑定了
    private boolean bound;

    // 自定义增强逻辑,MethodInterceptor是Callback的一个子接口,在实例化的时候会通过调用静态方法进行设置,省略
    private MethodInterceptor methodInterceptor;


    static {
        init();
    }

    @SneakyThrows
    static void init() {
		// 获取当前代理类的字节码对象
        Class proxyClass = Class.forName("org.example.ServiceImplCglibProxy");
        Method[] methods;

        // 处理Object类中的所有方法
        Class targetClass = Class.forName("java.lang.Object");
        methods = ReflectUtils.findMethods(
                new String[]{
                        "finalize", "()V",
                        "equals", "(Ljava/lang/Object;)Z",
                        "toString", "()Ljava/lang/String;",
                        "hashCode", "()I",
                        "clone", "()Ljava/lang/Object;"},
                targetClass.getDeclaredMethods());

        finalizeMethod = methods[0];
        finalizeMethodProxy = MethodProxy.create(targetClass, proxyClass, "()V", "finalize", "cglibFinalize");
        // 省略 Object 类中的其他方法的处理...

        // 处理ServiceImpl(父类)中的所有方法
        targetClass = Class.forName("org.example.ServiceImpl");
        methods = ReflectUtils.findMethods(
                new String[]{
                        "show", "(Ljava/lang/String;)V",
                        "getMsg", "(II)Ljava/lang/String;"
                },
                targetClass.getDeclaredMethods());
		
        // 处理ServiceImpl中的show方法
        showMethod = methods[0];
        showMethodProxy = MethodProxy.create(targetClass, proxyClass, "(Ljava/lang/String;)V", "show", "cglibShow");

        // 处理ServiceImpl中的getMsg方法
        getMsgMethod = methods[1];
        getMsgMethodProxy = MethodProxy.create(targetClass, proxyClass, "(II)Ljava/lang/String;", "getMsg", "cglibGetMsg");
    }

    
    public static MethodProxy findMethodProxy(Signature signature) {
        String methodSignature = signature.toString();
        // 先比较hashCode值,再比较字符串
        switch (methodSignature.hashCode()) {
            case -1574182249:
                if (methodSignature.equals("finalize()V")) {
                    return finalizeMethodProxy;
                }
                break;

            case 550733602:
                if (methodSignature.equals("show(Ljava/lang/String;)V")) {
                    return showMethodProxy;
                }
                break;
            case 351083702:
                if (methodSignature.equals("getMsg(II)Ljava/lang/String;")) {
                    return getMsgMethodProxy;
                }
                break;
        }

        return null;
    }


    // 为便于区分,称为cglib方法
    void cglibShow(String msg) {
        super.show(msg);
    }

    // 为便于区分,称为重写方法
    @SneakyThrows
    @Override
    public void show(String msg) {
        if(methodInterceptor == null){
            // 当使用methodProxy.invoke(target, args),由于target没有methodInterceptor,所有会进入到这个逻辑分支中
            super.show(msg);
            return;
        }
        // 将参数封装成Object数组,调用intercept方法
        // 在正常传递MethodInterceptor的情况下,会调用该方法
        methodInterceptor.intercept(this, showMethod, new Object[]{msg}, showMethodProxy);
    }


    // 为便于区分,称为cglib方法
    String cglibGetMsg(int x, int y) {
        return super.getMsg(x, y);
    }

    // 为便于区分,称为重写方法
    @SneakyThrows
    @Override
    public String getMsg(int x, int y) {
        if(methodInterceptor == null){
            return super.getMsg(x, y);
        }
        return (String) methodInterceptor.intercept(this, getMsgMethod, new Object[]{x, y}, getMsgMethodProxy);
    }

}

增强方法的调用流程图

在进一步讨论增强方法的调用流程之前,先重新查看 MethodInterceptor 的 intercept() 方法。我们可以得到四个参数,那么怎么选择来达成我们调用原始方法的目的。

结论:

  • 在不考虑引入额外的目标类对象 target,仅使用 intercept 方法中提供的四个参数的情况下,只有 invokeSuper() 方法能够正确执行,同时 this 指针指向的是 ServiceImplCglibProxy(代理类)对象,因为不会出现像 Spring 中使用 @Transactional 标注的方法中使用 this 会造成事务失效的问题。
  • 在使用额外引入的目标类对象 target 的情况下(和 JDK 动态代理的 InvocationHandler 类似,但 JDK Proxy 强制要求一个目标类对象,而 Cglib Proxy 并不要求目标类对象),除了 invokeSuper() 方法外,另外两个方法都可以正确执行。
  • **使用 MethodProxy 对象和 Method 对象的区别在于,MethodProxy 借助额外生成的字节码 FastClass 来实现对方法的直接调用,而 Method 则是通过反射来调用方法。**因此 MethodProxy 方式调用可以解决 Spring 的 this 造成的事务失效问题,但 Spring 是故意设计成和 JDK Proxy 的效果一样,为此还舍弃 invokeSuper 的调用方式,而是引入 target 来使用 invoke 调用。这样设计的官方解释没有仔细了解,个人分析,为了统一结果,因为 JDK Proxy 是通过反射来实现的,因此 this 只能代表目标类对象;如果仅仅因为使用的动态代理不同,就造成有的时候 this 失效,有的时候 this 有效,那么就凭空多添加了混乱。因此还不如都设计成 this 失效。
class MyMethodInterceptor implements MethodInterceptor {
    // 不是必要的,Spring注入target造成this无法调用自身对象,直接使用proxy对象就不会出现这种情况
    private Object target;
    
    @Override
    public Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("===========targetMethod before==========");

        // 由于FastClass的机制,这里不能调用methodProxy.invoke(proxy, args),否则

        // 正常可能出现的情况
        //  情况一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的无限递归中
        //

        //  情况二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循环中。
        //  这是因为targetMethod是一个Method方法对象,只有invoke方法,此时没有涉及到MethodProxy的invoke和invokeSuper方法
        //  而proxy是targetClass的一个子类对象,因此targetMethod.invoke(proxy, args)相当于在调用proxy对象中的同名targetMethod方法,即增强方法。所以陷入死循环

        // Spring使用this无法增强的原因,使用下面的情况三方案,其中target对象是外部传入的,和MethodInvocationHandler一样
        // 情况三:methodProxy.invoke(target, args)
        //
        Object result = methodProxy.invokeSuper(proxy, args);

        System.out.println("===========targetMethod after===========");

        return result;
    }
}

在这里插入图片描述

图:增强方法调用流程

MethodProxy

主要关注 invoke() 方法和 invokeSuper() 方法,因为这两个方法会在 MethodInterceptor 对象的 intercept() 方法中被我们调用来达到调用目标类中的原始方法的目的。

public class MethodProxy {
    private Signature overrideMethodSignature;
    private Signature cglibMethodSignature;

    // create方法中生成,init方法中清空
    private CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile FastClassInfo fastClassInfo;

    public static MethodProxy create(Class targetClass, 
                                     Class proxyClass, 
                                     String desc, 
                                     String overrideMethodName, 
                                     String cglibMethodName) {
        MethodProxy proxy = new MethodProxy();
        proxy.overrideMethodSignature = new Signature(overrideMethodName, desc);
        proxy.cglibMethodSignature = new Signature(cglibMethodName, desc);
        proxy.createInfo = new CreateInfo(targetClass, proxyClass);
        return proxy;
    }

    public Object invoke(Object object, Object[] args) throws Throwable {
        this.init();
        FastClassInfo fci = this.fastClassInfo;
        // 默认情况下 object 是 proxyClass 类型,那么 overrideMethod -> intercept -> invoke -> overrideMethod 形成死循环。

        // 如果按照Spring的方式手动传递 targetClass 类型的对象,object是targetClass类型(目标类)
        return fci.targetFastClass.invoke(fci.overrideMethodIndex, object, args);
    }

    public Object invokeSuper(Object proxy, Object[] args) throws Throwable {
        this.init();
        FastClassInfo fci = this.fastClassInfo;
        // 重写父类方法时,留了一份拷贝,称之为cglibMethod,因此可以在当前类直接调用父类中的同名方法
        return fci.proxyFastClass.invoke(fci.cglibMethodIndex, proxy, args);
    }

    private void init() {
        if (this.fastClassInfo == null) {
            synchronized (this.initLock) {
                if (this.fastClassInfo == null) {
                    CreateInfo ci = this.createInfo;
                    FastClassInfo fci = new FastClassInfo();
                    fci.targetFastClass = helper(ci, ci.targetClass);
                    fci.proxyFastClass = helper(ci, ci.proxyClass);
                    // overrideMethodSignature在targetClass和proxyClass中的签名都相同
                    fci.overrideMethodIndex = fci.targetFastClass.getIndex(this.overrideMethodSignature);
                    fci.cglibMethodIndex = fci.proxyFastClass.getIndex(this.cglibMethodSignature);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }

    }

    private static FastClass helper(CreateInfo ci, Class classType) {
        FastClass.Generator generator = new FastClass.Generator();
        generator.setType(classType);
        generator.setClassLoader(ci.proxyClass.getClassLoader());
        generator.setNamingPolicy(ci.namingPolicy);
        generator.setStrategy(ci.strategy);
        generator.setAttemptLoad(ci.attemptLoad);
        return generator.create();
    }

    private MethodProxy() {
    }

    public Signature getSignature() {
        return this.overrideMethodSignature;
    }

    public String getSuperName() {
        return this.cglibMethodSignature.getName();
    }

    public int getSuperIndex() {
        this.init();
        return this.fastClassInfo.cglibMethodIndex;
    }

    FastClass getFastClass() {
        this.init();
        return this.fastClassInfo.targetFastClass;
    }

    FastClass getSuperFastClass() {
        this.init();
        return this.fastClassInfo.proxyFastClass;
    }

    @SneakyThrows
    public static MethodProxy find(Class classType, Signature sig) {
        Method method = classType.getDeclaredMethod("findMethodProxy", MethodInterceptorGenerator.FIND_PROXY_TYPES);
        return (MethodProxy) method.invoke(null, sig);
    }

    private static class CreateInfo {
        Class targetClass;
        Class proxyClass;
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;

        public CreateInfo(Class targetClass, Class proxyClass) {
            this.targetClass = targetClass;
            this.proxyClass = proxyClass;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if (fromEnhancer != null) {
                this.namingPolicy = fromEnhancer.getNamingPolicy();
                this.strategy = fromEnhancer.getStrategy();
                this.attemptLoad = fromEnhancer.getAttemptLoad();
            }

        }
    }

    private static class FastClassInfo {
        FastClass targetFastClass;
        FastClass proxyFastClass;
        int overrideMethodIndex;
        int cglibMethodIndex;

        private FastClassInfo() {
        }
    }
}

FastClass

为了避免使用 Method 的 invoke 方法来进行反射调用而设计的。

TargetFastClass

public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {
    // 这里无论传入的是ServiceImpl还是ServiceImplCglibProxy,都可以进行强转
    // 如果是ServiceImplCglibProxy,就调用同名方法
    ServiceImpl serviceImpl = (ServiceImpl)obj;

    // 没有匹配就抛出异常
    try {
        switch (methodIndex) {
            case 0:
                return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());
            case 1:
                // 如果传入的实际对象是ServiceImplCglibProxy类型的,那么就会调用增强方法,而增强方法又会进而到intercept中,从而造成死循环
                serviceImpl.show((String)args[0]);
                return null;
        }
    } catch (Throwable throwable) {
        throw new InvocationTargetException(throwable);
    }

    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

ProxyFastClass

public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {
    // 强转成代理对象(ServiceImplCglibProxy)
    ServiceImplCglibProxy serviceImplProxy = (ServiceImplCglibProxy)obj;

    // 没有匹配就抛出异常
    try {
        // ProxyFastClass的索引顺序和TargetFastClass的索引顺序没有任何关系
        switch (methodIndex) {
            case 0:
                return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());
            case 1:
                return serviceImpl.cglibGetMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());
            case 2:
                serviceImpl.cglibShow((String)args[0]);
                return null;
            case 3:
                serviceImpl.show((String)args[0]);
                return null;
        }
    } catch (Throwable throwable) {
        throw new InvocationTargetException(throwable);
    }

    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

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

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

相关文章

工业性能CCD图像处理

硬件部分 软件部分 CCD新相机的调试处理(更换相机处理,都要点执行检测来查看图像变化) 问题:新相机拍摄出现黑屏,图像拍摄不清晰,(可以点击图像,向下转动鼠标的滚轮(Mouse Wheel)放大图像) 解决办法:进入CCD的设定,选择对应的相机,调试好参数(如下图) 选择好相…

技术Leader:像李云龙一样打造学习型团队

今天跟大家分享一下怎么样构建一个学习型的团队。 首先对于计算机行业而言&#xff0c;不明而喻&#xff0c;我们要接受的东西真的太多了。我们接触的信息和变化也太多了。如果只是因循守旧&#xff0c;排斥新东西&#xff0c;那么我们被时代淘汰只是个时间问题。 想当年我大…

【电子取证:FTK IMAGER 篇】DD、E01系统镜像动态仿真

​ 文章目录 【电子取证&#xff1a;FTK Imager 篇】DD、E01系统镜像动态仿真一、DD、E01系统镜像动态仿真 &#xff08;一&#xff09;使用到的软件 1、FTK Imager (v4.5.0.3)2、VMware Workstation 15 Pro (v15.5.2)&#xff08;二&#xff09;FTK Imager 挂载镜像 1、选择 …

thinkphp6入门(13)-- 一对多关联模型

定义一对一关联&#xff0c;例如&#xff0c;一个用户都有多个工作经历。 一、两表 1.用户表:user 2.工作经验表&#xff1a;work_experience user表的id关联work_experience表的user_id。 注意看&#xff0c;user_id1的有2条工作经验 二、数据模型 主表模型&#xff1a;…

Centos7 安装Redis详细教程

1. 安装依赖 redis是由C语言开发&#xff0c;因此安装之前必须要确保服务器已经安装了gcc&#xff0c;可以通过如下命令查看机器是否安装&#xff1a; gcc -v如果没有安装则通过以下命令安装&#xff1a; yum install -y gcc2.下载redis安装包并解压 # 下载&#xff0c;我是…

欧拉函数与欧拉定理

文章目录 AcWing 873. 欧拉函数题目链接欧拉函数欧拉函数的证明思路CODE时间复杂度分析 AcWing 874. 筛法求欧拉函数题目链接问题分析与时间复杂度CODE思路 欧拉定理 AcWing 873. 欧拉函数 题目链接 https://www.acwing.com/activity/content/problem/content/942/ 欧拉函数 …

java方法引用语法规则以及简单案例

目录 一、方法引用1.1 什么是方法引用1.2 方法引用的语法规则1.3 构造器引用1.4 方法引用的简单案例 参考资料 一、方法引用 1.1 什么是方法引用 方法引用是 Lambda 表达式的一种简写形式&#xff0c;用于表示已有方法的直接引用。 类似于lambda表达式&#xff0c;方法引用也…

插入算法(C语言)

#include<cstdio> #include<iostream> #define N 9 using namespace std; int main() {int arr[N1] { 1,4,7,13,16,19,22,25,280 }; int in,i,j;//要插入的数字//打印要插入数字的数组所有元素printf("插入前的数组: ");for ( i 0; i <N; i){print…

阶段十-java新特性

JDK9新特性 1.模块化系统 jar包结构的变化 jar -》model -》package -》class 通过不同的模块进行开发 每个模块都有自己的模块配置文件module-info.java 2.JShell JDK9自带的命令行开发&#xff0c;在进行简单的代码调试时可以直接编译使用 可以定义变量&#xff0c;方法&…

【改进YOLOv8】矿物尺寸图像分析系统:融合位置感知循环卷积(ParC)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着科技的不断发展&#xff0c;计算机视觉技术在各个领域中得到了广泛的应用。其中&#xff0c;物体检测是计算机视觉领域中的一个重要研究方向。物体检测的目标…

工作随记:oracle 19c客户端通过service访问PDB异常问题

文章目录 概要技术测试分析测试1&#xff1a;测试2&#xff1a;测试3&#xff1a;测试4&#xff1a; 解决方案&#xff1a;1、修改service2、修改pdb名称 总结 概要 应用端访问提示错误信息为&#xff1a;VersionHelper异常!未将对象引用设置到对象的实例&#xff01; 此问题…

jdk21升级,asm报错Unsupported class file major version 65

环境 jdk21升级&#xff0c;asm报错&#xff0c;spring-core版本5.3.18&#xff0c;项目springboot版本为2.6.6 报错明细 Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file ve…

交易历史记录20231205 记录

昨日回顾&#xff1a; select top 10000 * from dbo.CODEINFO A left join dbo.全部&#xff21;股20231205010101 B ON A.CODE B.代码 left join dbo.全部&#xff21;股20231205CONF D on A.CODED.代码left join dbo.全部&#xff21;股20231205 G on A.CODEG.代码 left…

Ubuntu-rsyslog和systemd-journald日志服务

rsyslog日志服务 rsyslog作为传统的系统日志服务&#xff0c;把所有收集到的日志都记录到/var/log/目录下的各个日志文件中。 常见的日志文件如下&#xff1a; /var/log/messages 绝大多数的系统日志都记录到该文件 /var/log/secure 所有跟安全和认证授权等日志…

Tcl语言语法精炼总结

一、置换符号 1.变量置换 $ TCl解释器会将认为$后面为变量名&#xff0c;将变量名置换成它的值 2.命令置换 [] []内是一个独立的TCL语句 3.反斜杠置换 \ 换行符、空格、[、$等被TCL解释器当作特殊符号处理。加上反斜杠后变成普通字符 \t TAB \n 换行符 4.双引号 “” “…

Tcon基础知识

1、TCON&#xff0c;就是 Timing Controller 的缩写。从主芯片输出的要在 TFT 显示屏上显示的数据&#xff0c;在经过 TCON 模块后可以变换生成 Panel 可以直接利用的 DATA 信号和驱动器&#xff08;包括 source driver 和 gate driver&#xff09;的控制信号。 TV 市场上 TCO…

Java--1v1双向通信-控制台版

文章目录 前言客户端服务器端输出线程端End 前言 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的网络传输协议&#xff0c;它提供了端到端的数据传输和可靠性保证。 本程序就是基于tcp协议编写而成的。 利用 TCP 协议进行通信的两个应用…

[Unity+文心知识库]使用百度智能云搭建私有知识库,集成知识库API,打造具备知识库的AI二次元姐姐

1.简述 最近从百度智能云的官方技术支持那边了解到&#xff0c;目前百度千帆大模型平台提供有在线的知识库功能&#xff0c;能够在线上传自己的私人知识库文档&#xff0c;并且配置文心一言模型作为文本生成的引擎&#xff0c;构建自己的私有知识库。之前自己搭建知识库都是用的…

微服务--07--Sentienl中使用的限流算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Sentienl中使用的限流算法1、计数器固定窗口算法2、计数器滑动窗口算法----&#xff08;默认&#xff09;3、漏桶算法----&#xff08;排队等待&#xff09;4、令牌…