• 你不知道的java-佛怒轮回


      在开发中,我是一个极懒的人, 而且特别不喜欢写无用且烦锁的代码,但java的语言特性又使得我不得不写这些无用的代码,以前我研究过一段时间的python代码,发现python的语法确实简单得令人发指,有一段时间,如果有机会去开发python代码,都想放弃java代码开发项目的冲动 ,但现在是残酷的,因为生活所迫,不得不继续用java代码来谋求生活,就像有人据说中,生活就像被强间一样,既然不能改变,那就好好的享受吧,但是我们也不能一味的忍受,总要改变点什么 。
      话不多说,先来看一个例子。

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = 3;
        System.out.println(response(a, b, c));
    }
    
    public static Map response(Object... args) {
        Map map = new HashMap<>();
        map.put("a", args[0]);
        map.put("b", args[1]);
        map.put("c", args[2]);
        return map;
    }
    

      上面这个例子的意图是想调用response()方法,将传入response()方法作作为一个map返回。 但我不想传入map的key,而在response()方法中自动的获取传入参数名,如 response(a, b, c), 想在response()方法中获取a的变量名"a",b的变量名"b",c的变量名"c",如果在另外一个地方同样调用response(d),则会获取到d的变量名"d", 这能做到不? 按java语言的特点,是获取不到的,因为在编译时,为了节省内存空间,在编译时,会将复杂的变量名简单化,因此无从得到具体的变量名。
      来看一下真实的应用场景 。
    在这里插入图片描述
      Spring Boot中,我们写了3个测试方法。 test0(),test01(),test02(),对于test0()方法,是我们经常遇到的情况,我们要返回一个Map 对象,但不得不写下面几行鸡肋的代码,索然无味,弃之不行的代码。

    Map map = new HashMap();
    map.put("a",1);
    map.put("b",2);
    map.put("dataDto",dataDto);
    

      如果能像test01()和test02()方法一样,只需要将变量丢进result方法中,就可以得到一个Map对象,或得到一个具体的对象,那该多好啊。
      如创建一个对象

    @Data
    public class DataDto {
        private Integer a;
        private Integer b;
        private Integer c;
    }
    

      调用如下方法。
    在这里插入图片描述
      就可以得到DataDto对象或修改参数的顺序,也能得到正确的DataDto对象。
    在这里插入图片描述
      如果能实现这一点,那该多好啊。 为什么我想这么做呢?因为python中有一种元组的语法,如下所示 。
    在这里插入图片描述
      python中的这种语法是不是很赞,但是想在java中实现这种语法,基本上不可能,之前在《Java编程思想》提到过一种语法,也是java的元组 。 但这种语法最多能像我这样实现。
    在这里插入图片描述  上面语法的好处就是就是不需要返回多个参数时重复的创建对象,在Tuple中传入3个参数, 可以通过getFirst(),getSecond(),getThird()来获取传入参数的值,但这样抹除了原来参数的含义。 最后写代码的人都不知道first,second,third是什么东西了,如
    在这里插入图片描述
      如上图所示 ,

    DataDto dto1 = R.result(a);
    DataDto dto2 = R.result(b, a);
    DataDto dto3 = R.result(c, b, a);
    

      a,b,c 传入参数没有任何顺序可言,但结果输出DataDto对象的属性是正确的,这样做的好处,一方面我们写代码的风格更加自由,另外,当上面3种情况,我们不需要在DataDto中写3个构造方法,分别适应传入的参数。 只需要传入的参数名和DataDto的属性字段名对应即可,如果我们不想写构造函数,那我们至少也要调用setA() ,setB() ,setC()方法,因此上面这种写法至少了少掉了1~3行鸡肋代码,从输出结果上来看,竟然实现了此功能 。

    {"a":1}
    {"a":1,"b":2}
    {"a":1,"b":2,"c":3}
    

      来看看http://localhost:8080/test01执行效果
    在这里插入图片描述
      再来看看http://localhost:8080/test02执行效果
    在这里插入图片描述
      再来看看http://localhost:8080/test03的执行结果

    在这里插入图片描述
      是不是我们之前提出的疑问,在例子中都得到了解决,感觉不可能啊,在方法的内部根本拿不到调用者的方法参数名称 ,那这么神奇的代码,到底如何实现的呢?

      先来简述一下,到底如何实现,通过常规操作是不可能实现了, 因此需要修改字节码的方式来实现, 这种技术很多都用到,如cglib代理,我们只需要声明变量时,将变量名和变量值构建一个Map加入到threadLocal中,当调用R.result()时,根据变量值取出变量名,再在R.result()方法内部获取使用R.result()所在方法的返回参数。
    在这里插入图片描述
      当然,目前只对返回参数是对象类型或Map类型做支持,当然如返回参数是基本数据类型或返回参数是字符串类型,或返回参数是List类型,这些可以根据实际的业务需要再去做扩展即可。 如果方法的返回参数是对象类型,则通过反射创建对象并实例化它,并通过反射为字段赋值,如果返回值是Map对象,则直接构建map对象返回即可,是不是原理也很简单,接下来看对象修改后的字节码 。 看test03()方法 。
    在这里插入图片描述

    public class LocalvariablesNamesEnhancer {
    
            static ThreadLocal>> localVariables = new ThreadLocal>>();
            public static void checkEmpty() {
                if (localVariables.get() != null && localVariables.get().size() != 0) {
                    logger.error("LocalVariablesNamesTracer.checkEmpty, constraint violated (%s)", localVariables.get().size());
                }
            }
            
            public static void clear() {
                if (localVariables.get() != null) {
                    localVariables.set(null);
                }
            }
            public static void enter() {
                if (localVariables.get() == null) {
                    localVariables.set(new Stack>());
                }
                localVariables.get().push(new HashMap());
            }
    
            public static void exit() {
                if (localVariables.get().isEmpty()) {
                    return;
                }
                localVariables.get().pop().clear();
            }
    
            public static Map locals() {
                if (localVariables.get() != null && !localVariables.get().empty()) {
                    return localVariables.get().peek();
                }
                return new HashMap();
            }
    
            public static void addVariable(String name, Object o) {
                locals().put(name, o);
            }
    
            public static void addVariable(String name, boolean b) {
                locals().put(name, b);
            }
    
            public static void addVariable(String name, char c) {
                locals().put(name, c);
            }
    
            public static void addVariable(String name, byte b) {
                locals().put(name, b);
            }
    
            public static void addVariable(String name, double d) {
                locals().put(name, d);
            }
    
            public static void addVariable(String name, float f) {
                locals().put(name, f);
            }
    
            public static void addVariable(String name, int i) {
                locals().put(name, i);
            }
    
            public static void addVariable(String name, long l) {
                locals().put(name, l);
            }
    
            public static void addVariable(String name, short s) {
                locals().put(name, s);
            }
    
            public static Map getLocalVariables() {
                return locals();
            }   
        }
    }
    

      在生成字节码中用到了下面3个方法

    LocalVariablesNamesTracer.enter();
    LocalVariablesNamesTracer.addVariable("a", a);
    LocalVariablesNamesTracer.exit();
    

      每次enter方法时会向localVariables栈中添加一个Map对象,每一次调用exit()方法时,会将栈顶的Map对象移除掉,而调用addVariable()方法时,会将变量名和变量值存储于栈顶的map中。
    在这里插入图片描述
      看修改后的字节码
    在这里插入图片描述

      实现原理是什么呢?先看下图。
    在这里插入图片描述

      通过上图分析,我相信大家对执行过程有了一定的了然,当然,再来看result()的实现

    public class R {
        public static Map getResParam(Object... args) {
        	// 构建map
            Map result = new HashMap();
            Object[] var6 = args;
            int var5 = args.length;
            for (int var4 = 0; var4 < var5; ++var4) {
                Object o = var6[var4];
                List names = LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.getAllLocalVariableNames(o);
                Iterator var9 = names.iterator();
                while (var9.hasNext()) {
                    String name = (String) var9.next();
                    result.put(name, o);
                }
            }
            return result;
        }
    	
    	public static List getAllLocalVariableNames(Object o) {
    	    List allNames = new ArrayList();
    	    // 将R.result() 传入参数值与栈顶中的变量值比对,
    	    // 如果相等,则返回对象名
    	    for (String variable : getLocalVariables().keySet()) {
    	        if (getLocalVariables().get(variable) == o) {
    	            allNames.add(variable);
    	        }
    	        if (o != null && o instanceof Number && o.equals(getLocalVariables().get(variable))) {
    	            allNames.add(variable);
    	        }
    	    }
    	    return allNames;
    	}
    
    
        public static  T result(Object... args) {
            Object result = null;
            try {
                Throwable throwable = new Throwable();
                StackTraceElement[] stackTraceElements = throwable.getStackTrace();
                // 获取到调用result() 代码所在方法的类名,方法名,行号
                String className = stackTraceElements[1].getClassName();
                String methodName = stackTraceElements[1].getMethodName();
                int line = stackTraceElements[1].getLineNumber();
                // 通过参数值获取参数名和参数值的map对象
                Map resp = getResParam(args);
                // 通过字节码读取方法的返回值
                String returnType = ClassMethodUtils.getMethodReturnType(className, methodName, line);
                // 如果是map,则返回map对象
                if("Ljava/util/Map;".equals(returnType)){
                    return (T)resp;
                }
                // 获取得 returnType =  Lcom/example/thread_no_known/dto;
                // 如果需要反射调用,需要删除到开头的L和字符串结尾的;
                // 同时将字符串中 / 替换为 . 
                returnType = returnType.substring(1);
                returnType = returnType.substring(0, returnType.length() - 1);
    		
                returnType = returnType.replaceAll("/", ".");
                // 反射实例化对象
                Class clazz = Class.forName(returnType);
                result = clazz.newInstance();
                Field fields[] = clazz.getDeclaredFields();
                if (fields != null && fields.length > 0) {
                	// 为属性赋值
                    for (Field field : fields) {
                        if(field.getName().startsWith("$")){
                            continue;
                        }
                        field.setAccessible(true);
                        field.set(result, resp.get(field.getName()));
                    }
                }
                return (T) result;
            } catch (Exception e) {
                log.error("异常",e);
            }
            return null;
        }
    }
    

      对于result()方法,有两个比较重要的块,第一,从栈顶中获取Map,根据值获取key,并再封装成map返回即可。但有小伙伴肯定会觉得费解。
    在这里插入图片描述
      为什么会通过值获取到多个key呢? 因为这里存在一种这样的情况 ,如下。
    在这里插入图片描述
      从上面例子中,人只塞入两个参数,但map返回值却是3个,这难道有bug不?
      反过来想,假如在getAllLocalVariableNames()方法中,取到一个变量值也传入值相等即返回。
    在这里插入图片描述
      将会出现你传入了a,b,c 3个参数,可能返回值只有a,c两个参数。
    在这里插入图片描述
      因此这就明显有逻辑上的错误了,因此,在这里使用宁可错杀,也不能错过的方式,只要值与之前的相等,则加入到返回的map中,这一点需要注意 。

      像这样写,就明显的语言上的错误了。 我只要得到一个对象
    在这里插入图片描述
    DataDto,但对象DataDto的b属性我不想赋值,但返回结果却是b有值,明显语义不对嘛,但小伙伴也不用这么想,你想想,你都不用b 这个变量,你定义在这里做什么,而且还与其他变量值一样,所以这可能是这种运用产生的后遗证。 这点还需要注意 。

      接下来看通过类名,方法名,行号获取方法的返回值。

    Throwable throwable = new Throwable();
    StackTraceElement[] stackTraceElements = throwable.getStackTrace();
    String className = stackTraceElements[1].getClassName();
    String methodName = stackTraceElements[1].getMethodName();
    int line = stackTraceElements[1].getLineNumber();
    
    

      通过Throwable,我们只能获取到类名,方法名,行号这些信息,如果想拿到方法的返回值,显然拿不到,那怎么办呢?

    public static String getMethodReturnType(String className,String methodName ,int line) {
        try {
            String resourcePath = ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
            ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
            // 获取class的输入流
            InputStream is = ClassParser.getInputStream(classLoader, resourcePath);
            // 解析class方法
            ClassFile classFile = ClassFile.Parse(ClassReaderUtils.readClass(is));
            // 遍历类中所有的方法
            for (MemberInfo memberInfo : classFile.getMethods()) {
                MethodDescriptorParser parser = new MethodDescriptorParser();
                // 解析方法,得到方法描述,如请求参数,返回参数,方法名等
                MethodDescriptor parsedDescriptor = parser.parseMethodDescriptor(memberInfo.Descriptor());
                StringBuilder parameterTypeStr = new StringBuilder();
                String byteMethodName = memberInfo.Name();          // 获取字节码中方法名
                if (!byteMethodName.equals(methodName)) {           //如果方法名不相等,则重新查找
                    continue;
                }
                for (String parameterType : parsedDescriptor.parameterTypes) {
                    String d = ClassNameHelper.toStandClassName(parameterType);
                    parameterTypeStr.append(d);
                }
                AttributeInfo attributeInfos[] = memberInfo.getAttributes();
                // 获取Code下的第一个LineNumberTable
                for (AttributeInfo codeAttribute : attributeInfos) {
                    if (codeAttribute instanceof CodeAttribute) {
                        AttributeInfo codeAttributeInfos[] = ((CodeAttribute) codeAttribute).getAttributes();
                        for (AttributeInfo attributeInfo : codeAttributeInfos) {
                        	// 获取行号表属性
                            if (attributeInfo instanceof LineNumberTableAttribute) {
                                LineNumberTableEntry[] lineNumberTableEntries = ((LineNumberTableAttribute) attributeInfo).getLineNumberTables();
                                LineNumberTableEntry firstLine = lineNumberTableEntries[0];
                                LineNumberTableEntry lastLine = lineNumberTableEntries[lineNumberTableEntries.length - 1];
                                // 如果调用栈中的行号在字节码中方法的行号之间,则此方法就是我们要找的方法
                                // 获取此方法的返回值即可
                                if (line >= firstLine.getLineNumber().Value() && line <= lastLine.getLineNumber().Value()) {
                                    return parsedDescriptor.returnType;
                                }
                            }
                            break;
                        }
                        break;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    

      上面代码来源于另外一个jar包,感兴趣可去下载看看。
    https://github.com/quyixiao/classparser.git 这个包的主要用途就是解析class字节码,得到类的信息如下

    ClassFile {
        u4             magic;
        u2             minor_version;
        u2             major_version;
        u2             constant_pool_count;
        cp_info        constant_pool[constant_pool_count-1];
        u2             access_flags;
        u2             this_class;
        u2             super_class;
        u2             interfaces_count;
        u2             interfaces[interfaces_count];
        u2             fields_count;
        field_info     fields[fields_count];
        u2             methods_count;
        method_info    methods[methods_count];
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

      上面代码获取方法的返回值,可能大家理解起来还是费解的,如
    在这里插入图片描述
      以test5()方法调用R.result()为例, 在public static T result(Object… args) {} 方法内部能拿到调用result()方法的类名为com.example.thread_no_known.contoller.TestController,方法名为test5() , 行号为126行,在解析字节码后,遍历类的所有方法,126 行刚好是test5()方法内部,因此test5()就是我们要找的方法,再通过字节码解析得到test5()方法的返回值即可,得到返回值,再通过反射创建对象,为对象属性赋值,就轻而易举了。
      我相信大家看到这里觉得是那么回事了,但有小伙伴肯定会想。
    在这里插入图片描述
      如上图所示,你又是如何修改字节码的呢? 这涉及到另外一个包 。 代码也上传到github上了。
    https://github.com/quyixiao/transmit-variable-local.git

      mvn clean install 打包这个项目 。 在jar包启动时,作为启动参数添加进去,如下图所示 。
    在这里插入图片描述
      这个包做了哪些事情呢?在类加载时修改类字节码 。
    在这里插入图片描述

      我们着生来分析这个类。

    public byte[] doTransform(String className, byte[] classFileBuffer, ClassLoader loader) throws IOException, NotFoundException, CannotCompileException {
    	// 如果不符合条件,则对字节码不做修改
        if (Utils.isNotNull(className) && Utils.validate(className)) {
    
            System.out.println("real doTransform class name :" + className);
            // 这里使用了javassist ,获取CtClass对象
            final CtClass ctClass = Utils.getCtClass(classFileBuffer, loader);
    
            String temp = className.replaceAll("\\.","/");
            File file = new File("/Users/quyixiao/github/Thread_NO_Known/origin" + getdir(className));
            if(!file.exists()){
                file.mkdirs();
            }
    
            file = new File("/Users/quyixiao/github/Thread_NO_Known/out" + getdir(className));
            if(!file.exists()){
                file.mkdirs();
            }
    
    
            for (CtMethod method : ctClass.getDeclaredMethods()) {
            	// 如果类名中包含$ ,则表示代理方法,而不是开发者自己写的,对于这种方法,不需要修改字节码
                if (method.getName().contains("$")) {
                    // Generated method, skip
                    continue;
                }
                
                // Signatures names
                // 获取字节码中所有的Code属性
                CodeAttribute codeAttribute = (CodeAttribute) method.getMethodInfo().getAttribute("Code");
                if (codeAttribute == null || javassist.Modifier.isAbstract(method.getModifiers())) {
                    continue;
                }
                // 获取方法的本地变量表
                LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable");
                List> parameterNames = new ArrayList>();
                if (localVariableAttribute == null) {
                    if (method.getParameterTypes().length > 0)
                        continue;
                } else {
                    if (localVariableAttribute.tableLength() < method.getParameterTypes().length + (javassist.Modifier.isStatic(method.getModifiers()) ? 0 : 1)) {
                        logger.warn("weird: skipping method %s %s as its number of local variables is incorrect (lv=%s || lv.length=%s || params.length=%s || (isStatic? %s)", method.getReturnType().getName(), method.getLongName(), localVariableAttribute, localVariableAttribute != null ? localVariableAttribute.tableLength() : -1, method.getParameterTypes().length, javassist.Modifier.isStatic(method.getModifiers()));
                    }
                    for (int i = 0; i < localVariableAttribute.tableLength(); i++) {
    
                        if (!localVariableAttribute.variableName(i).equals("__stackRecorder")) {
                            parameterNames.add(new T2(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i)));
                        }
                    }
    
                    Collections.sort(parameterNames, new Comparator>() {
                        public int compare(T2 o1, T2 o2) {
                            return o1._1.compareTo(o2._1);
                        }
                    });
    
                }
                List names = new ArrayList();
                // 下面这一块主要是
                for (int i = 0; i < method.getParameterTypes().length + (javassist.Modifier.isStatic(method.getModifiers()) ? 0 : 1); i++) {
                    if (localVariableAttribute == null) {
                        continue;
                    }
                    try {
                        String name = parameterNames.get(i)._2;
                        // 排队掉this本地变量
                        if (!name.equals("this")) {
                            names.add(name);
                        }
                    } catch (Exception e) {
                        System.out.println("exception 97");
                    }
                }
                StringBuilder iv = new StringBuilder();
                if (names.isEmpty()) {
                    iv.append("new String[0];");
                } else {
                    iv.append("new String[] {");
                    for (Iterator i = names.iterator(); i.hasNext(); ) {
                        iv.append("\"");
                        String aliasedName = i.next();
                        if (aliasedName.contains("$")) {
                            aliasedName = aliasedName.substring(0, aliasedName.indexOf("$"));
                        }
                        iv.append(aliasedName);
                        iv.append("\"");
                        if (i.hasNext()) {
                            iv.append(",");
                        }
                    }
                    iv.append("};");
                }
    
                String sigField = "$" + method.getName() + LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.computeMethodHash(method.getParameterTypes());
                try { // #1198
                    ctClass.getDeclaredField(sigField);
                } catch (NotFoundException nfe) {
                    CtField signature = CtField.make("public static String[] " + sigField + " = " + iv.toString(), ctClass);
                    ctClass.addField(signature);
                }
    
                if (localVariableAttribute == null) {
                    continue;
                }
                // 上面这块代码主要是 如图10 所示 
                // public static String[] $test695092022 = new String[]{"param1", "param2"}; 属性生成 
                
                
                // OK.
                // Here after each local variable creation instruction,
                // we insert a call to com.linzi.utils.LocalVariables.addVariable('var', var)
                // without breaking everything...
                for (int i = 0; i < localVariableAttribute.tableLength(); i++) {
    
                    // name of the local variable
                    String name = localVariableAttribute.getConstPool().getUtf8Info(localVariableAttribute.nameIndex(i));
                    System.out.println("变量名="+name);
    
                    // Normalize the variable name
                    // For several reasons, both variables name and name$1 will be aliased to name
                    String aliasedName = name;
                    if (aliasedName.contains("$")) {
                        aliasedName = aliasedName.substring(0, aliasedName.indexOf("$"));
                    }
                    
                    if (name.equals("this")) {
                        continue;
                    }
    
                /* DEBUG
                IO.write(ctClass.toBytecode(), new File("/tmp/lv_"+applicationClass.name+".class"));
                ctClass.defrost();
                 */
    
                    try {
    
                        // The instruction at which this local variable has been created
                        Integer pc = localVariableAttribute.startPc(i);
    
                        // Move to the next instruction (insertionPc)
                        CodeIterator codeIterator = codeAttribute.iterator();
                        codeIterator.move(pc);
                        pc = codeIterator.next();
    
                        Bytecode b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), name, localVariableAttribute.index(i));
                        codeIterator.insert(pc, b.get());
                        codeAttribute.setMaxStack(codeAttribute.computeMaxStack());
    
                        // Bon chaque instruction de cette méthode
                        while (codeIterator.hasNext()) {
                            int index = codeIterator.next();
                            int op = codeIterator.byteAt(index);
    
                            String option = Factory.NewInstruction(op);
                            System.out.println("op =" + op + ",option="+option);
                            // DEBUG
                            // printOp(op);
    
                            int varNumber = -1;
                            // The variable changes
                            if (storeByCode.containsKey(op)) {
                                varNumber = storeByCode.get(op);
                                if (varNumber == -2) {
                                    varNumber = codeIterator.byteAt(index + 1);
                                }
                            }
    
                            // Si c'est un store de la variable en cours d'examination
                            // et que c'est dans la frame d'utilisation de cette variable on trace l'affectation.
                            // (en fait la frame commence à localVariableAttribute.startPc(i)-1 qui est la première affectation
                            //  mais aussi l'initialisation de la variable qui est deja tracé plus haut, donc on commence à localVariableAttribute.startPc(i))
                            if (varNumber == localVariableAttribute.index(i) && index < localVariableAttribute.startPc(i) + localVariableAttribute.codeLength(i)) {
                                b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), aliasedName, varNumber);
                                codeIterator.insertEx(b.get());
                                codeAttribute.setMaxStack(codeAttribute.computeMaxStack());
                            }
                        }
                    } catch (Exception e) {
                        // Well probably a compiled optimizer (I hope so)
                    }
    
                }
    
                // init variable tracer
                method.insertBefore("com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();");
                method.insertAfter("com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit();", true);
    
            }
            // Done.
            byte [] result = ctClass.toBytecode();
            IO.write(result, new File("/Users/quyixiao/github/Thread_NO_Known/out/"+temp+".class"));
            ctClass.defrost();
    
            return result;
        }
        return null;
    }
    
    
    private final static Map storeByCode = new HashMap();
    
    /**
     * Useful instructions
     */
    static {
        storeByCode.put(CodeIterator.ASTORE_0, 0);
        storeByCode.put(CodeIterator.ASTORE_1, 1);
        storeByCode.put(CodeIterator.ASTORE_2, 2);
        storeByCode.put(CodeIterator.ASTORE_3, 3);
        storeByCode.put(CodeIterator.ASTORE, -2);
    
        storeByCode.put(CodeIterator.ISTORE_0, 0);
        storeByCode.put(CodeIterator.ISTORE_1, 1);
        storeByCode.put(CodeIterator.ISTORE_2, 2);
        storeByCode.put(CodeIterator.ISTORE_3, 3);
        storeByCode.put(CodeIterator.ISTORE, -2);
        storeByCode.put(CodeIterator.IINC, -2);
    
        storeByCode.put(CodeIterator.LSTORE_0, 0);
        storeByCode.put(CodeIterator.LSTORE_1, 1);
        storeByCode.put(CodeIterator.LSTORE_2, 2);
        storeByCode.put(CodeIterator.LSTORE_3, 3);
        storeByCode.put(CodeIterator.LSTORE, -2);
    
        storeByCode.put(CodeIterator.FSTORE_0, 0);
        storeByCode.put(CodeIterator.FSTORE_1, 1);
        storeByCode.put(CodeIterator.FSTORE_2, 2);
        storeByCode.put(CodeIterator.FSTORE_3, 3);
        storeByCode.put(CodeIterator.FSTORE, -2);
    
        storeByCode.put(CodeIterator.DSTORE_0, 0);
        storeByCode.put(CodeIterator.DSTORE_1, 1);
        storeByCode.put(CodeIterator.DSTORE_2, 2);
        storeByCode.put(CodeIterator.DSTORE_3, 3);
        storeByCode.put(CodeIterator.DSTORE, -2);
    }
    

      重点看上面加粗代码

    private static Bytecode makeBytecodeForLVStore(CtMethod method, String sig, String name, int slot) {
        Bytecode b = new Bytecode(method.getMethodInfo().getConstPool());
        // 如果字符串在常量池中索引小等于255 ,用ldc 指令
        // 如果字符串在常量号中索引大于255,则用ldc_w指令 
        b.addLdc(name);
        // 如果以像Short,boolean ,char , byte 类型,都用整形表示,则用iload指令
        if ("I".equals(sig) || "B".equals(sig) || "C".equals(sig) || "S".equals(sig) || "Z".equals(sig))
            b.addIload(slot);
        else if ("F".equals(sig)) // 如果是F 开头,则用fload指令
            b.addFload(slot);
        else if ("J".equals(sig)) // 如果是J 开头,表示Long类型,则用lload指令
            b.addLload(slot);
        else if ("D".equals(sig)) // 如果是double类型,则使用dload指令
            b.addDload(slot);
        else	 // 如果是引用类型,则使用Aload指令
            b.addAload(slot);
    
        String localVarDescriptor = sig;
        // 如果非基本数据类型,则本地变量用Object来描述
        if (!"B".equals(sig) && !"C".equals(sig) && !"D".equals(sig) && !"F".equals(sig) &&
                !"I".equals(sig) && !"J".equals(sig) && !"S".equals(sig) && !"Z".equals(sig))
            localVarDescriptor = "Ljava/lang/Object;";
    
        b.addInvokestatic("com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer$LocalVariablesNamesTracer", "addVariable", "(Ljava/lang/String;" + localVarDescriptor + ")V");
        return b;
    }
    

      其实makeBytecodeForLVStore()方法写了那么多,就是加了LocalVariablesNamesTracer.addVariable(“a”, a);这一行代码 。

      最后在方法调用前加com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();方法。对应的代码如下。
      method.insertBefore(“com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();”);

      最后在方法调用后加com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit();

      method.insertAfter(“com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit();”, true);
    在这里插入图片描述
    test6()生成的字节码如下图所示
    如图10
    在这里插入图片描述
      以test6()方法为例,看修改前和修改后字节码做了哪些修改。
    在这里插入图片描述
      从图中可以看出,红色部分为新增加的字节码 。我们解读一下下面这几条指令

    • 0 invokestatic #71 : 使用invokestatic指令调用常量池中指向71的静态方法。
      在这里插入图片描述

    • 3 ldc #62 : 将常量池指向62的变量推到操作数栈顶。
      在这里插入图片描述

    • 5 aload_2 :将本地变量槽2的引用变量推入操作数栈顶

    • 6 invokestatic #61 : :使用invokestatic指令调用指向常量池中61的静态方法 。
      在这里插入图片描述

      我相信大家看了跟没有看一样, 不知道我在说什么 ,这里我要科普一下, 对于普通方法,方法本地变量槽第0个位置肯定是this,而对于静态方法,变量槽的第0个位置可能是方法参数,也可能是内部声明的变量,而test6()方法肯定不是静态方法,而方法有两个参数,因此变量槽的第0个位置肯定是this,变量槽的第1个位置存储的是param1变量,变量槽的第2个位置存储的是param2, 而LocalVariablesNamesTracer.addVariable(“param2”, param2);这个调用需要两个参数,因此就出现了

    • 3 ldc #62
    • 5 aload_2

      这两步调用,实际上是将字符串param2推入操作数栈顶,再将param2的变量值推入操作数栈顶。然后再调用静态方法 。 之前也按照 《go 语言手写虚拟机》这本书,自己实现了一个Java 版本的虚拟机,接下来看里面的代码 。
    在这里插入图片描述
    在这里插入图片描述
      通过invokestatic指令调用源码,我相信再来理解上面几条指令,你肯定已经理解了。 而上面截图的源码和相关博客在
    自己动手写Java虚拟机 (Java核心技术系列)_java版 这篇博客中。

    总结

      我相信大家对我想要实现的功能及原理肯定有一定理解了, 但肯定有小伙伴会问 , method.insertBefore(“com.linzi.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();”); 这个的底层实现又是怎样的呢?是怎样通过一个字节一个字节的修改,最终得到我们想要的效果呢? 这一块是javassist 源码的解析了。本来我也想去研究一下,

    在这里插入图片描述
      我连源码都准备好了,但是由于个人时间不够,平常也有业务需求需要开发,同时还有tomcat , Netty , Dubbo , Nacos , ElasticSearch等更多的开源框架值得我去研究,因此这么底层的东西,只能放到后面有时间,有精力,有激情再来写博客了, 因为这种东西可能需要花几个星期时间才能研究明白。
      这篇博客的内容也没有那么复杂,可能也只是我们没有想到而已,但是他提供的思想我觉得还是很有借鉴意义的。至少以另外一种方式,以不太美观的方式实现了java元组,如果应用于我们的WEB 开发,让我们繁锁的代码得到简化,使得业务逻辑变得更加清晰,我觉得还不错,希望对读者有所帮助,到这里,这一篇博客又告一段落了, 期侍下一篇博客再见。

    本文相关的代码

    https://github.com/quyixiao/Thread_NO_Known.git

    https://github.com/quyixiao/transmit-variable-local.git

    https://github.com/quyixiao/jvm-java.git

    https://github.com/quyixiao/javassist-3.23.1-GA.git

  • 相关阅读:
    给电脑重装系统后修改远程桌面端口的方法
    运维排查 | Systemd 之服务停止后状态为 failed
    [office] excel2003限定单元格输入值范围教程 #微信#经验分享
    【DL】self-attention
    LeetCode HOT 100 —— 23.合并K个升序链表
    使用shell脚本安装hadoop高可用集群
    【软件逆向-基础知识】分析方法、汇编指令体系结构
    Java日志
    修改图片尺寸的几个简单方法
    【iMessage相册推位置推软件】实现 Greetbale 的类型Compiler Error编译器将返回错误Compiler Error
  • 原文地址:https://blog.csdn.net/quyixiao/article/details/127056310