ASM(全称为 “Abstract Syntax Tree for Manipulation”)是一个用于操作Java字节码的库。它允许你动态地生成、转换和分析Java字节码,可以用于实现诸如字节码增强、代码生成、静态分析等功能。
ASM 的优点:
低级别操作:ASM 提供了一种低级别的字节码操作方式,允许你直接操作字节码指令,从而能够更精确地控制和修改字节码。
性能:由于 ASM 是基于底层字节码操作的,相对而言,它的性能更高,生成的字节码更紧凑,执行效率更高。
灵活性:ASM 提供了对字节码的细粒度访问,可以进行更复杂和灵活的字节码操作,适用于需要更高级别的控制和自定义的场景。
ASM 提供了一组核心 API,用于操作和分析 Java 字节码。以下是 ASM 的核心 API:
ClassVisitor:ClassVisitor 是 ASM 的核心接口之一,用于访问和修改类的结构。通过实现 ClassVisitor 接口,你可以在类被读取、转换和写入时进行自定义的操作。
MethodVisitor:MethodVisitor 是 ClassVisitor 的一个子接口,用于访问和修改方法的结构。你可以实现 MethodVisitor 接口来自定义访问和修改方法的行为。
FieldVisitor:FieldVisitor 是 ClassVisitor 的另一个子接口,用于访问和修改类的字段。通过实现 FieldVisitor 接口,你可以自定义访问和修改字段的行为。
AnnotationVisitor:AnnotationVisitor 是 ASM 的接口之一,用于访问和修改类、方法、字段的注解。通过实现 AnnotationVisitor 接口,你可以自定义访问和修改注解的行为。
ClassReader:ClassReader 是一个用于解析和读取类文件的类。你可以使用 ClassReader 来读取类文件,并以合适的方式将其传递给 ClassVisitor 进行进一步的操作。
ClassWriter:ClassWriter 是一个用于生成字节码的类。你可以使用 ClassWriter 来生成修改后的字节码,并将其写入新的类文件或者加载到内存中。
除了以上列出的核心 API,ASM 还提供了其他一些辅助类和接口,用于处理字节码的各个方面,例如 Type 类用于表示和操作类型信息,Handle 类用于表示方法句柄等。
这些核心 API 为你提供了灵活和底层的手段来操作和分析字节码,允许你进行各种自定义的字节码操作,例如生成新的类、修改现有类的结构、增强方法等。
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>SQLQueryAgentartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-bomartifactId>
<version>9.5version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asmartifactId>
dependency>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-commonsartifactId>
dependency>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-treeartifactId>
dependency>
<dependency>
<groupId>org.javassistgroupId>
<artifactId>javassistartifactId>
<version>3.28.0-GAversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<configuration>
<createDependencyReducedPom>falsecreateDependencyReducedPom>
<finalName>${project.artifactId}finalName>
<filters>
<filter>
<artifact>*:*artifact>
<excludes>
<exclude>META-INF/**exclude>
excludes>
filter>
filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>com.*.*.StartUpPremain-Class>
<Can-Redefine-Classes>trueCan-Redefine-Classes>
<Can-Retransform-Classes>trueCan-Retransform-Classes>
manifestEntries>
transformer>
transformers>
configuration>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
import java.lang.instrument.Instrumentation;
public class StartUp {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("-----------------启动开始---------------------");
inst.addTransformer(new MyTransformer(),true);
System.out.println("-----------------启动结束---------------------");
}
}
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer){
if (className != null && className.equals("com/**/service/impl/QhyuServiceImpl")) {
try {
ClassReader classReader = new ClassReader(className);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new MyClassVisitor(classWriter);
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();
}catch (Exception e){
e.printStackTrace();
}
}
return classfileBuffer;
}
}
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyClassVisitor extends ClassVisitor implements Opcodes {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
System.out.println("MyClassVisitor初始化完成");
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
System.out.println("visitMethod-qhyu1"+name);
cv.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println("visitMethod-qhyu2"+name);
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
//Base类中有两个方法:无参构造以及process方法,这里不增强构造方法
if (!name.equals("" ) && mv != null) {
System.out.println("qhyu-name:"+name);
mv = new MyMethodVisitor(mv);
}
return mv;
}
static class MyMethodVisitor extends MethodVisitor implements Opcodes {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("qhyustart");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
//方法在返回之前,打印"end"
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("qhyuend");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
}
mv.visitInsn(opcode);
}
}
}
利用这个类就可以实现对字节码的修改。详细解读其中的代码,对字节码做修改的步骤是:
后,将需要被增强的方法交给内部类MyMethodVisitor来进行处理。我的springboot项目需要使用到这个插桩的agent,所以启动的时候带上参数就行
-javaagent:G:\SQLQueryAgent\SQLQueryAgent\target\SQLQueryAgent.jar
但是我启动之后报错了,错误如下:
java.lang.IllegalArgumentException
at org.objectweb.asm.ClassReader.<init>(Unknown Source)
at org.objectweb.asm.ClassReader.<init>(Unknown Source)
at org.objectweb.asm.ClassReader.<init>(Unknown Source)
at com.swcares.skyworth.transformer.MeituanTransformer.transform(MeituanTransformer.java:31)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:469)
at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1545)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1472)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:682)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:649)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1608)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:524)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:495)
at org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.checkDefaultDispatcherName(DispatcherServletAutoConfiguration.java:172)
at org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.getMatchOutcome(DispatcherServletAutoConfiguration.java:163)
at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:225)
at org.springframework.context.annotation.ConfigurationClassParser.processMemberClasses(ConfigurationClassParser.java:371)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:271)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:249)
at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:599)
at org.springframework.context.annotation.ConfigurationClassParser.access$800(ConfigurationClassParser.java:110)
at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.lambda$processGroupImports$1(ConfigurationClassParser.java:811)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports(ConfigurationClassParser.java:808)
at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorHandler.process(ConfigurationClassParser.java:779)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:192)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:319)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.swcares.skyworth.WebApplication.main(WebApplication.java:12)
这个是因为asm的版本问题导致的,我搜索了下项目中的asm,我们使用的是9.5版本。但是springboot项目中cglib和test框架带了4.2版本和5.0.4版本。
解决方案:
排除其他的,引入9.5,重新启动。
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asmartifactId>
<version>9.5version>
<exclusions>
<exclusion>
<groupId>*groupId>
<artifactId>*artifactId>
exclusion>
exclusions>
dependency>
这样就可以在sql查询前后进行aop处理了。
我的IDEA是社区版,使用的ASM Bytecode Viewer插件还挺流畅,这样在插入一些东西的时候,可以用java代码写出来然后查看ASMified查看,例如我的System.out.println(“qhyustart”)代码