Dubbo之Wrapper源码解析

功能概述

  • 为了减少反射的调用,Dubbo会为每个服务提供者的实现生成一个Wrapper类,通过Wrapper类去调用真正的接口实现类。

功能分析

核心类Wrapper分析

主要成员变量分析

private static final Map<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap<Class<?>, Wrapper>(); //class wrapper map:类与Wrapper的缓存,当需要执行调用时,根据Class即可找到Wrapper,然后通过Wrapper调用目标对象中方法,减少反射调用
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String[] OBJECT_METHODS = new String[] {"getClass", "hashCode", "toString", "equals"};
private static final Wrapper OBJECT_WRAPPER = new Wrapper() {...代码省略...
}//Object对应的封装类(匿名内部类)

主要成员方法分析

获取Wrapper封装类

public static Wrapper getWrapper(Class<?> c) { //获取Wrapper的实例(先从缓存中获取,若没有则对应创建)while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.{c = c.getSuperclass(); //不能封装动态类,动态类取它的父类进行封装}if (c == Object.class) { //Object 返回默认的对象封装类return OBJECT_WRAPPER;}return WRAPPER_MAP.computeIfAbsent(c, key -> makeWrapper(key)); //构建封装类,并设置到缓存中,key的值与c相同
}

创建封装类

