使用java.lang.instrument實現第三方jar包內類的修改,包括引入外部依賴,引數獲取
阿新 • • 發佈:2018-12-11
最近專案開發需求中,使用了第三方供應商提供的jar包形式的sdk ,sdk中的日誌由其自己管理列印,現在想獲取到日誌列印時傳入的message,就必須想辦法對sdk的原始碼進行改動。
首先想到的是反編譯jar包,然後修改後重新打包,嘗試了一下後感覺很麻煩,而且很不cool。後來就查到了javaassist工具可以完美解決這個問題,可以實現我們熟悉的AOP功能,記錄下使用過程。
javaassist是通過類載入層面,通過修改class檔案代替原來的class來實現我們的目的的。
首先建立一個agent代理專案,pom結構如下:
<dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.23.1-GA</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-all</artifactId> <version>5.1</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.5.7</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.5.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> <executions> <execution> <id>assemble-all</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> <configuration> <artifactSet> <includes> <include>javassist:javassist:jar:</include> <include>net.bytebuddy:byte-buddy:jar:</include> <include>net.bytebuddy:byte-buddy-agent:jar:</include> </includes> </artifactSet> </configuration> </plugin> </plugins> <resources> <resource> <directory>${basedir}/src/main/resources</directory> </resource> <resource> <directory>${basedir}/src/main/java</directory> </resource> </resources> </build>
需要注意的是我們需要建立src/main/resources/META-INF/MANIFEST.MF才能使這個專案打出的jar包正確執行,內容如下:
Manifest-Version: 1.0
Premain-Class: com.xxx.agent.LogAgent
Can-Redefine-Classes: true
Boot-Class-Path: javassist.jar
Premain-Class是我們專案中要建立的主類的名稱,內容如下:
public class LogAgent { public static void premain(String agentArgs, Instrumentation inst) { ClassFileTransformer transformer = new FileSdkLoggerTransformer(); inst.addTransformer(transformer); } }
premain是在你要代理的專案的main函式之前執行的,在主專案執行之前就已經把我們的類給修改掉了。
FileSdkLoggerTransformer類是我們自己建的轉化類,用來攔截修改原專案中的類。
public class FileSdkLoggerTransformer implements ClassFileTransformer { private static final Set<String> classNameSet = new HashSet<>(); static { classNameSet.add("com.sportradar.sdk.common.classes.FileSdkLogger"); } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { if(className != null){ String currentClassName = className.replaceAll("/","."); if(!classNameSet.contains(currentClassName)) return null; ClassPool.getDefault().importPackage("com.xxx.betradar"); CtClass ctClass = ClassPool.getDefault().get(currentClassName); CtBehavior[] declaredBehaviors = ctClass.getMethods(); for (CtBehavior ctBehavior:declaredBehaviors) { if(ctBehavior.getName().equals("logTraffic")){ CtClass[] parameterTypes = ctBehavior.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { System.out.println(parameterTypes[i].toString()); } //增強方法 //ctBehavior.insertAt(0,"{ SDKStarter.getXmlQueue().add($1); }"); ctBehavior.insertAt(0,"{ SDKStarter.getXmlQueue().add(message); }"); } } return ctClass.toBytecode(); } } catch (Exception e) { e.printStackTrace(); } return null; } }
這裡要注意,如果要使用代理方法的引數,可以用$1這種方式,1表示引數佔位符index,也可以直接用引數名,比如程式碼中的message。
如果要在代理方法中使用別的類,比如自己寫的程式碼SDKStarter,需要將這個類的路徑引入,相當於import,ClassPool.getDefault().importPackage("com.xxx.betradar"); 。
執行原專案時,加上-javaagent:xxx.jar即可,xxx.jar是代理專案打出來的jar包。