자바 바이트 프레임 asm 빠 른 입문

12181 단어 BytecodeJVM
asm 는 자바 의 바이트 코드 프레임 워 크 로 동적 생 성 클래스 나 기 존 기능 을 강화 하 는 데 사용 된다.
일반 asm 의 응용 장면 은 주로 op 에 있 습 니 다. 예 를 들 어 Spring 은 밑바닥 에 asm 를 사 용 했 습 니 다. 그러나 asm 는 op 방면 에서 만 강력 한 능력 을 발휘 할 수 있 는 것 이 아 닙 니 다. 예 를 들 어 지금 rpc 프레임 워 크 를 쓰 려 면 직렬 화 대상 에서 어려움 을 겪 을 수 있 습 니 다. 자바 의 직렬 화 체 제 를 사용 하 시 겠 습 니까?좀 느리다json?(예 를 들 어 아 리 의 대신 이 만 든 fastjson 은 성능 이 좋 습 니 다) 그러나 저 는 서열 화 된 대상 이 간단 하고 포 함 된 대상 이 없 으 며 깊이 복사 할 필요 가 없습니다. 그리고 저 는 json 의 형식 에 따라 하고 싶 지 않 습 니 다.
내 계획 은 대상 을 다음 과 같은 형식의 문자열 로 맞 추 는 것 이다.

fieldName1,Type1,value1;fieldName2,Type2,value2;fieldName3,Type3,value3;fieldName4,Type4,value4;...;

반사 로 이 루어 진 코드 는 대체로 다음 과 같다.

for (Map.Entry> iter : argFields.entrySet()) {
fieldName = iter.getKey(); //
pair = iter.getValue();
fieldType = pair.getKey(); //
fieldValue = pair.getValue().get(event); // ( )
if (fieldValue instanceof Date) {
sBuilder.append(fieldName).append(",").append(fieldType).append(",").append(((Date) fieldValue).getTime()).append(";");
} else {
sBuilder.append(fieldName).append(",").append(fieldType).append(",").append(fieldValue).append(";");
}
}

속 도 는 괜 찮 지만 저 는 더 빠 를 수 있다 고 생각 합 니 다. 그래서 저 는 asm 으로 반 사 를 대체 할 생각 을 했 습 니 다. 개인 적 으로 바이트 코드 에 대한 명령 은 조금 알 고 있 기 때문에 저 는 200 개의 바이트 코드 명령 으로 제 가 원 하 는 종 류 를 직접 쓰 는 것 이 어렵 습 니 다. 그래서 저 는 먼저 자바 로 제 가 원 하 는 종 류 를 썼 습 니 다.그리고
javap -verbose XXX

그것 을 역 컴 파일 한 후에 역 컴 파일 된 바이트 코드 명령 과 주석 에 따라 한 걸음 한 걸음 마지막 으로 클래스 를 완성 했다.
아래 의 이 종 류 는 내 가 원 하 는 종류 이다. 나 는 먼저 자바 로 그것 을 완성 했다.

public StringBuilder write(Event event) throws IOException {
GNSEvent obj = (GNSEvent) event;
StringBuilder sBuilder = new StringBuilder(256);
sBuilder.append("cityCode").append(",").append("java.lang.String").append(obj.getCityCode());
// obj obj
return sBuilder;
}

javap 로 역 컴 파일 하여 다음 에 유용 한 정 보 를 얻 을 수 있 습 니 다:

public com.futurefleet.framework.serialization.Serializer_1GNSEvent();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."":()V
4: return
LineNumberTable:
line 16: 0

LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/futurefleet/framework/serialization/Serializer_1GNSEvent;