private static Wrapper makeWrapper(Class<?> c) { //为指定class构建Wrapper封装类的实例if (c.isPrimitive()) { //基本类型不能创建封装类throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);}String name = c.getName(); //被封装的类的全限定名,如:org.apache.dubbo.demo.GreetingServiceClassLoader cl = ClassUtils.getClassLoader(c); //获取类加载器// 拼接类代码对应的字符串 (对应Wrapper类中的抽象方法)StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ "); //构建当前类中的setPropertyValue()抽象方法StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); //将setPropertyValue方法中的Object强制转化为具体类型,如:(org.apache.dubbo.demo.GreetingService)$1c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); //将getPropertyValue方法中的Object强制转化为具体类型c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); //将invokeMethod方法中的Object强制转化为具体类型Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types> 属性名与属性类型的映射MapMap<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance> 方法对应的描述符与方法实例的映射MapList<String> mns = new ArrayList<>(); // method names. 方法名列表List<String> dmns = new ArrayList<>(); // declaring method names. 被封装的类或接口中,声明的方法名列表// get all public field.for (Field f : c.getFields()) { //处理被封装类的所有public字段String fn = f.getName(); //获取字段名称Class<?> ft = f.getType(); //获取字段类型,如 java.lang.Stringif (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) { //static、transient修饰的字段不处理(接口中的字段,都是public static final字段,所以不会处理,那这里处理就是对类封装时处理)continue;}c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }"); //通过setPropertyValue方法,为目标对象设置成员属性的值,如:if( $2.equals("employeeName") ){ w.employeeName=(java.lang.String)$3;c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }"); //通过getPropertyValue方法,获取目标对象的成员变量值,如:if( $2.equals("employeeName") ){ return ($w)w.employeeName; }pts.put(fn, ft); //设置成员属性名与属性类型的关系,如Map<"employeeName,"java.lang.String">}Method[] methods = c.getMethods();// get all public method.boolean hasMethod = hasMethods(methods); //处理被封装类的所有public方法(判断是否有非Object中的方法)if (hasMethod) { //拼接invokeMethod方法中的调用逻辑(把被封装的类或接口中的声明方法,依次拼接起来)c3.append(" try{");for (Method m : methods) { //对类中的方法依次封装处理(构造Wrapper中的invokeMethod方法,如org.apache.dubbo.demo.GreetingService中声明中的所有方法)//ignore Object's method.(忽略Object对象中的方法)if (m.getDeclaringClass() == Object.class) {continue;}String mn = m.getName();c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); //$2指当前类中的invokeMethod()的第二个参数(比较方法名称)int len = m.getParameterTypes().length;c3.append(" && ").append(" $3.length == ").append(len);// 比较方法参数个数(需要方法名称和参数个数都相等)boolean override = false; //判断同一个接口或类中是存在重载的方法for (Method m2 : methods) { //按方法名,判断是否重写if (m != m2 && m.getName().equals(m2.getName())) {override = true;break;}}if (override) { //若有重载的方法(只按方法名称不能匹配出方法,还得按参数类型进行匹配)if (len > 0) { //方法参数个数for (int l = 0; l < len; l++) { //c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"").append(m.getParameterTypes()[l].getName()).append("\")");}}}c3.append(" ) { "); //组装出判断条件,如:if( "hello".equals( $2 )  &&  $3.length == 1 &&  $3[0].getName().equals("org.apache.dubbo.demo.Fruit"))if (m.getReturnType() == Void.TYPE) { //返回类型为voidc3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");} else { //方法有返回类型(组装方法的返回类型,如:return ($w)w.hello((org.apache.dubbo.demo.Fruit)$4[0] )c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");}c3.append(" }");mns.add(mn); //加入到方法名列表if (m.getDeclaringClass() == c) {dmns.add(mn); //被封装的类或接口中声明的方法}ms.put(ReflectUtils.getDesc(m), m); //将方法描述符与方法实例缓存起来}c3.append(" } catch(Throwable e) { ");c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");c3.append(" }");}c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); //若没有找到方法,则抛出“未找到方法”的异常// deal with get/set method.(处理set/get方法,非规范的方法就不会处理了)Matcher matcher;for (Map.Entry<String, Method> entry : ms.entrySet()) {String md = entry.getKey(); //暴露接口中的方法描述信息,如hello(Lorg/apache/dubbo/demo/FruitEnum;)Ljava/lang/String;Method method = entry.getValue();if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { //判断是否匹配get方法对应的描述信息(描述信息可以确定唯一的方法)String pn = propertyName(matcher.group(1));c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");pts.put(pn, method.getReturnType());} else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { //匹配is、has、can方法String pn = propertyName(matcher.group(1));c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");pts.put(pn, method.getReturnType());} else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { //匹配set方法Class<?> pt = method.getParameterTypes()[0];String pn = propertyName(matcher.group(1));c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }"); //拼接的内容如:" if( $2.equals("msg") ){ w.setMsg((java.lang.String)$3); return;"pts.put(pn, pt); //设置属性名与属性Class的映射}}c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");// make class(构建Class对象)long id = WRAPPER_CLASS_COUNTER.getAndIncrement();ClassGenerator cc = ClassGenerator.newInstance(cl); //使用ClassGenerator类生成器来生成Wrapper的Classcc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id); //org.apache.dubbo.common.bytecode.Wrapper0,判断类是否是public,然后进行类名拼接cc.setSuperClass(Wrapper.class); //将Wrapper指定为父类,创建其封装类cc.addDefaultConstructor(); //添加默认构造函数cc.addField("public static String[] pns;"); // property name array.cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.cc.addField("public static String[] mns;"); // all method name array.cc.addField("public static String[] dmns;"); // declared method name array.for (int i = 0, len = ms.size(); i < len; i++) {cc.addField("public static Class[] mts" + i + ";");}cc.addMethod("public String[] getPropertyNames(){ return pns; }");cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");cc.addMethod("public String[] getMethodNames(){ return mns; }");cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");cc.addMethod(c1.toString()); //处理setPropertyValue()方法cc.addMethod(c2.toString()); //处理getPropertyValue()方法cc.addMethod(c3.toString()); //处理invokeMethod()方法try {Class<?> wc = cc.toClass(); //将CtClass转换为Class// setup static field.(设置静态字段值)wc.getField("pts").set(null, pts);wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));wc.getField("mns").set(null, mns.toArray(new String[0]));wc.getField("dmns").set(null, dmns.toArray(new String[0]));int ix = 0;for (Method m : ms.values()) { //遍历方法参数列表wc.getField("mts" + ix++).set(null, m.getParameterTypes());}return (Wrapper) wc.newInstance(); //使用Class对象创建实例,并强转为Wrapper类型} catch (RuntimeException e) {throw e;} catch (Throwable e) {throw new RuntimeException(e.getMessage(), e);} finally {cc.release();ms.clear();mns.clear();dmns.clear();}
}

