利用Java Agent 做Spring MVC Controller 层的出参入参打印日志

许多开发使用的spring aop来做切面 今天尝试一下使用JVM层面的切面

1、建立 agent jar工程

创建工程 logging-agent 使用POM为 javassist 日志

如下:使用了字节码 javassist

如果想处理springaop 代理的请增加依赖否则就不需要

XML 复制代码
   <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.21</version> <!-- 确保使用最新版本 -->
        </dependency>
        <!-- AspectJ Weaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version> <!-- 确保使用最新版本 -->
        </dependency>

为了方便使用fastJSON 做序列化

完整POM如下

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>logging-agent</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.21</version> <!-- 确保使用最新版本 -->
        </dependency>
        <!-- AspectJ Weaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version> <!-- 确保使用最新版本 -->
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version> <!-- 确保使用最新版本 -->
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>org.logging.LoggingAgent</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <!--自动添加META-INF/MANIFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>org.logging.LoggingAgent</Premain-Class>
                            <Agent-Class>org.logging.LoggingAgent</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2、建立一个Agent类

java 复制代码
package org.logging;

import com.alibaba.fastjson.JSON;
import javassist.*;
import org.springframework.aop.framework.AopProxyUtils;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.*;

public class LoggingAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new LoggingTransformer());
    }

    static Set<Class<?>> skipLogClassSet = new HashSet<>();

    static Map<Class<?>, Boolean> decisionSkipClassMap = new HashMap<>();

    static {
        skipLogClassSet.add(forName("org.apache.catalina.connector.RequestFacade"));
        skipLogClassSet.add(forName("javax.servlet.ServletRequest"));
        skipLogClassSet.add(forName("javax.servlet.ServletResponse"));
    }


    public static Class<?> forName(String clazz) {
        try {
            return Class.forName(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return Void.class;
    }


    public static String toJSONString(Object[] a) {
        if (a == null)
            return "null";

        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
//            b.append(String.valueOf(a[i]));
            Class<?> clazz = a[i].getClass();
            System.out.println(clazz);


            if (!decisionSkipClassMap.containsKey(clazz)) {
                // 检查类是否实现了每个接口
                for (Class<?> interfaceClass : skipLogClassSet) {
                    if (interfaceClass.isAssignableFrom(clazz)) {
                        System.out.println("myObject 的类实现了 " + interfaceClass.getName() + " 接口");
                        decisionSkipClassMap.put(clazz, true);

                    } else {
                        System.out.println("myObject 的类没有实现 " + interfaceClass.getName() + " 接口");
                        if (decisionSkipClassMap.containsKey(clazz) && decisionSkipClassMap.get(clazz)) {
                            //nothing
                            System.out.println("myObject 的类没有实现 " + interfaceClass.getName() + " 接口 但是实现了其他忽略接口");
                        } else {
                            decisionSkipClassMap.put(clazz, interfaceClass.isAssignableFrom(clazz));
                        }
                    }
                }
            }
            if (!decisionSkipClassMap.get(clazz)) {
                b.append(JSON.toJSONString(a[i]));
            }


            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    public static class LoggingTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
            // 忽略不需要处理的类
            if (!className.startsWith("com/xdx/interfaces/facade")) {
                return null;
            }

            // Skip Spring CGLIB proxy classes
            if (className.contains("$$EnhancerBySpringCGLIB$$")) {
                return null;
            }

            // Skip Spring CGLIB FastClass proxy classes
            if (className.contains("$$FastClassBySpringCGLIB$$")) {
                return null;
            }

            try {
                String finalClassName = getFinalClassName(className.replace("/", "."));
                ClassPool pool = ClassPool.getDefault();
                pool.insertClassPath(new ClassClassPath(this.getClass()));
                CtClass ctClass = pool.get(finalClassName);


                // 获取类的所有声明字段
                CtField[] fields = ctClass.getDeclaredFields();

                String logger = null;
                // 打印字段的名称和类型
                for (CtField field : fields) {
                    System.out.println("Field Name: " + field.getName() + ", Field Type: " + field.getType().getName());
                    if ("org.slf4j.Logger".equals(field.getType().getName())) {
                        logger = field.getName();
                    }
                }

                for (CtMethod method : ctClass.getDeclaredMethods()) {
                    try {
                        addLogging(method, logger);
                    } catch (Exception e) {
                        System.err.println("Failed to instrument method: " + method.getName());
                        e.printStackTrace();
                    }
                }

                return ctClass.toBytecode();
            } catch (NotFoundException e) {
                // Log exception and continue without instrumentation
                System.err.println("Class not found: " + className + " - " + e.getMessage());
            } catch (Exception e) {
                e.printStackTrace();
            }

            return classfileBuffer;
        }

        private String getFinalClassName(String className) {
            if (className.contains("$$EnhancerBySpringCGLIB$$") || className.contains("$$FastClassBySpringCGLIB$$")) {
                try {
                    Class<?> proxyClass = Class.forName(className);
                    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(proxyClass);
                    return targetClass.getName();
                } catch (ClassNotFoundException e) {
                    // Handle exception and return the original className
                    e.printStackTrace();
                }
            }
            return className;
        }

        private void addLogging(CtMethod method, String logger) throws CannotCompileException {
            if (Objects.isNull(logger)) {
                addLogging2(method);
            } else {
                addLogging1(method, logger);
            }
        }

        private void addLogging1(CtMethod method, String logger) throws CannotCompileException {
//            method.insertBefore("System.out.println(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));");
            method.insertBefore(logger + ".info(\"[ENTER1] Method: " + method.getName() + " Arguments: \" +org.logging.LoggingAgent.toJSONString($args));");
//            method.insertBefore("log.info(org.logging.LoggingAgent.toJSONString($args));");
            method.insertAfter(logger + ".info(\"[EXIT1] Method: " + method.getName() + " Return: \" + $_);");
//            method.insertBefore(logger + ".info(\"[EXIT] Method: " + method.getName() + " Return: \" + org.logging.LoggingAgent.toJSONString($_));");
        }

        private void addLogging2(CtMethod method) throws CannotCompileException {
            method.insertBefore("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());"
                    + "logger.info(\"[ENTER2] Method: " + method.getName() + " Arguments: \" +  org.logging.LoggingAgent.toJSONString($args));");
//            method.insertBefore("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());" +
//                    "logger.info(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));");
            method.insertAfter(
                    "logger.info(\"[EXIT2] Method: " + method.getName() + " Return: \" + $_);", true);//src:表示插入的代码,这是一段 Java 源代码的 String。
//            asFinally:这是一个 boolean 值,决定了插入的代码块是在正常返回后执行,还是在方法的正常返回和异常返回后都执行。
//            method.insertAfter("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());" +
//                    "logger.info(\"[EXIT] Method: " + method.getName() + " Return: \" + logger.info(org.logging.LoggingAgent.toJSONString($_)));", true);
        }

        private void addLogging3(CtMethod method) throws CannotCompileException {
            method.insertBefore("System.out.println(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));");
            method.insertAfter("System.out.println(\"[EXIT] Method: " + method.getName() + " Return: \" + $_);");
        }
    }
}

3、打agent jar

4、创建一个demoWeb工程

-javaagent:D:\code\logging-agent\target\logging-agent-1.0-SNAPSHOT.jar

为了能调试需要增加jar

相关推荐
激流丶2 分钟前
【缓存策略】你知道 Write Through(直写)这个缓存策略吗?
java·分布式·后端·缓存·中间件
BillKu15 分钟前
Linux(CentOS)项目总结(前后端分离)
java·linux·mysql·nginx·centos
生命几十年3万天24 分钟前
故事1111
java
一二小选手29 分钟前
【Java Web】EL表达式
java·servlet·el
编程修仙1 小时前
java的单例设计模式
java·单例模式·设计模式
南城花随雪。1 小时前
Spring框架之模板方法模式 (Template Method Pattern)
java·开发语言·模板方法模式
架构悟道1 小时前
不当愣头青、聊聊软件架构中的那些惯用的保命手段
java·分布式·架构·设计·高可用·可靠性·容错
蓝田~1 小时前
Springboot -- 自定义异常,异常处理
java·spring boot·spring
Leslie_Lei1 小时前
Hutool-Java工具库
java·hutool