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的文件格式组织。

  • 相关阅读:
    Photoshop 基础七 位图 矢量图 栅格化
    Photoshop 基础六 图层
    Warfare And Logistics UVALive
    Walk Through the Forest UVA
    Airport Express UVA
    Guess UVALive
    Play on Words UVA
    The Necklace UVA
    Food Delivery ZOJ
    Brackets Sequence POJ
  • 原文地址:https://www.cnblogs.com/simoncook/p/8891271.html
Copyright © 2011-2022 走看看