关联类ClassGenerator分析

主要成员变量分析

private static final AtomicLong CLASS_NAME_COUNTER = new AtomicLong(0); //未指定类名时,默认产生类名,用到的下标
private static final String SIMPLE_NAME_TAG = "<init>";
private static final Map<ClassLoader, ClassPool> POOL_MAP = new ConcurrentHashMap<ClassLoader, ClassPool>(); //ClassLoader - ClassPool(类加载器与javassist中的类池对应缓存)
private ClassPool mPool; //javassist中的类池
private CtClass mCtc;   //javassist中的编译时类
private String mClassName;  //动态生成的类名
private String mSuperClass; //父类对应的名称
private Set<String> mInterfaces; //存放类实现的接口列表
private List<String> mFields; //存放字段对应的代码片段,如ccp.addField("public static java.lang.reflect.Method[] methods;");
private List<String> mConstructors; //存放构造函数对应的代码片段
private List<String> mMethods; //存放方法对应的代码片段
private Map<String, Method> mCopyMethods; // <method desc,method instance>  方法描述符与方法实例的映射
private Map<String, Constructor<?>> mCopyConstructors; // <constructor desc,constructor instance> 方法描述符与构造实例的映射
private boolean mDefaultConstructor = false; //是否使用默认构造函数

主要成员方法分析

动态创建Class对象

public Class<?> toClass(ClassLoader loader, ProtectionDomain pd) { //创建Class对象(将当前维护的Class信息,创建Class对象)if (mCtc != null) {mCtc.detach(); //detach:分离, 从ClassPool中移除CtClass}// 基于当前类维护的数据,进行逻辑处理long id = CLASS_NAME_COUNTER.getAndIncrement();try {CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass); // 从类池ClassPool中获取类名mSuperClass对应的CtClassif (mClassName == null) { //若没显示设置类名时,自动生成对应的类名,如 org.apache.dubbo.common.bytecode.ClassGenerator0mClassName = (mSuperClass == null || javassist.Modifier.isPublic(ctcs.getModifiers()) // ||都优先级大于?: 且结合性是从左到右的? ClassGenerator.class.getName() : mSuperClass + "$sc") + id; //构建类名:取ClassGenerator名称或mSuperClass名称}mCtc = mPool.makeClass(mClassName); //根据类名className创建对应的CtClass对象if (mSuperClass != null) { // 设置继承的类(java是单继承,所以只会设置一个父类)mCtc.setSuperclass(ctcs);}mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag. (每一个动态类都实现了DC接口)if (mInterfaces != null) { // 设置实现的接口for (String cl : mInterfaces) {mCtc.addInterface(mPool.get(cl));}}if (mFields != null) { // 设置字段for (String code : mFields) {mCtc.addField(CtField.make(code, mCtc)); // 将字段对应的字符串,转换为CtField}}if (mMethods != null) { // 设置方法for (String code : mMethods) {if (code.charAt(0) == ':') {mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))),code.substring(1, code.indexOf('(')), mCtc, null));} else {mCtc.addMethod(CtNewMethod.make(code, mCtc)); // 将方法对应的字符串,转换为CtMethod}}}if (mDefaultConstructor) { // 设置默认的构造函数(无参的构造函数)mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));}if (mConstructors != null) { // 处理构造函数for (String code : mConstructors) {if (code.charAt(0) == ':') {mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null));} else {String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $.mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length - 1]), mCtc));}}}return mCtc.toClass(loader, pd); //使用CtClass转换到Class} catch (RuntimeException e) {throw e;} catch (NotFoundException e) {throw new RuntimeException(e.getMessage(), e);} catch (CannotCompileException e) {throw new RuntimeException(e.getMessage(), e);}
}
  • 代码解析:动态创建Class的流程:
    • 将设置的代码字符串,如继承的类、实现的接口、设置的方法、字段等转换javassist对应的数据模型,如CtMethod、CtFiled等
    • 然后按类或接口的组成进行组装,如设置继承的类、设置实现的接口、设置类中的方法和字段等
    • 使用javassist的CtClass.toClass()获取到动态生成的Class(类似Mybatis的动态SQL,按字符串动态组装,最终形成SQL)

