概述
Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
下面一个方法的目的是获取一个类加载器(ClassLoader),以加载指定的.jar或.class文件,在之后的代码中会使用到。
private static ClassLoader getLocaleClassLoader() throws Exception { List<URL> classPathURLs = new ArrayList<>(); // 加载.class文件路径 classPathURLs.add(classesPath.toURI().toURL()); // 获取所有的jar文件 File[] jarFiles = libPath.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".jar"); } }); Assert.assertFalse(ObjectHelper.isArrayNullOrEmpty(jarFiles)); // 将jar文件路径写入集合 for (File jarFile : jarFiles) { classPathURLs.add(jarFile.toURI().toURL()); } // 实例化类加载器 return new URLClassLoader(classPathURLs.toArray(new URL[classPathURLs.size()])); }
获取类型信息
@Test public void test() throws NotFoundException { // 获取默认类型池对象 ClassPool classPool = ClassPool.getDefault(); // 获取指定的类型 CtClass ctClass = classPool.get("java.lang.String"); System.out.println(ctClass.getName()); // 获取类名 System.out.println("\tpackage " + ctClass.getPackageName()); // 获取包名 System.out.print("\t" + Modifier.toString(ctClass.getModifiers()) + " class " + ctClass.getSimpleName()); // 获取限定符和简要类名 System.out.print(" extends " + ctClass.getSuperclass().getName()); // 获取超类 // 获取接口 if (ctClass.getInterfaces() != null) { System.out.print(" implements "); boolean first = true; for (CtClass c : ctClass.getInterfaces()) { if (first) { first = false; } else { System.out.print(", "); } System.out.print(c.getName()); } } System.out.println(); }
修改类方法
@Test public void test() throws Exception { // 获取本地类加载器 ClassLoader classLoader = getLocaleClassLoader(); // 获取要修改的类 Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib"); // 实例化类型池对象 ClassPool classPool = ClassPool.getDefault(); // 设置类搜索路径 classPool.appendClassPath(new ClassClassPath(clazz)); // 从类型池中读取指定类型 CtClass ctClass = classPool.get(clazz.getName()); // 获取String类型参数集合 CtClass[] paramTypes = {classPool.get(String.class.getName())}; // 获取指定方法名称 CtMethod method = ctClass.getDeclaredMethod("show", paramTypes); // 赋值方法到新方法中 CtMethod newMethod = CtNewMethod.copy(method, ctClass, null); // 修改源方法名称 String oldName = method.getName() + "$Impl"; method.setName(oldName); // 修改原方法 newMethod.setBody("{System.out.println(\"执行前\");" + oldName + "($$);System.out.println(\"执行后\");}"); // 将新方法添加到类中 ctClass.addMethod(newMethod); // 加载重新编译的类 clazz = ctClass.toClass(); // 注意,这一行会将类冻结,无法在对字节码进行编辑 // 执行方法 clazz.getMethod("show", String.class).invoke(clazz.newInstance(), "hello"); ctClass.defrost(); // 解冻一个类,对应freeze方法 }
动态创建类
@Test public void test() throws Exception { ClassPool classPool = ClassPool.getDefault(); // 创建一个类 CtClass ctClass = classPool.makeClass("edu.alvin.reflect.DynamiClass"); // 为类型设置接口 //ctClass.setInterfaces(new CtClass[] {classPool.get(Runnable.class.getName())}); // 为类型设置字段 CtField field = new CtField(classPool.get(String.class.getName()), "value", ctClass); field.setModifiers(Modifier.PRIVATE); // 添加getter和setter方法 ctClass.addMethod(CtNewMethod.setter("setValue", field)); ctClass.addMethod(CtNewMethod.getter("getValue", field)); ctClass.addField(field); // 为类设置构造器 // 无参构造器 CtConstructor constructor = new CtConstructor(null, ctClass); constructor.setModifiers(Modifier.PUBLIC); constructor.setBody("{}"); ctClass.addConstructor(constructor); // 参数构造器 constructor = new CtConstructor(new CtClass[] {classPool.get(String.class.getName())}, ctClass); constructor.setModifiers(Modifier.PUBLIC); constructor.setBody("{this.value=$1;}"); ctClass.addConstructor(constructor); // 为类设置方法 CtMethod method = new CtMethod(CtClass.voidType, "run", null, ctClass); method.setModifiers(Modifier.PUBLIC); method.setBody("{System.out.println(\"执行结果\" + this.value);}"); ctClass.addMethod(method); // 加载和执行生成的类 Class<?> clazz = ctClass.toClass(); Object obj = clazz.newInstance(); clazz.getMethod("setValue", String.class).invoke(obj, "hello"); clazz.getMethod("run").invoke(obj); obj = clazz.getConstructor(String.class).newInstance("OK"); clazz.getMethod("run").invoke(obj); }
创建代理类
@Test public void test() throws Exception { // 实例化代理类工厂 ProxyFactory factory = new ProxyFactory(); //设置父类,ProxyFactory将会动态生成一个类,继承该父类 factory.setSuperclass(TestProxy.class); //设置过滤器,判断哪些方法调用需要被拦截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method m) { return m.getName().startsWith("get"); } }); Class<?> clazz = factory.createClass(); TestProxy proxy = (TestProxy) clazz.newInstance(); ((ProxyObject)proxy).setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { //拦截后前置处理,改写name属性的内容 //实际情况可根据需求修改 System.out.println(thisMethod.getName() + "被调用"); try { Object ret = proceed.invoke(self, args); System.out.println("返回值: " + ret); return ret; } finally { System.out.println(thisMethod.getName() + "调用完毕"); } } }); proxy.setName("Alvin"); proxy.setValue("1000"); proxy.getName(); proxy.getValue(); }其中,TestProxy类内容如下:
public class TestProxy { private String name; private String value; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
获取方法名称
@Test public void test() throws Exception { // 获取本地类加载器 ClassLoader classLoader = getLocaleClassLoader(); // 获取要修改的类 Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib"); // 实例化类型池 ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new ClassClassPath(clazz)); CtClass ctClass = classPool.get(clazz.getName()); // 获取方法 CtMethod method = ctClass.getDeclaredMethod("show", ObjectHelper.argumentsToArray(CtClass.class, classPool.get("java.lang.String"))); // 判断是否为静态方法 int staticIndex = Modifier.isStatic(method.getModifiers()) ? 0 : 1; // 获取方法的参数 MethodInfo methodInfo = method.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag); for (int i = 0; i < method.getParameterTypes().length; i++) { System.out.println("第" + (i + 1) + "个参数名称为: " + localVariableAttribute.variableName(staticIndex + i)); } }关于“获取方法名称”,其主要作用是:当Java虚拟机加载.class文件后,会将类方法“去名称化”,即丢弃掉方法形参的参数名,而是用形参的序列号来传递参数。如果要通过Java反射获取参数的参数名,则必须在编辑是指定“保留参数名称”。Javassist则不存在这个问题,对于任意方法,都能正确的获取其参数的参数名。
Spring MVC就是通过方法参数将请求参数进行注入的,这一点比struts2 MVC要方便很多,Spring也是借助了Javassist来实现这一点的。
附录
代码中使用了一个ObjectHelper类,这是我自用的一个小工具类,该类的代码可点击查看。