之前和大家说过字节码操作框架的ASM,它使用起来比较困难,今天再学习
一个javassit,它使用起来比较简单,把很多的底层细节都屏蔽了..
javassit是什么呢?
Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
它的作用很广泛,我们接下来主要从以下几个方面进行代码的讲解,
& 获取类型的基本信息:包名,类名,父类,接口等
// 获取默认类型池对象 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();基本可以获取类的全部基础信息...
& 修改类的方法:动态修改指定类的方法
下面的代码,拦截到买票的方法,并在该方法执行前插入了两个控制台打印信息:
/** * 修改指定类的动态方法 * @throws Exception */ public void modifyClassMethod() throws Exception { // 获取要修改的类 Class<?> clazz = Class.forName("com.suning.dynamic_proxy.javassit.Station"); // 实例化类型池对象 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("sellTicket", 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("sellTicket", String.class).invoke(clazz.newInstance(), null); // 解冻一个类 ctClass.defrost(); }
& 动态创建类:
比如下面的代码创建了一个Person类,该类里面有一个字段叫userName,然后有对应的setter和getter方法,最后还有一个show方法显示用户的名称:
/** * 动态创建类 * @throws Exception */ public void dynamicGeneratorClass() throws Exception { ClassPool classPool = ClassPool.getDefault(); // 创建一个类 CtClass ctClass = classPool.makeClass("com.test.Person"); // 为类型设置字段 CtField field = new CtField(classPool.get(String.class.getName()), "userName", ctClass); field.setModifiers(Modifier.PRIVATE); // 添加getter和setter方法 ctClass.addMethod(CtNewMethod.setter("setUserName", field)); ctClass.addMethod(CtNewMethod.getter("getUserName", 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.userName=$1;}"); ctClass.addConstructor(constructor); // 为类设置方法 CtMethod method = new CtMethod(CtClass.voidType, "show", null, ctClass); method.setModifiers(Modifier.PUBLIC); method.setBody("{System.out.println(\"执行结果\" + this.userName);}"); ctClass.addMethod(method); // 加载和执行生成的类 Class<?> clazz = ctClass.toClass(); Object obj = clazz.newInstance(); clazz.getMethod("setUserName", String.class).invoke(obj, "jhp"); clazz.getMethod("show").invoke(obj); //执行构造器传递进来的参数 obj = clazz.getConstructor(String.class).newInstance("lyn"); clazz.getMethod("show").invoke(obj); }
然后显示的结果如下:
& 创建代理类:
下面的代码创建了一个动态的代理类,拦截到set方法并为其重新赋值,然后再通过拦截到get方法进行执行
/** * 创建代理类 * @throws Exception */ public void createProxyClass()throws Exception{ // 实例化代理类工厂 ProxyFactory factory = new ProxyFactory(); //设置父类,ProxyFactory将会动态生成一个类,继承该父类 factory.setSuperclass(PersonProxy.class); //设置过滤器,判断哪些方法调用需要被拦截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method m) { return m.getName().startsWith("get"); } }); Class<?> clazz = factory.createClass(); PersonProxy proxy = (PersonProxy) clazz.newInstance(); ((ProxyObject)proxy).setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method method, Method proxyMethod, Object[] args) throws Throwable { //拦截后前置处理 System.out.println("方法:"+method.getName() + ",被调用"); try { Object ret = proxyMethod.invoke(self, args); System.out.println("返回值: " + ret); return ret; } finally { System.out.println("方法:"+method.getName() + "调用完毕"); } } }); proxy.setName("烧烤小分队"); proxy.getName(); }
最后运行完的效果如下:
& 获取运行时候的指定方法对应的参数:
其实springmvc框架的参数注入使用的就是这个机制
比如下面的代码就可以实现获取person的register方法里面的所有参数
/** * 获取类运行时候的方法参数名称 * @throws Exception */ public void getRuntimeMethodParamterName()throws Exception{ // 获取要修改的类 Class<?> clazz = Class.forName("com.suning.dynamic_proxy.classloader.Person"); // 实例化类型池 ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new ClassClassPath(clazz)); CtClass ctClass = classPool.get(clazz.getName()); // 获取方法 CtMethod method = ctClass.getDeclaredMethod("register"); // 判断是否为静态方法 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)); } }
运行代码的效果如下:
至此javassit的常用功能就讲完了,功能很强大,但使用还是比较简单的,一般就是实例化classpool,之后进行其他对象的获取
CtClass,CtField,CtMethod常用的也就是这几个.