public java.lang.StringBuilder write(com.futurefleet.framework.base.event.Event) throws java.io.IOException;
Exceptions:
throws java.io.IOException Code:
Stack=3, Locals=4, Args_size=2
0: aload_1
1: checkcast #21; //class com/futurefleet/gateway/event/GNSEvent
4: astore_2
5: new #23; //class java/lang/StringBuilder
8: dup
9: sipush 256
12: invokespecial #25; //Method java/lang/StringBuilder."":(I)V
15: astore_3
16: aload_3
17: ldc #28; //String cityCode
19: invokevirtual #30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc #34; //String ,
24: invokevirtual #30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: ldc #36; //String java.lang.String
29: invokevirtual #30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: aload_2
33: invokevirtual #38; //Method com/futurefleet/gateway/event/GNSEvent.getCityCode:()Ljava/lang/String;
36: invokevirtual #30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: pop
40: aload_3
41: areturn


위의 구조 함수 와 write 방법 은 바로 제 가 필요 로 하 는 명령 순서 입 니 다. 뒤의 주석 정보 도 매우 중요 합 니 다. 자신의 뜻 대로 하 세 요. 반드시 사용 해 야 합 니 다.
이 걸 따라 해서 원 하 는 종 류 를 asm 으로 직접 편집 해 주시 면 됩 니 다.
상위 클래스 의 정의 및 구조 함수:

ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object",
new String[] { "com/futurefleet/framework/serialization/EventSerializer" });

//
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, null);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();

[color = red] asm 에 있 는 바이트 코드 명령 과 인자, 그리고 Stack, Locals 의 값 을 자세히 살 펴 보면 자바 p 가 역 컴 파일 한 것 과 똑 같 습 니 다. 친 [/ color]
여기 서 다시 한 번 설명 하 겠 습 니 다. 예 를 들 어 ALOAD 명령 은 MethodVisitor 의 어떤 방법의 매개 변수 인지 어떻게 압 니까?이것 은 매우 간단 합 니 다. org. objectweb. asm. Opcodes 이 인터페이스 에 있 는 설명 을 참조 하면 됩 니 다. 예 를 들 어:

int ILOAD = 21; // visitVarInsn
int LLOAD = 22; // -
int FLOAD = 23; // -
int DLOAD = 24; // -
int ALOAD = 25; // -

ILOAD, LLOAD, FLOAD 등 이 visitVarInsn 방법의 매개 변수 라 는 뜻 이다.
그리고 가장 중요 한, 내 가 필요 로 하 는 write 방법의 코드

// StringBuilder write(Event event) throws IOException;
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "write", "(Lcom/futurefleet/framework/base/event/Event;)Ljava/lang/StringBuilder;", null,
new String[] { "java/io/IOException" });
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitTypeInsn(CHECKCAST, eventTypeString);
methodVisitor.visitVarInsn(ASTORE, 2);
methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
methodVisitor.visitInsn(DUP);
methodVisitor.visitIntInsn(SIPUSH, 256);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(I)V");
methodVisitor.visitVarInsn(ASTORE, 3);
methodVisitor.visitVarInsn(ALOAD, 3);

FieldInfo fieldInfo = null;
Class> fieldClass = null;
for (Map.Entry iter : getters.entrySet()) {
fieldInfo = iter.getValue();
fieldClass = TypeUtils.getClass(fieldInfo.getFieldType());

methodVisitor.visitLdcInsn(fieldInfo.getName());
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
methodVisitor.visitLdcInsn(",");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
methodVisitor.visitLdcInsn(TypeUtils.getClass(fieldInfo.getFieldType()).getName());
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
methodVisitor.visitLdcInsn(",");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, eventTypeString, fieldInfo.getMethod().getName(), TypeUtils.getDesc(fieldInfo.getMethod()));

if (fieldClass == Date.class || fieldClass == java.sql.Date.class || fieldClass == java.sql.Time.class
|| fieldClass == java.sql.Timestamp.class) {
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, TypeUtils.getType(fieldClass), "getTime", "()J");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
} else {
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
"(" + TypeUtils.getDesc(TypeUtils.getClass(fieldInfo.getFieldType())) + ")Ljava/lang/StringBuilder;");
}
methodVisitor.visitLdcInsn(";");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
}
methodVisitor.visitInsn(POP);
methodVisitor.visitVarInsn(ALOAD, 3);
methodVisitor.visitInsn(ARETURN);
methodVisitor.visitMaxs(3, 4);
methodVisitor.visitEnd();
}

