许多开发使用的spring aop来做切面 今天尝试一下使用JVM层面的切面
1、建立 agent jar工程
创建工程 logging-agent 使用POM为 javassist 日志
如下:使用了字节码 javassist
如果想处理springaop 代理的请增加依赖否则就不需要
<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 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><transformerimplementation="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)) {//nothingSystem.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 {@Overridepublic 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 classesif (className.contains("$$EnhancerBySpringCGLIB$$")) {return null;}// Skip Spring CGLIB FastClass proxy classesif (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 instrumentationSystem.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 classNamee.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