asm杂耍第一弹,使用asm实现动态代理
asm:用来操作字节码的框架,可以对class文件进行CRUD,听这名字就知道是什么东西,在写c代码的时候来一句ASM装逼,这个就像java的汇编一样。
User接口
/**
* @author authorZhao
* @date 2019/12/30
*/
public interface User {
/**
* 获取名字
* @return
*/
String getName();
/**
* 设置名字
* @param name
*/
void setName(String name);
/**
* 获取年龄
* @return
*/
Integer getAge();
/**
* 设施自年龄
* @param age
*/
void setAge(Integer age) ;
}
测试代码
/**
* @author authorZhao
* @date 2019/12/30
*/
public class TestUser {
public static void main(String[] args) {
try {
Class uClass = UserImplUtils.getClass("com.qdz.proxy.asm.test1.UserImpl", User.class);
User user = (User)uClass.getConstructors()[0].newInstance();
user.setName("渣渣辉");
user.setAge(385);
System.out.println("带噶好,我系"+user.getName()+",我今天"+user.getAge()+"岁");
} catch (Exception e) {
e.printStackTrace();
}
}
}
User实现类
上文在不采用jdk和从cglib动态代理的情况下生成了user实现类,采用的是asm框架
上代码
/**
* @author authorZhao
* @date 2019/12/30
*/
public class UserImplUtils {
/**
* 根据类全名和接口名生成class文件
* @param className 类全名,注意是用点分隔的,采用idea的
* @param clazz 接口class对象 采用点分隔
* @return
* @throws Exception
*/
private static byte[] dump(String className,Class clazz) throws Exception {
String className2 = className.replace(".","/");
String sourceName = className.substring(className.lastIndexOf(".")+1)+".java";
//模仿常量池的语法
String constClassName = "L"+className2+";";//Lcom/qdz/proxy/asm/test1/UserImpl;
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
//声明需要实现的类
//jdk版本 11,权限,这个不懂,类名,签名,父类名,接口名
cw.visit(55, ACC_PUBLIC + ACC_SUPER, className2, null, "java/lang/Object", new String[]{clazz.getName().replace(".","/")});
//
cw.visitSource(sourceName, null);
//设置属性 name
// age
{
fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fv.visitEnd();
}
{
fv = cw.visitField(ACC_PRIVATE, "age", "Ljava/lang/Integer;", null, null);
fv.visitEnd();
}
//方法
{
//构造方法,语法固定
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(7, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
//这个this是固定写法
mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
//getName方法
{
mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(14, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className2, "name", "Ljava/lang/String;");
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
//setName方法
{
mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(18, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, className2, "name", "Ljava/lang/String;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(19, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", constClassName, null, l0, l2, 0);
mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
//getAge方法
{
mv = cw.visitMethod(ACC_PUBLIC, "getAge", "()Ljava/lang/Integer;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(22, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className2, "age", "Ljava/lang/Integer;");
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
//setAge方法
{
mv = cw.visitMethod(ACC_PUBLIC, "setAge", "(Ljava/lang/Integer;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(26, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, className2, "age", "Ljava/lang/Integer;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(27, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", constClassName, null, l0, l2, 0);
mv.visitLocalVariable("age", "Ljava/lang/Integer;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
/**
* 根据类名和接口返回对应的class对象
* @param className 类全名,注意是用点分隔的,采用idea的
* @param clazz 接口class对象 采用点分隔
* @return
* @throws Exception
*/
public static Class<?> getClass(String className,Class clazz ) throws Exception {
byte[] classData = dump(className, clazz);
return new MyClassLoader().defineClassForName(className, classData);
}
}
类加载器
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super(Thread.currentThread().getContextClassLoader());
}
/**
* 吧class数组转化为Class对象
* @param name 类全名
* @param data class数组
* @return
*/
public Class<?> defineClassForName(String name, byte[] data) {
return this.defineClass(name, data, 0, data.length);
}
}
说明:
-
为什么要定义接口
- 为了编辑器方便,免得反射方法名太麻烦
-
为什么这么生成
- 哈哈,我当然不会,先自己实现以下接口然使用asm工具生成代码
-
原创文章,转载请申明
附录:class文件结构
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;//55表示jdk11
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}