javassist 的使用

1/获取方法参数名

来源:点击打开链接

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

  • 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;
 
/**
 * <p>
 * <font color="red">依赖javassit</font>的工具类,获取方法的参数名
 * </p>
 *
 * @author dixingxing
 * @date Apr 20, 2012
 */
public class Classes {
    private Classes() {}
 
    /**
     *
     * <p>
     * 获取方法参数名称
     * </p>
     *
     * @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;
 
/**
 * <p>
 * 基于asm的工具类
 * </p>
 *
 * @author dixingxing
 * @date Apr 23, 2012
 */
public final class Classes {
 
    private Classes() {
    }
 
    /**
     *
     * <p>比较参数类型是否一致</p>
     *
     * @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;
    }
 
 
    /**
     *
     * <p>获取方法的参数名</p>
     *
     * @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. 

高级使用Javassist包括一些高级功能,如修改已有的类、动态生成类、修改类的字节码等。这些功能可以实现一些有趣和复杂的场景。 首先,我们可以使用Javassist来修改已有的类。这可以用于添加、删除或修改类的字段、方法和构造函数。通过获取类的CtClass对象,我们可以使用Javassist提供的API对其进行操作,然后将修改后的字节码重新写回class文件。这在实现日志切面、权限切面等功能时非常有用。 其次,Javassist还可以用于动态生成类。通过调用ClassLoader的defineClass方法,我们可以将由Javassist生成的字节码转换为Class对象,并在运行时加载。这样,我们可以在程序运行时创建新的类,而不需要提前定义它们。这在某些情况下非常有用,比如动态代理、动态生成工厂类等场景。 最后,Javassist还提供了修改类的字节码的能力。通过获取类的CtClass对象,我们可以使用Javassist提供的API对其进行字节码操作,比如修改方法的实现、添加方法的拦截器等。这在实现一些高级的AOP功能时非常有用。 总结来说,Javassist是一个强大的字节码操作工具,可以用于修改已有的类、动态生成类和修改类的字节码。它在实现一些高级的功能时非常有用,比如日志切面、权限切面、动态代理等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [javassist使用指南](https://blog.csdn.net/mChenys/article/details/122901039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值