使用java.lang.instrument实现第三方jar包内类的修改,包括引入外部依赖,参数获取

最近项目开发需求中,使用了第三方供应商提供的jar包形式的sdk ,sdk中的日志由其自己管理打印,现在想获取到日志打印时传入的message,就必须想办法对sdk的源码进行改动。

首先想到的是反编译jar包,然后修改后重新打包,尝试了一下后感觉很麻烦,而且很不cool。后来就查到了javaassist工具可以完美解决这个问题,可以实现我们熟悉的AOP功能,记录下使用过程。

javaassist是通过类加载层面,通过修改class文件代替原来的class来实现我们的目的的。

首先建立一个agent代理项目,pom结构如下:


    
        
            org.javassist
            javassist
            3.23.1-GA
        

        
            org.ow2.asm
            asm-all
            5.1
        
        
            net.bytebuddy
            byte-buddy
            1.5.7
        
        
            net.bytebuddy
            byte-buddy-agent
            1.5.7
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    1.8
                    1.8
                
            
            
                org.apache.maven.plugins
                maven-source-plugin
                3.0.1
                
                    
                        attach-sources
                        verify
                        
                            jar-no-fork
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-assembly-plugin
                2.6
                
                    
                        jar-with-dependencies
                    
                    
                        src/main/resources/META-INF/MANIFEST.MF
                    
                
                
                    
                        assemble-all
                        package
                        
                            single
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-shade-plugin
                
                    
                        package
                        
                            shade
                        
                    
                
                
                    
                        
                            javassist:javassist:jar:
                            net.bytebuddy:byte-buddy:jar:
                            net.bytebuddy:byte-buddy-agent:jar:
                        
                    
                
            
        
        
            
                ${basedir}/src/main/resources
            
            
                ${basedir}/src/main/java
            
        
    

需要注意的是我们需要建立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 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包。

 

 

你可能感兴趣的:(使用java.lang.instrument实现第三方jar包内类的修改,包括引入外部依赖,参数获取)