byte[] classCode = classWriter.toByteArray();

// {
// FileOutputStream fos = new FileOutputStream(new File("/Users/fengjiachun/Documents/" + className + ".class"));
// fos.write(classCode, 0, classCode.length);
// }

Class> asmClass = this.classLoader.defineClassPublic(className, classCode, 0, classCode.length);

관련 클래스 로 더 는 붙 이지 않 습 니 다. 주로 define Class 를 공개 하면 됩 니 다.
FieldInfo 는 직렬 화 할 클래스 의 대응 정 보 를 저장 하 는 데 사 용 됩 니 다. 데이터 구 조 는 다음 과 같 습 니 다.

private final String name; //
private final Method method; // ( getter, setter)
private final Field field; //

코드 에 주석 이 달 린 코드 를 열 고 생 성 된 class 파일 을 디스크 에 기록 한 다음 JD 등 역 컴 파일 도구 로 성 과 를 봅 시다.

public class Serializer_1GNSEvent
implements EventSerializer
{
public StringBuilder write(Event paramEvent)
throws IOException
{
GNSEvent localGNSEvent = (GNSEvent)paramEvent;
StringBuilder localStringBuilder = new StringBuilder(256);
localStringBuilder.append("serverTimeStamp").append(",").append("long").append(",").append(localGNSEvent.getServerTimeStamp()).append(";").append("hasOffset").append(",").append("java.lang.String").append(",").append(localGNSEvent.getHasOffset()).append(";").append("clientTimeStamp").append(",").append("long").append(",").append(localGNSEvent.getClientTimeStamp()).append(";").append("liveCity").append(",").append("java.lang.String").append(",").append(localGNSEvent.getLiveCity()).append(";").append("cityCode").append(",").append("java.lang.String").append(",").append(localGNSEvent.getCityCode()).append(";").append("lng").append(",").append("double").append(",").append(localGNSEvent.getLng()).append(";").append("type").append(",").append("int").append(",").append(localGNSEvent.getType()).append(";").append("lat").append(",").append("double").append(",").append(localGNSEvent.getLat()).append(";").append("version").append(",").append("java.lang.String").append(",").append(localGNSEvent.getVersion()).append(";").append("parseSucceed").append(",").append("boolean").append(",").append(localGNSEvent.isParseSucceed()).append(";");
return localStringBuilder;
}
}

생 성 된 이 종 류 는 캐 시 를 해 야 합 니 다. 이렇게 같은 유형의 대상 의 직렬 화 는 새로운 종 류 를 다시 만 들 필요 가 없습니다. 구체 적 으로 는 이번 내용 과 무관 하 므 로 자세히 말 하지 않 겠 습 니 다.
그 다음 에 똑 같은 사고방식 에 따라 반 서열 화 된 유형 을 생 성하 면 된다. 똑 같은 이 치 는 중복 되 지 않 는 다.
마지막 으로 테스트 를 통 해 반사 효율 보다 높 습 니 다. 제 가 생 성 한 직렬 화 와 반 직렬 화 류 는 현재 의 수요 에 매우 적합 하고 불필요 한 코드 가 없 기 때문에 제 응용 장면 에 있어 fastjson 을 사용 하여 대상 을 json 으로 서열 화 하 는 것 보다 빠 릅 니 다. 한 마디 로 하면 자신 에 게 맞 는 것 이 가장 좋 은 것 입 니 다.
후속: 우리 산포 입 니 다. asm 홈 페이지 에 플러그 인 [url] 이 있 습 니 다.http://asm.ow2.org/eclipse/index.html[/url]
설치 후 기본적으로 코드 를 직접 생 성 할 수 있 고 왼쪽 코드 를 직접 복사 하면 됩 니 다. 다음 그림 과 같 습 니 다.
[img]http://dl.iteye.com/upload/attachment/0079/6927/3d040ca3-7779-321c-83ab-8bc1e15ce53f.png[/img]

좋은 웹페이지 즐겨찾기