• cglib源码分析(二):Class name 生成策略


    一、如何获取动态生成的class 字节码

    结合生成的class文件是一个学习cglib的比较好的方法。在cglib中,生成的class文件默认只存储在内存中,我们可以在代码中加入下面语句来获取class file。

    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\code ");

    这里涉及到了一个比较关键的类DebuggingClassWriter,接下来我们简单的分析一下这个类。DebuggingClassWriter继承于ClassVisitor,熟悉ASM的TX就可以得知他是访问链上的一员。此类只有一个有参构造函数:

        public DebuggingClassWriter(int flags) {
        super(Opcodes.ASM4, new ClassWriter(flags));
        }

    我们可以看到访问链上的最后一名访问者就是ClassWriter,ClassWriter也是ASM中的一个类,他的主要作用就是产生class 字节码,DebuggingClassWriter最终是委托ClassWriter来生成字节码的。DebuggingClassWriter自己做的主要工作就是 将字节码持久化到硬盘上。

    static {
            debugLocation = System.getProperty(DEBUG_LOCATION_PROPERTY);
            if (debugLocation != null) {
                System.err.println("CGLIB debugging enabled, writing to '" + debugLocation + "'");
                try {
                  Class clazz = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
                  traceCtor = clazz.getConstructor(new Class[]{ClassVisitor.class, PrintWriter.class});
                } catch (Throwable ignore) {
                }
            }
        }

    上面代码是DebuggingClassWriter中的静态代码块,这部分代码在调用类的构造函数之前完成的。在类初始化的时候就会获取DEBUG_LOCATION_PROPERTY 的属性值,同时会通过java的反射机制来生成一个TraceClassVisitor对象,TraceClassVisitor 是 ASM 中的一个工具类,它的作用是将字节码转换成易读的文本,也就是反编译,和javap差不多。

    DebuggingClassWriter中完成关键工作的方法是toByteArray:

                    byte[] b = ((ClassWriter) DebuggingClassWriter.super.cv).toByteArray();
                    if (debugLocation != null) {
                        System.out.println(className);
                        String dirs = className.replace('.', File.separatorChar);
                        try {
                            new File(debugLocation + File.separatorChar + dirs).getParentFile().mkdirs();
                            
                            File file = new File(new File(debugLocation), dirs + ".class");
                            OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                            try {
                                out.write(b);
                            } finally {
                                out.close();
                            }
                            
                            if (traceCtor != null) {
                                file = new File(new File(debugLocation), dirs + ".asm");
                                out = new BufferedOutputStream(new FileOutputStream(file));
                                try {
                                    ClassReader cr = new ClassReader(b);
                                    PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
                                    ClassVisitor tcv = (ClassVisitor)traceCtor.newInstance(new Object[]{null, pw});
                                    cr.accept(tcv, 0);
                                    pw.flush();
                                } finally {
                                    out.close();
                                }
                            }
                        } catch (Exception e) {
                            throw new CodeGenerationException(e);
                        }
                    }

    首先委托ClassWriter来生成字节码,然后根据用户指定的路径来生成文件夹,最后生成两个文件,一个是.class 文件,另外一个是后缀为.asm的文件。用户可以直接打开.asm来直观的分析生成的class文件。

    二、命名策略

    通过第一步我们可以发现指定的文件夹下面有一些命名奇怪的class文件。本节我们就来分析一下这些类名都是怎么生成的。在上一章我们分析key和缓存的时候提到过,class generator必须继承AbstractClassGenerator ,然后实现抽象方法 generateClass,generateClass的作用就是用来生成class ,那么class name的生成也应该是在这个方法中。在每个generateClass方法中,当调用ClassEmitter 的begin_class方法时都要求提供一个入参 class internal name。这时会调用AbstractClassGenerator  的 getClassName 来生成一个可用的class internal name。

    final protected String getClassName() {
            if (className == null)
                className = getClassName(getClassLoader());
            return className;
        }
    
        private String getClassName(final ClassLoader loader) {
            final Set nameCache = getClassNameCache(loader);
            return namingPolicy.getClassName(namePrefix, source.name, key, new Predicate() {
                public boolean evaluate(Object arg) {
                    return nameCache.contains(arg);
                }
            });
        }
    
        private Set getClassNameCache(ClassLoader loader) {
            return (Set)((Map)source.cache.get(loader)).get(NAME_KEY);
    }

    上面代码可以看到,getClassName首先根据classloader和NAME_KEY来获取已经生成的类名的列表。命名策略是封装在NamingPolicy中的getClassName方法中的。用户可以根据自己的实际需求通过实现接口NamingPolicy来定制命名策略。Cglib提供了一个默认的命名策略DefaultNamingPolicy。DefaultNamingPolicy 同样实现了NamingPolicy接口。这部分代码比较简单,我们可以分析得出,默认的命名策略为

                     被代理class name + "$$" + class generator name + "ByCGLIB" + "$$" + key的hashcode

    如果命名冲突的话,会在上面产生的类名后面+”_”+index(index >=2).

     

    Note:如果需要自定义命名规则,比如return prefix+"_"+source+"_"+key.hashCode(); 要把source的包名去掉,否则将产生比较深的文件夹,key的使用要注意,不能直接+key,应使用key的hashcode。

  • 相关阅读:
    【转】 url中文乱码问题
    [转]Jquery 点击图片在弹出层显示大图
    JQuery获取和设置Select选项的常用方法总结
    springMVC框架下返回json格式的对象,list,map
    sqlserver数据库 表中字段值有空格,如何去除空格(例如char (5) 存入数据不足5位时sqlserver会自动补空格)
    jquery Jbox 插件实现弹出窗口在修改的数据之后,关闭弹出窗口刷新父页面的问题
    sqlserver 2008 r2 直接下载地址,可用迅雷下载
    web服务器与tomcat
    xml入门与解析
    jdbc框架-dbutils的简单使用
  • 原文地址:https://www.cnblogs.com/cruze/p/3847968.html
Copyright © 2020-2023  润新知