zoukankan      html  css  js  c++  java
  • 动态修改字节码以替换用反射调用get set方法的形式

    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

    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)进行反编译,结果如下,一目了然。

    Snip20180420_9

    Snip20180420_8

    用classpy查看两个class的结构,也能看出来,本质上就是换了常量池的UFT8字符串

    Snip20180420_12

    Snip20180420_11

    5. 与cglib比较

    简单的看了下cglib的BeanMap,原理相同,手法不同。差异在于,他是用asm去动态生成class。

    asm对class的生成做了一些封装,比如你要生成什么名字的class 构造函数是什么,版本是是什么,你只要调用他封装的接口,把这些要素当做参数传进去即可。不需要自己一个byte一个byte的去按class的文件格式组织。

  • 相关阅读:
    window.open和window.opener
    dict对象与QueryDict
    BeautifulSoup的一些方法
    ORM分组与聚合
    python-orm
    开发工具IDEA环境安装配置
    Greenplum介绍-table
    对package.json的理解和学习
    javaScript 的 map() reduce() foreach() filter()
    JSON的序列化和反序列化eval()和parse()方法以及stringfy()方法
  • 原文地址:https://www.cnblogs.com/simoncook/p/8891271.html
Copyright © 2011-2022 走看看