问题点答疑

  • 每个暴露的接口,都有一个wrapper封装类吗?是怎么找到这个Wrapper类的?

    • 解答:在服务暴露时ServiceConfig#doExportUrlsFor1Protocol会Wrapper.getWrapper(interfaceClass).getMethodNames()为暴露的接口创建Wrapper类,并对应缓存起来。消费端ReferenceConfig#init也会在启动时,创建接口对应的Wrapper类,并对应缓存起来。会转换为具体的类进行执行,避免了反射使用
  • Wrapper能够获取类的封装类吗?还是说只能获取接口的封装类?

    • 解答:不管是接口还是类都可以创建其对应的封装类的

归纳总结

  • Wrapper用于“包裹”目标类,Wrapper是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建Wrapper子类的过程中,子类代码生成逻辑会对getWrapper方法传入的Class对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建Wrapper实例

  • ClassGenerator:Class生成器,内部对Javassist的数据模型进行抽象,对外提供使用代码片段的方式创建Class

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

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

相关文章

Kotlin的数组

在 Kotlin 中&#xff0c;数组是一种固定大小的有序集合&#xff0c;可以存储相同类型的元素。Kotlin 提供了两种类型的数组&#xff1a;原生数组和数组类。以下是 Kotlin 中数组的详细使用方法&#xff1a; 1.创建数组 Kotlin 支持使用 arrayOf() 函数来创建数组&#xff1a;…

ubuntu 编译安装nginx及安装nginx_upstream_check_module模块

如果有帮助到你&#xff0c;麻烦点个赞呗&#xff5e; 一、下载安装包 # 下载nginx_upstream_check_module模块 wget https://codeload.github.com/yaoweibin/nginx_upstream_check_module/zip/master# 解压 unzip master# 下载nginx 1.21.6 wget https://github.com/nginx/…

【C++奇遇记】构造函数 | 初始化列表

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集 数据库专栏 初阶数据结构 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如…

操作系统练习:在Linux上创建进程,及查看进程状态

说明 进程在执行过程中可以创建多个新的进程。创建进程称为“父进程”&#xff0c;新的进程称为“子进程”。每个新的进程可以再创建其他进程&#xff0c;从而形成进程树。 每个进程都有一个唯一的进程标识符&#xff08;process identifier&#xff0c;pid&#xff09;。在L…

树结构转List

