0 前言
前一節(jié)講述了基于JVMTI如何實現(xiàn)Agent,還有一種是基于Java Instrument API實現(xiàn)Agent唯咬,可以在Java代碼層面編寫Agent代碼,而非基于C++/C的代碼共郭,具體使用可參考《Java Instrument 功能使用及原理》:
以 -javaagent:為開頭的默認為instrument的agent郭脂;
那么以上這兩種Agent實現(xiàn)方式稽荧,又是在JVMTI源碼中如何運行工作呢橘茉?
1 初始化 Agent
在JVM啟動時,會讀取JVM命令行參數(shù) -agentlib -agentpath -javaagent
,并構(gòu)建了Agent Library鏈表捺癞。初始化 Agent 代碼如下:
if (match_option(option, "-agentlib:", &tail) || (is_absolute_path = match_option(option, "-agentpath:", &tail))) {
if(tail != NULL) {
const char* pos = strchr(tail, '=');
size_t len = (pos == NULL) ? strlen(tail) : pos - tail;
char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len);
name[len] = '\0';
char *options = NULL;
if(pos != NULL) {
options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(pos + 1) + 1), pos + 1);
}
if ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) {
warning("profiling and debugging agents are not supported with Kernel VM");
} else if
// JVMTI_KERNEL 構(gòu)建Agent Library鏈表
add_init_agent(name, options, is_absolute_path);
}
} else if (match_option(option, "-javaagent:", &tail)) {
// -javaagent
if(tail != NULL) {
char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(tail) + 1), tail);
// 構(gòu)建Agent Library鏈表
add_init_agent("instrument", options, false);
}
// -Xnoclassgc
}
2 加載Agent鏈接庫
在啟動JVM create_vm
時,對agent鏈表中的每個agent庫构挤,加載所指定的動態(tài)庫髓介, 并調(diào)用里面的Agent_OnLoad方法,比如:對于Java Instrument Agent加載就是對libinstrument的動態(tài)庫instrument.so加載:
// Create agents for -agentlib: -agentpath: and converted -Xrun
void Threads::create_vm_init_agents() {
extern struct JavaVM_ main_vm;
AgentLibrary* agent;
JvmtiExport::enter_onload_phase();
for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {
OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent);
if (on_load_entry != NULL) {
// 調(diào)用 Agent_OnLoad 函數(shù)
jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);
if (err != JNI_OK) {
vm_exit_during_initialization("agent library failed to init", agent->name());
}
} else {
vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());
}
}
JvmtiExport::enter_primordial_phase();
}
3 創(chuàng)建 Instrument JPLISAgent
在方法Agent_OnLoad中創(chuàng)建一個新的 JPLISAgent(Java Programming Language Instrumentation Services Agent)筋现,初始化了類和包里的配置文件唐础,并且同時從Vm環(huán)境中獲取了 jvmtiEnv 的環(huán)境。
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jint result = JNI_OK;
JPLISAgent * agent = NULL;
// 創(chuàng)建一個新的JPLISAgent對象
initerror = createNewJPLISAgent(vm, &agent);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
if (parseArgumentTail(tail, &jarfile, &options) != 0) {
fprintf(stderr, "-javaagent: memory allocation failure.\n");
return JNI_ERR;
}
attributes = readAttributes(jarfile);
if (attributes == NULL) {
fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
return JNI_ERR;
}
premainClass = getAttribute(attributes, "Premain-Class");
if (premainClass == NULL) {
fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ERR;
}
/*
* Add to the jarfile 把jar文件追加到agent的classpath中矾飞。
*/
appendClassPath(agent, jarfile);
……
}
在代碼中一膨,可以看到在 讀取jar的配置文件MANIFEST里Premain-Class,并且把jar文件追加到agent的class path中洒沦。
4 JVMTI 回調(diào)接口注冊與執(zhí)行
以下是JVMTI的一些回調(diào)接口豹绪,通過這些回調(diào)接口設置回調(diào)函數(shù)指針:
typedef struct {
/* 50 : VM Initialization Event */
jvmtiEventVMInit VMInit;
/* 51 : VM Death Event */
jvmtiEventVMDeath VMDeath;
/* 52 : Thread Start */
jvmtiEventThreadStart ThreadStart;
/* 53 : Thread End */
jvmtiEventThreadEnd ThreadEnd;
/* 54 : Class File Load Hook */
jvmtiEventClassFileLoadHook ClassFileLoadHook;
/* 55 : Class Load */
jvmtiEventClassLoad ClassLoad;
/* 56 : Class Prepare */
jvmtiEventClassPrepare ClassPrepare;
/* 57 : VM Start Event */
jvmtiEventVMStart VMStart;
/* 58 : Exception */
jvmtiEventException Exception;
/* 59 : Exception Catch */
jvmtiEventExceptionCatch ExceptionCatch;
/* 60 : Single Step */
jvmtiEventSingleStep SingleStep;
/* 61 : Frame Pop */
jvmtiEventFramePop FramePop;
/* 62 : Breakpoint */
jvmtiEventBreakpoint Breakpoint;
/* 63 : Field Access */
jvmtiEventFieldAccess FieldAccess;
/* 64 : Field Modification */
jvmtiEventFieldModification FieldModification;
/* 65 : Method Entry */
jvmtiEventMethodEntry MethodEntry;
/* 66 : Method Exit */
jvmtiEventMethodExit MethodExit;
/* 67 : Native Method Bind */
jvmtiEventNativeMethodBind NativeMethodBind;
/* 68 : Compiled Method Load */
jvmtiEventCompiledMethodLoad CompiledMethodLoad;
/* 69 : Compiled Method Unload */
jvmtiEventCompiledMethodUnload CompiledMethodUnload;
/* 70 : Dynamic Code Generated */
jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
/* 71 : Data Dump Request */
jvmtiEventDataDumpRequest DataDumpRequest;
/* 72 */
jvmtiEventReserved reserved72;
/* 73 : Monitor Wait */
jvmtiEventMonitorWait MonitorWait;
/* 74 : Monitor Waited */
jvmtiEventMonitorWaited MonitorWaited;
/* 75 : Monitor Contended Enter */
jvmtiEventMonitorContendedEnter MonitorContendedEnter;
/* 76 : Monitor Contended Entered */
jvmtiEventMonitorContendedEntered MonitorContendedEntered;
/* 77 */
jvmtiEventReserved reserved77;
/* 78 */
jvmtiEventReserved reserved78;
/* 79 */
jvmtiEventReserved reserved79;
/* 80 : Resource Exhausted */
jvmtiEventResourceExhausted ResourceExhausted;
/* 81 : Garbage Collection Start */
jvmtiEventGarbageCollectionStart GarbageCollectionStart;
/* 82 : Garbage Collection Finish */
jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
/* 83 : Object Free */
jvmtiEventObjectFree ObjectFree;
/* 84 : VM Object Allocation */
jvmtiEventVMObjectAlloc VMObjectAlloc;
} jvmtiEventCallbacks;
4.1 執(zhí)行jvmtiEventVMInit的回調(diào)函數(shù)
虛擬機在創(chuàng)建create_vm
的時候,初始化了JVMTI環(huán)境后會執(zhí)行JvmtiExport::post_vm_initialized();
方法申眼,代碼如下:
void JvmtiExport::post_vm_initialized() {
EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Trg VM init event triggered" ));
// can now enable events
JvmtiEventController::vm_init();
JvmtiEnvIterator it;
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
if (env->is_enabled(JVMTI_EVENT_VM_INIT)) {
EVT_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Evt VM init event sent" ));
JavaThread *thread = JavaThread::current();
JvmtiThreadEventMark jem(thread);
JvmtiJavaThreadEventTransition jet(thread);
jvmtiEventVMInit callback = env->callbacks()->VMInit;
if (callback != NULL) {
// 調(diào)用了VMInit的回調(diào)函數(shù)
(*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());
}
}
}
}
4.2 執(zhí)行jvmtiEventClassFileLoadHook的回調(diào)函數(shù)
鉤子方法是jvmtiEventClassFileLoadHook的回調(diào)方法瞒津,代碼在classFileParser的文件中:
instanceKlassHandle ClassFileParser::parseClassFile(symbolHandle name, Handle class_loader,Handle protection_domain, KlassHandle host_klass, GrowableArray<Handle>* cp_patches, symbolHandle& parsed_name,bool verify, TRAPS) {
……
if (JvmtiExport::should_post_class_file_load_hook()) {
unsigned char* ptr = cfs->buffer();
unsigned char* end_ptr = cfs->buffer() + cfs->length();
JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain,
&ptr, &end_ptr,
&cached_class_file_bytes,
&cached_class_file_length);
if (ptr != cfs->buffer()) {
// JVMTI agent has modified class file data.
// Set new class file stream using JVMTI agent modified
// class file data.
cfs = new ClassFileStream(ptr, end_ptr - ptr, cfs->source());
set_stream(cfs);
}
}
…
}
在jvmtiexport::post_class_file_load_hook
函數(shù)最后 調(diào)用了post_to_env()函數(shù):
void post_to_env(JvmtiEnv* env, bool caching_needed) {
unsigned char *new_data = NULL;
jint new_len = 0;
JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,
_h_protection_domain,
_h_class_being_redefined);
JvmtiJavaThreadEventTransition jet(_thread);
JNIEnv* jni_env = (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)? NULL : jem.jni_env();
jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook;
if (callback != NULL) {
(*callback)(env->jvmti_external(), jni_env,
jem.class_being_redefined(),
jem.jloader(), jem.class_name(),
jem.protection_domain(),
_curr_len, _curr_data,
&new_len, &new_data);
}
......
}
函數(shù)中調(diào)用了jvmtiEventClassFileLoadHook的回調(diào)函數(shù),也就是剛才在結(jié)構(gòu)體中定義的jvmtiEventCallbacks括尸。鉤子函數(shù)eventHandlerClassFileLoadHook:
void JNICALL eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jclass class_being_redefined,
jobject loader,
const char* name,
jobject protectionDomain,
jint class_data_len,
const unsigned char* class_data,
jint* new_class_data_len,
unsigned char** new_class_data) {
JPLISEnvironment * environment = NULL;
environment = getJPLISEnvironment(jvmtienv);
/* if something is internally inconsistent (no agent), just silently return without touching the buffer */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
transformClassFile(environment->mAgent,
jnienv,
loader,
name,
class_being_redefined,
protectionDomain,
class_data_len,
class_data,
new_class_data_len,
new_class_data,
environment->mIsRetransformer);
restoreThrowable(jnienv, outstandingException);
}
}
重要的是transformClassFile函數(shù)巷蚪,看看它究竟做了啥事情:
transformedBufferObject = (*jnienv)->CallObjectMethod(
jnienv,
agent->mInstrumentationImpl,
agent->mTransform,
loaderObject,
classNameStringObject,
classBeingRedefined,
protectionDomain,
classFileBufferObject,
is_retransformer);
也就是調(diào)用了InstrumentationImpl里的transform方法,在InstrumentationImpl類里通過TransformerManager的transform的方法最終調(diào)用我們自定義的MyTransformer的類的transform方法濒翻。
private byte[] transform(ClassLoader var1, String var2, Class var3, ProtectionDomain var4, byte[] var5, boolean var6) {
TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager;
return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5);
}
4.3 注冊鉤子函數(shù)jvmtiEventClassFileLoadHook
如上屁柏,那么鉤子函數(shù)jvmtiEventClassFileLoadHook是何時注冊的,回到剛才創(chuàng)建新的JPLISAgent代碼中:
JPLISInitializationError createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jvmtiEnv * jvmtienv = NULL;
jint jnierror = JNI_OK;
*agent_ptr = NULL;
jnierror = (*vm)->GetEnv(vm,(void **) &jvmtienv,JVMTI_VERSION);
if (jnierror != JNI_OK) {
initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;
} else {
JPLISAgent * agent = allocateJPLISAgent(jvmtienv);
if (agent == NULL) {
initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
} else {
initerror = initializeJPLISAgent(agent, vm, jvmtienv);
if (initerror == JPLIS_INIT_ERROR_NONE) {
*agent_ptr = agent;
} else {
deallocateJPLISAgent(jvmtienv, agent);
}
}
/* don't leak envs */
if ( initerror != JPLIS_INIT_ERROR_NONE ) {
jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
}
return initerror;
}
函數(shù)initializeJPLISAgent初始化了JPLISAgent:
JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent,JavaVM * vm,jvmtiEnv * jvmtienv) {
……
checkCapabilities(agent);
jvmtierror == (*jvmtienv)->GetPhase(jvmtienv, &phase);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
if (phase == JVMTI_PHASE_LIVE) {
return JPLIS_INIT_ERROR_NONE;
}
/* now turn on the VMInit event */
if ( jvmtierror == JVMTI_ERROR_NONE ) {
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMInit = &eventHandlerVMInit;
jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks, sizeof(callbacks));
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
……
}
JPLISAgent里首先 注冊了一個VMInit的初始化函數(shù)eventHandlerVMInit有送,跟蹤eventHandlerVMInit函數(shù):
void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jthread thread) {
JPLISEnvironment * environment = NULL;
jboolean success = JNI_FALSE;
environment = getJPLISEnvironment(jvmtienv);
/* process the premain calls on the all the JPL agents */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
success = processJavaStart( environment->mAgent,
jnienv);
restoreThrowable(jnienv, outstandingException);
}
/* if we fail to start cleanly, bring down the JVM */
if ( !success ) {
abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART);
}
}
在processJavaStart里:
jboolean processJavaStart(JPLISAgent * agent, JNIEnv * jnienv) {
jboolean result;
result = initializeFallbackError(jnienv);
jplis_assert(result);
if ( result ) {
result = createInstrumentationImpl(jnienv, agent);
jplis_assert(result);
}
if ( result ) {
result = setLivePhaseEventHandlers(agent);
jplis_assert(result);
}
if ( result ) {
result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller);
}
if ( result ) {
deallocateCommandLineData(agent);
}
return result;
}
在setLivePhaseEventHandler函數(shù)中注冊了淌喻,代碼如下:
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
5 JPLISAgent結(jié)構(gòu)體
struct _JPLISAgent {
JavaVM * mJVM; /* handle to the JVM */
JPLISEnvironment mNormalEnvironment; /* for every thing but retransform stuff */
JPLISEnvironment mRetransformEnvironment;/* for retransform stuff only */
jobject mInstrumentationImpl; /* handle to the Instrumentation instance */
jmethodID mPremainCaller; /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
jmethodID mAgentmainCaller; /* method on the InstrumentationImpl for agents loaded via attach mechanism */
jmethodID mTransform; /* method on the InstrumentationImpl that does the class file transform */
jboolean mRedefineAvailable; /* cached answer to "does this agent support redefine" */
jboolean mRedefineAdded; /* indicates if can_redefine_classes capability has been added */
jboolean mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */
jboolean mNativeMethodPrefixAdded; /* indicates if can_set_native_method_prefix capability has been added */
char const * mAgentClassName; /* agent class name */
char const * mOptionsString; /* -javaagent options string */
};
struct _JPLISEnvironment {
jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */
JPLISAgent * mAgent; /* corresponding agent */
jboolean mIsRetransformer; /* indicates if special environment */
};
- mNormalEnvironment:agent環(huán)境;
- mRetransformEnvironment:retransform環(huán)境娶眷;
- mInstrumentationImpl:sun自己提供的instrument對象似嗤;
- mPremainCaller:
sun.instrument.InstrumentationImpl.loadClassAndCallPremain
方法,agent啟動時加載會被調(diào)用該方法届宠;- mAgentmainCaller:
sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain
方法烁落,agent attach動態(tài)加載agent的時會被調(diào)用該方法;- mTransform:
sun.instrument.InstrumentationImpl.transform
方法豌注;- mAgentClassName:javaagent的MANIFEST.MF里指定的
Agent-Class
伤塌;- mOptionsString:agent初始參數(shù);
- mRedefineAvailable:MANIFEST.MF里的參數(shù)
Can-Redefine-Classes:true
轧铁;- mNativeMethodPrefixAvailable:MANIFEST.MF里的參數(shù)
Can-Set-Native-Method-Prefix:true
每聪;- mIsRetransformer:MANIFEST.MF里的參數(shù)
Can-Retransform-Classes:true
;
在startJavaAgent的方法中調(diào)用了啟動JPLISAgent的方式,我們來看invokeJavaAgentMainMethod
:
jboolean invokeJavaAgentMainMethod(JNIEnv * jnienv,
jobject instrumentationImpl,
jmethodID mainCallingMethod,
jstring className,
jstring optionsString) {
jboolean errorOutstanding = JNI_FALSE;
jplis_assert(mainCallingMethod != NULL);
if (mainCallingMethod != NULL ) {
(*jnienv)->CallVoidMethod(jnienv,
instrumentationImpl,
mainCallingMethod,
className,
optionsString);
errorOutstanding = checkForThrowable(jnienv);
if ( errorOutstanding ) {
logThrowable(jnienv);
}
checkForAndClearThrowable(jnienv);
}
return !errorOutstanding;
}
在函數(shù)里药薯,實際上是調(diào)用java類sun.instrument.InstrumentationImpl 類里的方法loadClassAndCallPremain绑洛。
private void loadClassAndCallPremain(String var1, String var2) throws Throwable {
this.loadClassAndStartAgent(var1, "premain", var2);
}
private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {
this.loadClassAndStartAgent(var1, "agentmain", var2);
}
繼續(xù)查看Java的sun.instrument.InstrumentationImpl類的方法loadClassAndStartAgent:
private void loadClassAndStartAgent(String classname,
String methodname,
String optionsString) throws Throwable {
...
try {
m = javaAgentClass.getDeclaredMethod( methodname,
new Class<?>[] {
String.class,
java.lang.instrument.Instrumentation.class
}
);
twoArgAgent = true;
} catch (NoSuchMethodException x) {
// remember the NoSuchMethodException
firstExc = x;
}
if (m == null) {
// now try the declared 1-arg method
try {
m = javaAgentClass.getDeclaredMethod(methodname, new Class<?>[] { String.class });
} catch (NoSuchMethodException x) {
// ignore this exception because we'll try
// two arg inheritance next
}
}
if (m == null) {
// now try the inherited 2-arg method
try {
m = javaAgentClass.getMethod( methodname,
new Class<?>[] {
String.class,
java.lang.instrument.Instrumentation.class
}
);
twoArgAgent = true;
} catch (NoSuchMethodException x) {
// ignore this exception because we'll try
// one arg inheritance next
}
}
if (m == null) {
// finally try the inherited 1-arg method
try {
m = javaAgentClass.getMethod(methodname, new Class<?>[] { String.class });
} catch (NoSuchMethodException x) {
// none of the methods exists so we throw the
// first NoSuchMethodException as per 5.0
throw firstExc;
}
}
// the premain method should not be required to be public,
// make it accessible so we can call it
// Note: The spec says the following:
// The agent class must implement a public static premain method...
setAccessible(m, true);
// invoke the 1 or 2-arg method
if (twoArgAgent) {
m.invoke(null, new Object[] { optionsString, this });
} else {
m.invoke(null, new Object[] { optionsString });
}
// don't let others access a non-public premain method
setAccessible(m, false);
}
在InstrumentationImpl的類中初始化了我們自定義的Transformer的premain方法:
public class MyInjectTransformer implements ClassFileTransformer{
public static void premain(String options, Instrumentation ins) {
ins.addTransformer(new SQLInjectTransformer());
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
return null;
}
}
6 工作原理及運行時序圖
6.1 啟動并創(chuàng)建JVM,注冊vmInit回調(diào)函數(shù)
6.2 執(zhí)行vmInit回調(diào)函數(shù)童本,注冊jvmtiEventClassFileLoadHook回調(diào)函數(shù)真屯,加載并初始化 Instrument Agent
6.3 加載解析Class文件绑蔫,執(zhí)行jvmtiEventClassFileLoadHook回調(diào)函數(shù)
6.4 以-javaagent為例泵额,工作原理
- 在JVM啟動時配深,通過JVM參數(shù)-javaagent,傳入agent jar嫁盲,Instrument Agent被加載篓叶;
- 在Instrument Agent 初始化時,注冊了JVMTI初始化函數(shù)eventHandlerVMinit亡资;
- 在JVM啟動時澜共,會調(diào)用初始化函數(shù)eventHandlerVMinit,啟動了Instrument Agent锥腻,用sun.instrument.instrumentationImpl類里的方法loadClassAndCallPremain方法去初始化Premain-Class指定類的premain方法嗦董;
- 初始化函數(shù)eventHandlerVMinit,注冊了class解析的ClassFileLoadHook函數(shù)瘦黑;
- 在解析Class之前京革,JVM調(diào)用JVMTI的ClassFileLoadHook函數(shù),鉤子函數(shù)調(diào)用sun.instrument.instrumentationImpl類里的transform方法幸斥,通過TransformerManager的transformer方法最終調(diào)用我們自定義的Transformer類的transform方法匹摇;
- 因為字節(jié)碼在解析Class之前改的,直接使用修改后的字節(jié)碼的數(shù)據(jù)流替代甲葬,最后進入Class解析廊勃,對整個Class解析無影響;
- 重新加載Class依然重新走5-6步驟经窖;