十、方法调用的底层实现

一、方法调用分析(main方法是JVM指令执行的起点)

                我们写的代码,经过编译、经过类加载的各种阶段,进入了 JVM 的运行时数据区。但作为程序员真正关心是代码的执行,代码的执行其实本质上是方法的执行,站在 JVM 的角度归根到底还是字节码的执行。

main 函数是 JVM 指令执行的起点,JVM 会创建 main 线程来执行 main 函数,以触发 JVM 一系列指令的执行,真正地把 JVM 跑起来。

        接着,在我们的代码中,就是方法调用方法的过程,所以了解方法在 JVM 中的调用是非常必要的。

方法的调用与虚拟机栈示意图

二、方法调用的字节码指令

关于方法的调用,Java 字节码提供了 5 个指令,来调用不同类型的方法:

  • invokestatic 用来调用静态方法;--非虚方法
  • invokespecial 用于调用私有实例方法、构造器及 super 关键字等;--非虚方法
  • invokevirtual 用于调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种;-- 虚方法
  • invokeinterface 和上面这条指令类似,不过作用于接口类; -- 虚方法
  • invokedynamic 用于调用动态方法。

三、非虚方法

        如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法。这类方法的调用称为解析。它们在类加载的时候就会把符号引用解析为该方法的直接引用。

        静态方法,在编译的时候就已经写入到了常量池--非虚方法

        只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段中确定唯一的调用版本,Java 语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类方法 4 种,再加上被 final 修饰的方法(尽管它使用 invokevirtual 指令调用),这 5 种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用。不需要在运行时再去完成。

  • invokeStatic 用来调用静态方法
public static void main(String[] args) StaticResolution.Hello();
}

javap -v

这个方法调用在编译期间就明确以常量池项的形式固化在字节码指令的参数之中了。

  • invokeSpecial 用于调用私有实例方法、构造器及 super 关键字等

四、虚方法

        与非虚方法相反,不是非虚方法的方法就是虚方法。主要包括以下字节码中的两类

  • invokevirtual 用于调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种(排除掉被 final 修饰的方法);
  • invokeinterface 和上面这条指令类似,不过作用于接口类;

        为什么叫做虚方法呢?就是方法在运行时是可变的。

        很多时候,JVM 需要根据调用者的动态类型,来确定调用的目标方法,这就是动态绑定的过程;相对比,invokestatic 指令和 invokespecial 指令,就属于静态绑定过程。

        因为 invokeinterface 指令跟 invokevirtual 类似,只是作用与接口,所以我们只要熟悉 invokevirtual 即可。

        方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。

        在程序运行时,进行方法调用是最普遍、最频繁的操作,但是Class文件的编译过程不包括传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相对于之前说的直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。

  • 解析

        所有方法调用中的目标方法在Class文件里面都是一个常量池中的引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用。这种解析能成立的前提是:方法在程序真正执行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来,这类方法的调用称为解析。

        在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了他们不可能通过继承或别的方式重写其他版本,因此他们适合在类加载阶段进行解析。

        静态方法、私有方法、实例构造器、父类方法。这些方法称为非虚方法,它们在类加载的时候就会把符号引用解析为该方法的直接引用。与之相反,其他方法称为虚方法(除去final方法)

分派

  • 多态就是动态分派和静态分派

        方法会根据你送入的参数有不同的表现形式,这个就是分派。

要了解虚方法我们必须了解以下基础:

        Java 是一门面向对象的程序语言,因为 Java 具备面向对象的 3 个基本特征:继承、封装和多态。

        分派调用过程将会揭示多态性特征的一些最基本的体现,如“重载”和“重写”在 Java 虚拟机之中是如何实现的

  • 静态分派-重载

        多见于方法的重载。(重载:一个类中允许同时存在一个以上的同名方法,这些方法的参数个数或者类型不同)

