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];
}