你不知道的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<String, Object> response(Object... args) {
    Map<String, Object> 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<String,Object> 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<Stack<Map<String, Object>>> localVariables = new ThreadLocal<Stack<Map<String, Object>>>();
        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<Map<String, Object>>());
            }
            localVariables.get().push(new HashMap<String, Object>());
        }

        public static void exit() {
            if (localVariables.get().isEmpty()) {
                return;
            }
            localVariables.get().pop().clear();
        }

        public static Map<String, Object> locals() {
            if (localVariables.get() != null && !localVariables.get().empty()) {
                return localVariables.get().peek();
            }
            return new HashMap<String, Object>();
        }

        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<String, Object> 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<String, Object> getResParam(Object... args) {
    	// 构建map
        Map<String, Object> result = new HashMap();
        Object[] var6 = args;
        int var5 = args.length;
        for (int var4 = 0; var4 < var5; ++var4) {
            Object o = var6[var4];
            List<String> 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<String> getAllLocalVariableNames(Object o) {
	    List<String> allNames = new ArrayList<String>();
	    // 将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> 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<String, Object> 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<T2<Integer, String>> parameterNames = new ArrayList<T2<Integer, String>>();
            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<Integer, String>(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i)));
                    }
                }

                Collections.sort(parameterNames, new Comparator<T2<Integer, String>>() {
                    public int compare(T2<Integer, String> o1, T2<Integer, String> o2) {
                        return o1._1.compareTo(o2._1);
                    }
                });

            }
            List<String> names = new ArrayList<String>();
            // 下面这一块主要是
            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<String> 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<Integer, Integer> storeByCode = new HashMap<Integer, Integer>();

/**
 * 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 <com/linzi/classloading/enhancers/LocalvariablesNamesEnhancer$LocalVariablesNamesTracer.enter> : 使用invokestatic指令调用常量池中指向71的静态方法。
    在这里插入图片描述

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

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

  • 6 invokestatic #61 :<com/linzi/classloading/enhancers/LocalvariablesNamesEnhancer$LocalVariablesNamesTracer.addVariable> :使用invokestatic指令调用指向常量池中61的静态方法 。
    在这里插入图片描述

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

  • 3 ldc #62 <param2>
  • 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值