重载使用 invokevirtual 指令

        “Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type)。

        静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。父类引用指向子类对象,编译并不知道实际类型,只有运行的时候才知道。

        代码中定义了两个静态类型相同但实际类型不同的变量,但虚拟机(准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的。因此,在编译阶段,Javac 编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了 sayHello(Human)作为调用目标。

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。

       静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,而是由编译器来完成。

所以代码运行结果如下:

public class StaticDispatch {static abstract class Human{}static class Man extends Human{}static class Woman extends Human{}public static void sayHello(Human guy){System.out.println("hello,guy!");}public static void sayHello(Man guy){System.out.println("hello,gentlemen!");}public static void sayHello(Woman guy){System.out.println("hello,lady!");}public static void main(String[] args) {Human man=new Man();Human woman=new Woman();sayHello(man);sayHello(woman);}
}

输出:

hello,guy!

hello,guy!

总结:例子很简单,方法会根据你送入的参数有不同的表现形式,这个就是分派。

        举个简单的例子:你在酒吧遇到一个你心动的人,但这个人看上去不男不女,你怎么去与他/她打招呼?这个时候我至少知道是一个人,所以 打招呼说:地球人!你好,我是来自火星的(hello guy!)。我调用它最原始的外观类型(至少是个人)

  • 动态分派-重写

多见于方法的重写。(重写:在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改方法的访问权限,修改返回类型的为父类返回类型的子类。)

另外一个例子:

public class DynamicDispatch {static abstract class Human{protected abstract void sayHello();}static class Man extends Human{ @Overrideprotected void sayHello() { System.out.println("man say hello!");}}static class Woman extends Human{ @Overrideprotected void sayHello() { System.out.println("woman say hello!");}} public static void main(String[] args) {Human man=new Man();Human woman=new Woman();man.sayHello();woman.sayHello();man=new Woman();man.sayHello(); }
}

输出:

man say hello!

woman say hello!

woman say hello!

重写也是使用 invokevirtual 指令,只是这个时候具备多态性。

        显然,这里不可能再根据静态类型来决定,因为静态类型同样是Human的两个变量man和woman在调用sayHello()方法时执行了不同的行为,并且变量man在两次调用中执行了不同的方法。导致这个现象的原因很明显,是这两个变量的实际类型不同,Java虚拟机是如何根据实际类型来分派方法执行版本的呢

invokevirtual 指令有多态查找的机制,该指令运行时,解析过程如下:

  • 找到操作数栈顶的第一个元素所指向的对象实际类型,记做 c;
  • 如果在类型 c 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验。如果通过则返回这个方法直接引用,查找过程结束。不通过则返回 java.lang.IllegalAccessError;
  • 否则,按照继承关系从下往上依次对 c 的各个父类进行第二步的搜索和验证过程;
  • 如果始终没找到合适的方法,则抛出 java.lang.AbstractMethodError 异常,这就是 Java 语言中方法重写的本质。

        由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是Java语言方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

        另外一点,这个时候结合之前课程中讲过虚拟机栈中栈中的内容,我就知道动态链接是干嘛的:

        invokevirtual 可以知道方法 call()的符号引用转换是在运行时期完成的,在方法调用的时候。部分符号引用在运行期间转化为直接引用,这种转化就是动态链接。

详细看栈帧执行对内存区域的影响

方法表,也叫虚方法表

        虚拟机动态分派的实现

        动态分派会执行非常频繁的动作,JVM 运行时会频繁的、反复的去搜索元数据,所以 JVM 使用了一种优化手段,这个就是在方法区中建立一个虚方法表。使用虚方法表索引来替代元数据查找以提高性能。

        在实现上,最常用的手段就是为类在方法区中建立一个虚方法表。虚方法表中存放着各个方法的实际入口地址。

        如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口。

        如果子类中重写了这个方法,子类方法表中的地址将会替换为指向子类实现版本的入口地址。

        如图中,Son 重写了来自 Father 的全部方法,因此 Son 的方法表没有指向 Father 类型数据的箭头。

        但是 Son 和 Father都没有重写来自 Object 的方法,所以它们的方法表中所有从 Object 继承来的方法都指向了 Object 的数据类型。

        方法表一般在类加载阶段的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。

五、接口调用

  • invokeinterface 和 invokevirtual 指令类似,不过作用于接口类的default方法

六、Lambda 表达式

  • invokedynamic,用于调用动态方法

lambda表达式

Runnable r = () -> System.out.println("Hello Lambda!");

        主要使用invokedynamic,用于调用动态方法。底层是 methodHandle,方法句柄

        methodhandle 比反射好用,效率高,但是反射权限更大,能看到更多底层

        invokedynamic 这个字节码是比较复杂。和反射类似,它用于一些动态的调用场景,但它和反射有着本质的不同,效率也比反射要高得多。

        invokedynamic这个指令通常在 Lambda 语法中出现,我们来看一下一小段代码:

        使用 javap -v 命令可以在 main 方法中看到 invokedynamic 指令:

        另外,我们在 javap 的输出中找到了一些奇怪的东西:

        BootstrapMethods 属性在 Java 1.7 以后才有,位于类文件的属性列表中,这个属性用于保存 invokedynamic 指令引用的引导方法限定符。和上面介绍的四个指令不同,invokedynamic 并没有确切的接受对象,取而代之的,是一个叫 CallSite 的对象。

七、方法句柄-MethodHandler

官方文档解释:https://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.html

        invokedynamic 指令的底层,是使用方法句柄(MethodHandle)来实现的。方法句柄是一个能够被执行的引用,它可以指向静态方法和实例方法,以及虚构的 get 和 set 方法,从以下案例中可以看到 MethodHandle 提供的一些方法。

        MethodHandle 是什么?简单的说就是方法句柄,通过这个句柄可以调用相应的方法。

用 MethodHandle 调用方法的流程为:

(1) 创建 MethodType,获取指定方法的签名(出参和入参)

(2) 在 Lookup 中查找 MethodType 的方法句柄 MethodHandle

(3) 传入方法参数通过 MethodHandle 调用方法

methodhandle 比反射好用,效率高,但是反射权限更大,能看到更多底层

MethodType

MethodType 表示一个方法类型的对象,每个 MethodHandle 都有一个 MethodType 实例,MethodType 用来指明方法的返回类型和参数类型。其有多个工厂方法的重载。

STATIC METHODTYPE METHODTYPE(CLASS RTYPE)

STATIC METHODTYPE METHODTYPE(CLASS RTYPE, CLASS PTYPE0)

STATIC METHODTYPE METHODTYPE(CLASS RTYPE, CLASS[] PTYPES)

STATIC METHODTYPE METHODTYPE(CLASS RTYPE, CLASS PTYPE0, CLASS... PTYPES)

STATIC METHODTYPE METHODTYPE(CLASS RTYPE, LISTLASS> PTYPES)

STATIC METHODTYPE METHODTYPE(CLASS RTYPE, METHODTYPE PTYPES)

Lookup

MethodHandle.Lookup 可以通过相应的 findxxx 方法得到相应的 MethodHandle,相当于 MethodHandle 的工厂方法。查找对象上的工厂方法对应于方法、构造函数和字段的所有主要用例。

findStatic 相当于得到的是一个 static 方法的句柄(类似于 invokestatic 的作用)

findVirtual 找的是普通方法(类似于 invokevirtual 的作用)

invoke

其中需要注意的是 invoke 和 invokeExact,前者在调用的时候可以进行返回值和参数的类型转换工作,而后者是精确匹配的。

所以一般在使用是,往往 invoke 使用比 invokeExact 要多,因为 invokeExact 如果类型不匹配,则会抛错。

Lambda 表达式的捕获与非捕获

当 Lambda 表达式访问一个定义在 Lambda 表达式体外的非静态变量或者对象时,这个 Lambda 表达式称为“捕获的”

那么“非捕获”的 Lambda 表达式来就是 Lambda 表达式没有访问一个定义在 Lambda 表达式体外的非静态变量或者对象

Lambda 表达式是否是捕获的和性能悄然相关。一个非捕获的 lambda 通常比捕获的更高效,非捕获的 lambda 只需要计算一次. 然后每次使用到它都会返回一个唯一的实例。而捕获的 lambda 表达式每次使用时都需要重新计算一次,而且从目前实现来看,它很像实例化一个匿名内部类的实例。

lambda 最差的情况性能内部类一样, 好的情况肯定比内部类性能高。

Oracle 公司的性能比较的文档,详细而全面的比较了 lambda 表达式和匿名函数之间的性能差别。

lambda 开发组也有一篇 PPT 其中也讲到了 lambda 的性能(包括 capture 和非 capture 的情况)。 lambda 最差的情况性能内部类一样, 好的情况肯定比内部类性能高。

https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

http://nerds-central.blogspot.tw/2013/03/java-8-lambdas-they-are-fast-very-fast.html

/*** 方法句柄(MethodHandle)使用案例**/
public class MethodHandleDemo {static class Bike {String sound(Integer a) {return "ding  ding ding";}}static class Animal {String sound(Integer a) {return "wow  wow wow";}}static class Man extends Animal {@OverrideString sound(Integer a) {return "ha ha ha" + a;}}String sound(Object o) throws Throwable {// 1、方法句柄--工厂方法FactoryMethodHandles.Lookup lookup = MethodHandles.lookup();// 2、方法类型表示接受的参数和返回类型(第一个参数是返回参数)MethodType methodType = MethodType.methodType(String.class,Integer.class);// 3、拿到具体的MethodHandle(findVirtual相当于字节码)MethodHandle methodHandle = lookup.findVirtual(o.getClass(), "sound", methodType);// 4、执行方法String obj = (String) methodHandle.invoke(o,1);return obj;}public static void main(String[] args) throws Throwable {// 每次送入的实例不一样String str = new MethodHandleDemo().sound(new Bike());System.out.println(str);str = new MethodHandleDemo().sound(new Animal());System.out.println(str);str = new MethodHandleDemo().sound(new Man());System.out.println(str);}
}

总结

        Lambda 语言实际上是通过methodhanle方法句柄来完成的,大致这么实现(JVM 编译的时候使用 invokedynamic 实现 Lambda 表达式,invokedynamic的是使用 MethodHandle 实现的,所以 JVM 会根据你编写的 Lambda 表达式的代码,编译出一套可以去调用 MethodHandle 的字节码代码,参考实例类:MethodHandleDemo)

        句柄类型(MethodType)是我们对方法的具体描述,配合方法名称,能够定位到一类函数。访问方法句柄和调用原来的指令基本一致,但它的调用异常,包括一些权限检查,在运行时才能被发现。

        案例中,我们完成了动态语言的特性,通过方法名称和传入的对象主体,进行不同的调用,而 Bike 和 Man 类,可以没有任何关系。

        可以看到 Lambda 语言实际上是通过方法句柄来完成的,在调用链上自然也多了一些调用步骤,那么在性能上,是否就意味着 Lambda 性能低呢?对于大部分“非捕获”的 Lambda 表达式来说,JIT 编译器的逃逸分析能够优化这部分差异,性能和传统方式无异;但对于“捕获型”的表达式来说,则需要通过方法句柄,不断地生成适配器,性能自然就低了很多(不过和便捷性相比,一丁点性能损失是可接受的)。

        invokedynamic 指令,它实际上是通过方法句柄来实现的。和我们关系最大的就是 Lambda 语法,我们了解原理,可以忽略那些对 Lambda 性能高低的争论,同时还是要尽量写一些“非捕获”的 Lambda 表达式。

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

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

相关文章

idea中切换JDK8、JDK11、JDK17

有时候,我们可能需要在不同的Java版本中去测试或者查看源码,idea可以让我们修改Java的版本。 前提:你必须下载安装好对应的Java版本,可参考文章【windows下切换JDK8、JDK11、JDK17】(https://blog.csdn.net/xijinno1/a…

GoLang学习之路,对Elasticsearch的使用,一文足以(包括泛型使用思想)(一)

这几天没有更新,其主要的的原因是,在学习对Elasticsearch的使用。Elasticsearch是一个非常强大的数据库索引工具。是一个基于Lucene库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎,具有HTTP Web接口和无模式JSON文档。Elastics…

Java学习路线第四篇:框架项目

这篇则分享Java学习路线第四part:框架项目 恭喜你已经成功追到第四章节啦,要被自己的努力感动到了吧,而这节将承担起学完框架项目的使命,本使命为单向契约,你可选择YES或者选择YES。 CRM 动力节点java项目实战|Spri…

工业信息采集平台 软件界面介绍

关键字:蓝鹏测控典型界面,蓝鹏测控交互界面,蓝鹏测控图形界面,蓝鹏测控历史界面,蓝鹏测控自定义界面 软件测控平台对UI程序界面,实现形式没有要求,也就是说,客户可以根据自己的需要设计任何样式的交互界面。 (1&…

【计算机视觉】角点检测(Harris、SIFT)

Harris 角点指的是窗口延任意方向移动,都有很大变化量的点。 用数学公式表示为: E(u,v)反映的移动后窗口的差异,w(x,y)为每个像素的点权值,I(xu,yv)是移动的像素值,I(x,y)是移动前的像素值。 将E(u,v)进行泰勒展开&am…

作为铭文跨链赛道龙头,SoBit 有何突出之处?

跨链桥赛道将是铭文市场长期的发展的刚需 在比特币网络中,Ordinals 铭文铸造的铭文总量已经超过了 5100 万枚,并累计费用收入超 5028 BTC。同时,仅 BRC-20 叙事方向的市值,就已经超过了 30 亿美元,并且随着铭文资产种类…

D3485国产芯片+5V工作电压, 内置失效保护电路采用SOP8封装

D3485是一款5V供电、半双工的RS-485收发器,芯片内部包含一路驱动器和路接收器。D3485使用限摆率驱动器,能显著减小EMI和由于不恰当的终端匹配电缆所引起的反射,并实现高达10Mbps的无差错数据传输。D3485内置失效保护电路,保证接收…

MIT线性代数笔记-第31讲-线性变换及对应矩阵

目录 31.线性变换及对应矩阵打赏 31.线性变换及对应矩阵 线性变换相当于是矩阵的抽象表示,每个线性变换都对应着一个矩阵 例: 考虑一个变换 T T T,使得平面上的一个向量投影为平面上的另一个向量,即 T : R 2 → R 2 T:R^2 \to R…

经典目标检测YOLO系列(一)复现YOLOV1(2)反解边界框及后处理

经典目标检测YOLO系列(一)复现YOLOV1(2)反解边界框及后处理 在上个博客,我们提出了新的YOLOV1架构,这次我们解决前向推理过程中的两个问题。 经典目标检测YOLO系列(一)YOLOV1的复现(1)总体架构 1、边界框的计算 1.1 反解边界框公式的改变 1.1.1 原版…

踩坑RV1106板端部署rknn模型

文章目录 1、交叉编译2、板上跑通3、验证自己模型 1、交叉编译 官方给的一个流程: RKNN 模型推理测试为了避免踩坑在开头提出来 按照官方的流程可以跑通,他自己提供的yolov5s.rknn(640*640)的模型,但是跑自己的模型的时候加载就会…

【星海随笔】浅谈用户态和内核态

内核 操作系统的核心是内核(kernel),它独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。 linux 内核空间在虚拟空间中是固定大小的,并且在物理空间中同样也是固定大小的。 一般情况下&#xff…

如何合理配置云服务器的CPU和内存?

​  提到云服务器性能,大抵有两个主要影响因素,CPU 核心数量和内存容量 ,它们决定了云服务器的速度和可靠性。日常运用中,我们如何判断网站需要需要更多或更少?如何扩大或缩小它们以优化网站的性能? 一般来说,您拥…

黑白图如何转换成彩色图

将黑白图转换为伪彩色图是通过给黑白图中的灰度值映射到彩虹色等色谱上,从而呈现出彩色效果。以下是一些常见的黑白图伪彩转换方式: 热度图(Heatmap): 将低灰度值映射为冷色,高灰度值映射为热色。通常使用…

鸿蒙4.0实战应用(ArkTS)-抽奖转盘

构建主界面 在这个章节中,我们将完成示例主界面的开发,效果如图所示: 功能要求: 通过画布组件Canvas,画出抽奖圆形转盘。通过显式动画启动抽奖功能。通过自定义弹窗弹出抽中的奖品。 在绘制抽奖圆形转盘前&#xff…

文献速递:人工智能医学影像分割---高效的MR引导CT网络训练,用于CT图像中前列腺分割

01 文献速递介绍 如今,根据国家癌症研究所的报告,美国约有9.9%的男性患有前列腺癌。1 此外,根据美国癌症协会的数据,预计2019年将有174,650个新病例被诊断出前列腺癌,与此同时大约有31,620名男性将死于前列腺癌。因此…

大数据与人工智能|信息技术产业架构、行业发展与前沿技术(第2节)

内容链接:信息技术产业架构、行业发展与前沿技术(大数据与人工智能系列课程 第2节) 声明:学习使用,侵权必删! 主要内容:1. 从算盘到量子计算机,介绍了半导体行业的发展历程和技术原…

软件测试/测试开发丨Python自动化测试学习笔记

1. 引言 自动化测试是软件开发中的关键环节,它可以提高测试效率、减少重复工作,并提供更快速、稳定的测试结果。Python作为一种易学易用的编程语言,为自动化测试提供了强大的工具和库。本文将介绍如何使用Python进行自动化测试。 2. 安装Py…

涨见识!微信还能这样批量导出好友

我们在使用微信的过程中,有时会因为种种原因,导致微信被封号,特别是永久封号这种情况,会导致微信里的好友丢失。面对这种情况,我们可以提前把微信里的好友导出来进行备份,万一被封号了,还可以用…

华帝,业绩好起来了,但股价还没

作者 | 辰纹 来源 | 洞见新研社 成为奥运会的赞助商,往往是一个品牌从本土走向全球的起点。 1928年,阿迪达斯首次出现在阿姆斯特丹奥运会上,从那一刻开始,阿迪达斯与耐克在全球相爱相杀,“缠绵”了近百年&#xff1…

uniapp 安装插件 uView (多平台快速开发的UI框架)

1. 下载并导入插件 打开链接 https://ext.dcloud.net.cn/plugin?id1593 导入成功后&#xff0c;可见 2. 添加配置 main.js import uView from /uni_modules/uview-ui Vue.use(uView)uni.scss import /uni_modules/uview-ui/theme.scss;App.vue <style lang"scss&qu…