动态修改字节码以替换用反射调用get set方法的形式
2018-04-20 15:48 chen.simon 阅读(1552) 评论(0) 编辑 收藏 举报1. 起因
在前两天,为了解决websphere和JDK8上部署的应用发起webservice调用(框架用的cxf)时报错的问题,跟了一些代码,最终发现可以通过加上参数-Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true来解决。
2. ClassTailor.noOptimize优化了什么
分析jaxb的代码分析,由于webservice调用要用到xml与bean对象的转换,于是就是用到对bean字段的get set。通常的想法此处用反射便可以完成。但是jaxb在这里用了动态生成字节码的方式直接调用bean的get set方法,以达到节提升性能的目的,本质上就是换了class文件中常量池的UFT8字符串。
具体可以参见com.sun.xml.bind.v2.bytecode.ClassTailor类,com.sun.xml.bind.v2.runtime.reflect.opt.AccessorInjector 类。
3. 按他的方式做一个demo
假设需要操作的bean是User 如下:
/* * 文 件 名: User.java * 版 权: . Copyright 2008-2017, All rights reserved Co.,Ltd. * 描 述: <描述> * 修 改 人: simon * 修改时间: 2018年4月20日 */ package com.cnblogs.simoncook.jaxb.bean; /** * <一句话功能简述> * <功能详细描述> * * @author simon * @version [版本号, 2018年4月20日] * @see [相关类/方法] * @since [产品/模块版本] */ public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
转换的模板类与接口
/* * 文 件 名: IMethodAccess.java * 版 权: . Copyright 2008-2017, All rights reserved Co.,Ltd. * 描 述: <描述> * 修 改 人: simon * 修改时间: 2018年4月20日 */ package com.cnblogs.simoncook.jaxb.helper; /** * <一句话功能简述> * <功能详细描述> * * @author simon * @version [版本号, 2018年4月20日] * @see [相关类/方法] * @since [产品/模块版本] */ public interface IMethodAccess { public Object get(Object bean); public void set(Object instance, Object value); }
/* * 文 件 名: MethodAcessRef.java * 版 权: . Copyright 2008-2017, All rights reserved Co.,Ltd. * 描 述: <描述> * 修 改 人: simon * 修改时间: 2018年4月20日 */ package com.cnblogs.simoncook.jaxb.helper; /** * <一句话功能简述> <功能详细描述> * * @author simon * @version [版本号, 2018年4月20日] * @see [相关类/方法] * @since [产品/模块版本] */ public class MethodAcessTemplate implements IMethodAccess { public MethodAcessTemplate() { } public Object get(Object bean) { return ((Bean)bean).getRef(); } public void set(Object instance, Object value) { ((Bean)instance).setRef((Ref)value); } }
动态字节码生成与加载调用类
/* * 文 件 名: ClassTailor.java * 版 权: . Copyright 2008-2017, All rights reserved Co.,Ltd. * 描 述: <描述> * 修 改 人: simon * 修改时间: 2018年4月20日 */ package com.cnblogs.simoncook.jaxb.helper; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import com.cnblogs.simoncook.jaxb.bean.User; /** * <一句话功能简述> <功能详细描述> * * @author simon * @version [版本号, 2018年4月20日] * @see [相关类/方法] * @since [产品/模块版本] */ public class ClassTailor { /** * <一句话功能简述> <功能详细描述> * * @param args * @see [类、类#方法、类#成员] */ public static void main(String[] args) { User u = new User(); ClassLoader classLoader = ClassTailor.class.getClassLoader(); String templateClassName = "com/cnblogs/simoncook/jaxb/helper/MethodAcessTemplate"; String methodAcessRefTemplate = templateClassName + ".class"; InputStream templateClassResource = classLoader.getResourceAsStream(methodAcessRefTemplate); String[] replacements = {"com/cnblogs/simoncook/jaxb/helper/Bean", "com/cnblogs/simoncook/jaxb/bean/User", "com/cnblogs/simoncook/jaxb/helper/Ref", "java/lang/String", "()Lcom/cnblogs/simoncook/jaxb/helper/Ref;", "()Ljava/lang/String;", "(Lcom/cnblogs/simoncook/jaxb/helper/Ref;)V", "(Ljava/lang/String;)V", "getRef", "getName", "setRef", "setName"}; String newClassName = "com/cnblogs/simoncook/jaxb/bean/User_getset_name"; byte[] accessClassByte = tailor(templateClassResource, templateClassName, newClassName, replacements); dumpClass(newClassName, accessClassByte); try { @SuppressWarnings("unchecked") Class<IMethodAccess> c = buildGetSetClass(classLoader, newClassName, accessClassByte); IMethodAccess accessor = (IMethodAccess)c.newInstance(); accessor.set(u, "simon"); System.out.println(u.getName()); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } /** * <一句话功能简述> <功能详细描述> * * @param classLoader * @param newClassName * @param accessClassByte * @return * @throws NoSuchMethodException * @throws IllegalAccessException * @throws InvocationTargetException * @see [类、类#方法、类#成员] */ private static Class<IMethodAccess> buildGetSetClass(ClassLoader classLoader, String newClassName, byte[] accessClassByte) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE); final Method resolveClass = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class); AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // TODO: check security implication // do these setAccessible allow anyone to call these methods freely?s defineClass.setAccessible(true); resolveClass.setAccessible(true); return null; } }); ClassLoader filedTypeClassLoader = classLoader; @SuppressWarnings("unchecked") Class<IMethodAccess> c = (Class<IMethodAccess>)defineClass.invoke(filedTypeClassLoader, newClassName.replace('/', '.'), accessClassByte, 0, accessClassByte.length); resolveClass.invoke(filedTypeClassLoader, c); return c; } /** * <一句话功能简述> <功能详细描述> * * @param newClassName * @param accessClassByte * @see [类、类#方法、类#成员] */ private static void dumpClass(String newClassName, byte[] accessClassByte) { FileOutputStream out = null; BufferedOutputStream bufW = null; try { File file = new File("/Users/simon/700.temp_/jaxbclass/" + newClassName); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } out = new FileOutputStream(file); bufW = new BufferedOutputStream(out); bufW.write(accessClassByte); bufW.flush(); bufW.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufW != null) { try { bufW.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static byte[] tailor(InputStream image, String templateClassName, String newClassName, String... replacements) { DataInputStream in = new DataInputStream(image); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); DataOutputStream out = new DataOutputStream(baos); // skip until the constant pool count long l = in.readLong(); out.writeLong(l); // read the constant pool size short count = in.readShort(); out.writeShort(count); // replace constant pools for (int i = 0; i < count; i++) { byte tag = in.readByte(); out.writeByte(tag); switch (tag) { case 0: // this isn't described in the spec, // but class files often seem to have this '0' tag. // we can apparently just ignore it, but not sure // what this really means. break; case 1: // CONSTANT_UTF8 { String value = in.readUTF(); if (value.equals(templateClassName)) value = newClassName; else { for (int j = 0; j < replacements.length; j += 2) if (value.equals(replacements[j])) { value = replacements[j + 1]; break; } } out.writeUTF(value); } break; case 3: // CONSTANT_Integer case 4: // CONSTANT_Float out.writeInt(in.readInt()); break; case 5: // CONSTANT_Long case 6: // CONSTANT_Double i++; // doubles and longs take two entries out.writeLong(in.readLong()); break; case 7: // CONSTANT_Class case 8: // CONSTANT_String out.writeShort(in.readShort()); break; case 9: // CONSTANT_Fieldref case 10: // CONSTANT_Methodref case 11: // CONSTANT_InterfaceMethodref case 12: // CONSTANT_NameAndType out.writeInt(in.readInt()); break; default: throw new IllegalArgumentException("Unknown constant type " + tag); } } // then copy the rest byte[] buf = new byte[512]; int len; while ((len = in.read(buf)) > 0) out.write(buf, 0, len); in.close(); out.close(); // by now we got the properly tailored class file image return baos.toByteArray(); } catch (Exception e) { } return null; } }
4. 分析
用JD 对模板类class与之前的class(需要dump)进行反编译,结果如下,一目了然。
用classpy查看两个class的结构,也能看出来,本质上就是换了常量池的UFT8字符串。
5. 与cglib比较
简单的看了下cglib的BeanMap,原理相同,手法不同。差异在于,他是用asm去动态生成class。
asm对class的生成做了一些封装,比如你要生成什么名字的class 构造函数是什么,版本是是什么,你只要调用他封装的接口,把这些要素当做参数传进去即可。不需要自己一个byte一个byte的去按class的文件格式组织。