javassist 的使用

1/获取方法参数名

来源:点击打开链接

javassist和 asm都是动态创建、修改字节码的类库。个人感觉主要区别是:

  • asm的性能要好一些。
  • javassist的主要优势是学习成本低,可以根据java源代码生成字节码,而不必直接和字节码打交道。(本例不涉及生成字节码的部分)
  • 发现javassist貌似不支持泛型。
package org.cc.tx.aop.javassit;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
 
import org.cc.core.CcException;
import org.cc.core.common.Exceptions;
 
/**
 * 

* 依赖javassit的工具类,获取方法的参数名 *

* * @author dixingxing * @date Apr 20, 2012 */ public class Classes { private Classes() {} /** * *

* 获取方法参数名称 *

* * @param cm * @return */ protected static String[] getMethodParamNames(CtMethod cm) { CtClass cc = cm.getDeclaringClass(); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute .getAttribute(LocalVariableAttribute.tag); if (attr == null) { throw new CcException(cc.getName()); } String[] paramNames = null; try { paramNames = new String[cm.getParameterTypes().length]; } catch (NotFoundException e) { Exceptions.uncheck(e); } int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; for (int i = 0; i < paramNames.length; i++) { paramNames[i] = attr.variableName(i + pos); } return paramNames; } /** * 获取方法参数名称,按给定的参数类型匹配方法 * * @param clazz * @param method * @param paramTypes * @return */ public static String[] getMethodParamNames(Class clazz, String method, Class... paramTypes) { ClassPool pool = ClassPool.getDefault(); CtClass cc = null; CtMethod cm = null; try { cc = pool.get(clazz.getName()); String[] paramTypeNames = new String[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) paramTypeNames[i] = paramTypes[i].getName(); cm = cc.getDeclaredMethod(method, pool.get(paramTypeNames)); } catch (NotFoundException e) { Exceptions.uncheck(e); } return getMethodParamNames(cm); } /** * 获取方法参数名称,匹配同名的某一个方法 * * @param clazz * @param method * @return * @throws NotFoundException * 如果类或者方法不存在 * @throws MissingLVException * 如果最终编译的class文件不包含局部变量表信息 */ public static String[] getMethodParamNames(Class clazz, String method) { ClassPool pool = ClassPool.getDefault(); CtClass cc; CtMethod cm = null; try { cc = pool.get(clazz.getName()); cm = cc.getDeclaredMethod(method); } catch (NotFoundException e) { Exceptions.uncheck(e); } return getMethodParamNames(cm); } } [2].[代码] 基于asm的实现 跳至 [1] [2] ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 /** * Classes.java 9:22:44 AM Apr 23, 2012 * * Copyright(c) 2000-2012 HC360.COM, All Rights Reserved. */ package org.cc.core.common; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.cc.core.asm.ClassReader; import org.cc.core.asm.ClassVisitor; import org.cc.core.asm.ClassWriter; import org.cc.core.asm.Label; import org.cc.core.asm.MethodVisitor; import org.cc.core.asm.Opcodes; import org.cc.core.asm.Type; /** *

* 基于asm的工具类 *

* * @author dixingxing * @date Apr 23, 2012 */ public final class Classes { private Classes() { } /** * *

比较参数类型是否一致

* * @param types asm的类型({@link Type}) * @param clazzes java 类型({@link Class}) * @return */ private static boolean sameType(Type[] types, Class[] clazzes) { // 个数不同 if (types.length != clazzes.length) { return false; } for (int i = 0; i < types.length; i++) { if(!Type.getType(clazzes[i]).equals(types[i])) { return false; } } return true; } /** * *

获取方法的参数名

* * @param m * @return */ public static String[] getMethodParamNames(final Method m) { final String[] paramNames = new String[m.getParameterTypes().length]; final String n = m.getDeclaringClass().getName(); final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassReader cr = null; try { cr = new ClassReader(n); } catch (IOException e) { e.printStackTrace(); Exceptions.uncheck(e); } cr.accept(new ClassVisitor(Opcodes.ASM4, cw) { @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { final Type[] args = Type.getArgumentTypes(desc); // 方法名相同并且参数个数相同 if (!name.equals(m.getName()) || !sameType(args, m.getParameterTypes())) { return super.visitMethod(access, name, desc, signature, exceptions); } MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions); return new MethodVisitor(Opcodes.ASM4, v) { @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { int i = index - 1; // 如果是静态方法,则第一就是参数 // 如果不是静态方法,则第一个是"this",然后才是方法的参数 if(Modifier.isStatic(m.getModifiers())) { i = index; } if (i >= 0 && i < paramNames.length) { paramNames[i] = name; } super.visitLocalVariable(name, desc, signature, start, end, index); } }; } }, 0); return paramNames; } public static void main(String[] args) throws SecurityException, NoSuchMethodException { String[] s = getMethodParamNames(Dates.class.getMethod("parse", String.class,String.class)); System.out.println(Strings.join(s)); s = getMethodParamNames(Dates.class.getMethod("parse", String.class,String[].class)); System.out.println(Strings.join(s)); } }


2/ javassist.notFoundException

来源:点击打开链接

AOP中需要通过反射获取方法参数名称,所以用到javassist,jar包测试ok
可放到war包,部署到tomcat后便报异常:javassist.NotFoundException: xxx.xxx.xxxServiceImpl
war包中配的AOP拦截jar包中的Service方法,jar已通过maven引入到war中。

javassist代码:
ClassPool pool = ClassPool.getDefault();
CtClass cls = pool.get(clazz.getName());  // 此处报异常

不知哪位有这方面经验往指教,谢谢。


问题补充:
chenxiang105 写道
引用

war 中都是些什么?

jsp clases lib库文件?



:(  war也是一种打包方式 可以丢在tomcat weapp下直接运行项目

jar可以程序没有问题, jar既然引入了就应该可以使用.
再报错的地方打log 看那个getname() 是个什么东西

在启动的时候检查xxx/xxx/xxxServiceImpl.class文件在启动后webapp下面项目中是否可以找到
映射到配置文件是否可以找到 是否正确



getname() 获得的是全称类名,debug过了,是没有问题的。
xxxServiceImpl.class 存在,不通过javassist反射是可以调用的。

这个问题貌似是类因为在不同的项目中,所以javassist找不到类文件

有个insertClassPath方法,但不知道怎样正确使用。。。测试了半天依然报错。。。杯具。。。




问题补充:看了官方对这个问题的说明,还没有试,希望对以后遇到该问题的人有所帮助,如下:

The default ClassPool returned by a static method ClassPool.getDefault() searches the same path that the underlying JVM (Java virtual machine) has. If a program is running on a web application server such as JBoss and Tomcat, the ClassPool object may not be able to find user classes since such a web application server uses multiple class loaders as well as the system class loader. In that case, an additional class path must be registered to the ClassPool. Suppose that pool refers to a ClassPool object: 

pool.insertClassPath(new ClassClassPath(this.getClass()));

This statement registers the class path that was used for loading the class of the object that this refers to. You can use any Class object as an argument instead of this.getClass(). The class path used for loading the class represented by that Class object is registered. 

You can register a directory name as the class search path. For example, the following code adds a directory /usr/local/javalib to the search path: 

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

The search path that the users can add is not only a directory but also a URL: 

ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

This program adds "http://www.javassist.org:80/java/" to the class search path. This URL is used only for searching classes belonging to a package org.javassist. For example, to load a class org.javassist.test.Main, its class file will be obtained from: 

http://www.javassist.org:80/java/org/javassist/test/Main.class

Furthermore, you can directly give a byte array to a ClassPool object and construct a CtClass object from that array. To do this, use ByteArrayClassPath. For example, 

ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);

The obtained CtClass object represents a class defined by the class file specified by b. The ClassPool reads a class file from the given ByteArrayClassPath if get() is called and the class name given to get() is equal to one specified by name. 

If you do not know the fully-qualified name of the class, then you can use makeClass() in ClassPool: 

ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

makeClass() returns the CtClass object constructed from the given input stream. You can use makeClass() for eagerly feeding class files to the ClassPool object. This might improve performance if the search path includes a large jar file. Since a ClassPool object reads a class file on demand, it might repeatedly search the whole jar file for every class file. makeClass() can be used for optimizing this search. The CtClass constructed by makeClass() is kept in the ClassPool object and the class file is never read again. 

The users can extend the class search path. They can define a new class implementing ClassPath interface and give an instance of that class to insertClassPath() in ClassPool. This allows a non-standard resource to be included in the search path. 

你可能感兴趣的:(java)