Javassit
是通过直接书写 java代码
, 通过它提供了一套系统, 给你编译成字节码的操作 . ASM
是直接书写 字节码
, 所以难度上来很大的, 不是一般水平, 根本难以使用, 所以CGLIB基于ASM提出了一种代理的模板 , 为什么说是模板呢, 因为真正的字节码操作, 不仅仅局限在代理. 所以``
它如何区分JDK版本的 , 这个就告诉我们了, JDK版本每次变迁多了些设么.
public static final int MAJOR_VERSION; static { int ver = JAVA_3; try { Class.forName("java.lang.StringBuilder"); ver = JAVA_5; Class.forName("java.util.zip.DeflaterInputStream"); ver = JAVA_6; Class.forName("java.lang.invoke.CallSite", false, ClassLoader.getSystemClassLoader()); ver = JAVA_7; Class.forName("java.util.function.Function"); ver = JAVA_8; Class.forName("java.lang.Module"); ver = JAVA_9; List.class.getMethod("copyOf", Collection.class); ver = JAVA_10; Class.forName("java.util.Optional").getMethod("isEmpty"); ver = JAVA_11; } catch (Throwable t) {} MAJOR_VERSION = ver; }
**由于我们开发使用的话, 绝对不可能修改当前已有的类, 除非agent 或者自定义ClassLoader. 因为Java不支持卸载系统类加载器加载的类, 你说恶不恶心, 除非agent , ** 因此我们这个例子只是生产一个新的类, 去继承父类的方法.
public class Parent { public void test() { System.out.println(this + " : echo"); } public static void main(String[] args) throws Exception { // 1. 默认生成一个 ClassPool , 其实很简单就是加入了Java的基本包装类型9种和加入了Java的Object类. ClassPool pool = ClassPool.getDefault(); // 2. 去拿到我们com.javassit.demo.Parent类 , 由于Javassit 中最基本的类单元是CTClass . CtClass parent = pool.getCtClass("com.javassit.demo.Parent"); // 3. 创建一个 子对象, 去继承父对象 CtClass child = pool.makeClass("com.javassit.demo.Child", parent); // 4. 我们直接可以拿到该类对象了. Class<?> cClass = child.toClass(); // 5. 直接基本构造器实例化就行了. Parent ins = (Parent) cClass.getConstructor().newInstance(); // 6. invoke test ins.test(); } }
上诉代码流程很简单, 最后会调用成功, 就是一个简单的例子. 下面我们要添加东西了.
构造器 , 是类基本结构 , 所以提高了 CtConstructor
定义一个构造器.
public class Parent { private Long id; // 最好使用包装类型. 基本类型好像失败. public Parent(Long id) { this.id = id; } public long getId() { return id; } public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass parent = pool.getCtClass("com.javassit.demo2.Parent"); CtClass child = pool.makeClass("com.javassit.demo2.Child", parent); // 添加构造器, 只用写出构造器如何书写就行了. 纯JAVA代码 child.addConstructor(CtNewConstructor.make("public Child(Long id){super(id);}", child)); Class<?> cClass = child.toClass(); Parent ins = (Parent) cClass.getConstructor(Long.class).newInstance(100L); long id = ins.getId(); System.out.println(id); } }
其中 CtNewConstructor.make("public Child(Long id){super(id);}", child)
这个make操作会去编译你的Java代码, 编译成字节码, 由于我字节码学的不好, 不展开解释了. .
为什么非要使用包装类型呢, 是因为Java的反射的缺陷, 这也是Java的弊端. 你不信可以自己反射去实例化一个对象. 基本类型, 根本无法实例化 , 不管是不是基础, 原因是Java会自动拆箱 , 装箱. 不是你手动执行的.
当构造器 是一个基本数据类型, 不会有拆箱操作的. 如果我们传入Long类型, 而且反射只能传入包装类型, 此时注定会失败的.
0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: lload_1 6: putfield #2 // Field id:J 9: return
如同下属代码 :
Long x = 100L; long y = x;
实际上走了两次转换, 因为Java的字面量全是基本数据类型 , 所以100L - > Long , 需要valueOf, 而第二次 Long -> long 需要拆箱, 也就是langValue .
0: ldc2_w #3 // long 100l 3: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 6: astore_1 7: aload_1 8: invokevirtual #6 // Method java/lang/Long.longValue:()J
我们这个例子, 直接在 Parent上修改 他的类构造器, 因此需要定义一个ClassLoader.
public class Parent { private long id; // 最好使用包装类型. 基本类型好像失败. public Parent(Long id) { this.id = id; } public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); // 1. 加载目前的字节码 CtClass parent = pool.getCtClass("com.javassit.demo3.Parent"); // 2. 就是插入输出 for (CtConstructor constructor : parent.getConstructors()) { // 插入到前面, 插入到后面. // 第一个参数是$1 constructor.insertAfter("System.out.println(/"id=/"+$1);"); } // 3. 输出字节码 byte[] bytes = parent.toBytecode(); // 4. 遵循双亲委派模型, 去加载类 , 破坏会发现一堆问题 , 一堆类找不到 Map<String, Class<?>> cache = new ConcurrentHashMap<>(); ClassLoader classLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // 如果使我们需要的类,直接放到catch中. 这种容易出现内存泄漏, 但是重复定义会报错的. 只能这么做了. if (name.equals("com.javassit.demo3.Parent")) { if (cache.get(name) == null) { Class<?> aClass = defineClass(name, bytes, 0, bytes.length); cache.put(name, aClass); } return cache.get(name); } // 委派给父类加载 return super.loadClass(name); } }; Class<?> aClass = classLoader.loadClass(Parent.class.getName()); // 这里不能类型转换, 不然会失败的. 因为会有俩不同的类存在 Object o = aClass.getConstructor(Long.class).newInstance(1L); System.out.println("对象的类加载器是 : "+o.getClass().getClassLoader()); } } // 输出 : // id=1 // 对象的类加载器是 : com.javassit.demo3.Parent$1@2038ae61
最后生成的代码如下
public Parent(Long id) { this.id = id; // 这个是方法的返回值. 所以构造器. 默认是空的. Object var3 = null; System.out.println("id=" + id); }
public class JavaBean { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); String className = "com.javassit.demo5.GavaBean"; CtClass cc = pool.makeClass(className); // 创建字段, 需要申明类型 . 名字, 和什么插入的类 CtField field = new CtField(pool.get(String.class.getName()), "name", cc); // 设置访问类型 field.setModifiers(Modifier.PRIVATE); // 添加字段 cc.addField(field); // 直接可以创建get set方法. cc.addMethod(CtNewMethod.setter("setName", field)); cc.addMethod(CtNewMethod.getter("getName", field)); // 创建无参构造器. CtConstructor constructor = CtNewConstructor.make(null, null, cc); constructor.setBody("{System.out.println(/"instance/");}"); cc.addConstructor(constructor); CtConstructor pc = new CtConstructor(new CtClass[]{pool.get(String.class.getName())}, cc); // $0=this , $1,$2,$3... 代表方法参数 , 因为非静态方法默认就是带一个this指针,默认就是第0个参数 pc.setBody("{$0.name = $1;System.out.println(/"instance/");}"); cc.addConstructor(pc); Class<?> GavaBeanClass = cc.toClass(); Object tom = GavaBeanClass.getConstructor(String.class).newInstance("tom"); Method method = GavaBeanClass.getMethod("getName"); Object invoke = method.invoke(tom); System.out.println(invoke); cc.writeFile("D://代码库//aop"); } }
由于前面插入的 insert before 和 after 两者之间还不能有共通的变量 , 所以很麻烦, , 还取不到方法体. 等我修炼.