在开发中,我是一个极懒的人, 而且特别不喜欢写无用且烦锁的代码,但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 对象,但不得不写下面几行鸡肋的代码,索然无味,弃之不行的代码。
Mapmap = 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
在生成字节码中用到了下面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

3 ldc #62

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

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