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 两者之间还不能有共通的变量 , 所以很麻烦, , 还取不到方法体. 等我修炼.