使用LinkedList效率更高 1、单个顶级节点 public static List<CmsStudentOutline> getTreeList(CmsStudentOutline root) {List<CmsStudentOutline> list new ArrayList<>();Queue<CmsStudentOutline> queue new LinkedList<>();if (root nu…

【大数据】Hive 表中插入多条数据

Hive 表中插入多条数据 在 Hive 中&#xff0c;我们可以使用 INSERT INTO 语句向表中插入数据。当我们需要插入多条数据时&#xff0c;有多种方式可以实现。本文将介绍如何在 Hive 表中插入多条数据&#xff0c;并提供相应的代码示例。 1.使用单个 INSERT INTO 语句插入多条数…

Java之接口

作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; Java之接口 接口的概念语法规则接口特性接口使用案…

数据结构<树和二叉树>顺序表存储二叉树实现堆排

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

Redis的数据持久化

前言 本文主要介绍Redis的三种持久化方式、AOF持久化策略等 什么是持久化 持久化是指将数据在内存中的状态保存到非易失性介质&#xff08;如硬盘、固态硬盘等&#xff09;上的过程。在计算机中&#xff0c;内存中的数据属于易失性数据&#xff0c;一旦断电或重启系统&#…

java实现视频抽帧以及获取其他视频信息

java实现视频抽帧以及获取其他视频信息 1.在pom.xml文件中导入jar包 <dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.4.3</version> </dependency><dependency><groupId&…

stable diffusion基础

整合包下载&#xff1a;秋叶大佬 【AI绘画8月最新】Stable Diffusion整合包v4.2发布&#xff01; 参照&#xff1a;基础04】目前全网最贴心的Lora基础知识教程&#xff01; VAE 作用&#xff1a;滤镜微调 VAE下载地址&#xff1a;C站&#xff08;https://civitai.com/models…

漏洞指呗-VluFocus靶场专栏-番外篇

漏洞指呗-VluFocus靶场专栏-番外篇奇技淫巧 &#x1f338;struts2漏洞扫描工具&#x1f338;step1 修改ip和端口step2 验证漏洞是否存在step3 执行cmd命令&#xff0c;获取flag &#x1f338;Goby插件工具headshot&#x1f338;step1 输入ip和端口 检测step2 cmd 输入指令 &…

vactor中迭代器失效问题

目录 什么是迭代器失效导致迭代器失效的操作VS和g环境下对与迭代器失效的态度 什么是迭代器失效 迭代器的底层其实就是一个指针&#xff0c;或者对指针进行了封装 vector的迭代器就是一个指针T* 一个迭代器指向某一个空间&#xff0c;此时这块空间被释放了&#xff0c;这个迭…

【SA8295P 源码分析】35 - QNX侧 Marvell 88Q5152 Phy_Switch 导通实录(硬核)

【SA8295P 源码分析】35 - QNX侧 Marvell 88Q5152 Phy_Switch 导通实录(硬核) 一、硬件原理分析二、88Q5152 芯片读写时序分析2.1 Clause 22 读、写寄存器配置(配置 Port 5 采用C22条款)2.1.1 88Q5152 P5 端口配置2.2 Clause 45 读寄存器时序(配置 Port 1、Port 2 采用C45…

HTML <source> 标签

实例 拥有两份源文件的音频播放器。浏览器应该选择它所支持的文件(如果有的话): <audio controls><source src="horse.ogg" type="audio/ogg"><source src="horse.mp3" type="audio/mpeg">Your browser does n…

Keepalived配置文件详解+主从IP接管实战

文章目录 Keepalived配置文件全局配置VRRP实例主备配置对比 LVS配置Keepalived一主一从IP接管实战MASTER配置文件BACKUP配置文件检查效果测试 Keepalived配置文件 全局配置 #以下是官方给的默认配置文件(示例配置) #以#或&#xff01;为注释符 global_defs {notification_ema…

Appium Desktop安装

【提示&#xff1a;官方已不再维护&#xff0c;建议命令行方式安装&#xff0c;但可以学习了解一下】 Appium Desktop是一款适用于Mac、Windows和Linux的应用程序&#xff0c;它以漂亮灵活的UI为您提供Appium自动化服务器的强大功能。它基本上是Appium Server的图形界面。您可…

【华为认证数通高级证书实验-分享篇2】

实验拓扑 注&#xff1a;代码块为各交换机路由器中的配置命令 配置拓扑文件 实验要求 实现全网通 实验配置 SW3 [SW3]v b 10 20 [SW3]int e0/0/1 [SW3-Ethernet0/0/1]po link-t a [SW3-Ethernet0/0/1]po de v 10 [SW3-Ethernet0/0/1]int e0/0/2 [SW3-Ethernet0/0/2]po li…

ES6自用笔记

目录 原型链 引用类型&#xff1a;__proto__(隐式原型)属性&#xff0c;属性值是对象函数&#xff1a;prototype(原型)属性&#xff0c;属性值是对象 相关方法 person.prototype.isPrototypeOf(stu) Object.getPrototypeOf(Object)替换已不推荐的Object._ _ proto _ _ Ob…

【hive】hive中row_number() rank() dense_rank()的用法

hive中row_number() rank() dense_rank()的用法 一、函数说明 主要是配合over()窗口函数来使用的&#xff0c;通过over(partition by order by )来反映统计值的记录。 rank() over()是跳跃排序&#xff0c;有两个第二名时接下来就是第四名(同样是在各个分组内)dense_rank() …