ASM是一个通用的 Java 字节码操控和分析框架。它可以用于修改已有的类也可以直接生成类。ASM 提供了一些常用的字节码转换和分析算法,从中可以构建自定义的复杂转换和源码分析工具。ASM提供了与其他 Java 字节码框架类似的方法,但是更注重性能。因为它被设计和实现成尽可能小和快,所以非常适用于动态系统(当然也可以用于静态的方式,例如在编译器中)。
注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。
网上有大量详细的关于以上两种工具的原理分析及使用说明,这里我就简单使用一下;入门后,则可以充份发挥想象力,实现功能.
ASM
(1)引入依赖
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
(2)实现动态生成以下这段代码的效果:
package test.asm;
public class Hello {
public static void say() {
System.out.println("HelloWorld!!!");
}
}
(3)编写代码
package test.asm;
import jdk.internal.org.objectweb.asm.Opcodes;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import java.lang.reflect.Method;
public class AsmTool {
public static void main(String[] args) throws Exception {
// 生成二进制字节码
byte[] bytes = generate();
// 使用自定义的ClassLoader
MyClassLoader cl = new MyClassLoader();
// 加载我们生成的 HelloWorld 类
Class<?> clazz = cl.defineClass("test.asm.HelloWorld", bytes);
// 反射获取 main 方法
Method main = clazz.getMethod("say", String[].class);
// 调用 main 方法
main.invoke(null, new Object[]{new String[]{}});
}
private static byte[] generate() {
ClassWriter cw = new ClassWriter(0);
// 定义对象头:版本号、修饰符、全类名、签名、父类、实现的接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "test/asm/HelloWorld",
null, "java/lang/Object", null);
// 添加方法:修饰符、方法名、描述符、签名、抛出的异常
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "say",
"([Ljava/lang/String;)V", null, null);
// 执行指令:获取静态属性
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
mv.visitLdcInsn("HelloWorld!!!");
// 调用方法
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
// 返回
mv.visitInsn(Opcodes.RETURN);
// 设置栈大小和局部变量表大小
mv.visitMaxs(2, 1);
// 方法结束
mv.visitEnd();
// 类完成
cw.visitEnd();
// 生成字节数组
return cw.toByteArray();
}
}
/**
* 自定义ClassLoader以支持加载字节数组形式的字节码
*/
class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
// ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
// 所以我们需要定义一个子类将这个方法暴露出来
return super.defineClass(name, b, 0, b.length);
}
}
Annotation Processor
(1)引入依赖
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
javapoet是生成java代码用的; auto-service帮助我们自动注册处理器的;代替了手动在/resources/META-INF/services下新建javax.annotation.processing.Processor的步骤;
(2)新建一个工程,然后新增一个自定义注解
//可以设置仅编译时有效RetentionPolicy.SOURCE
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface GyTest {
}
(3)继承AbstractProcessor,重写方法
package com.gy;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
@AutoService(Processor.class) //自动注册
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"*"})
public class GyProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnv.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(GyTest.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("AutomaticGenerationHelloWorld")
.addModifiers(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.FINAL)
.addMethod(main)
.build();
try {
JavaFile javaFile = JavaFile.builder("com.gy", helloWorld)
.addFileComment("This codes are generated automatically. Do not modify!")
.build();
// write to file
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
}
(4)将该工程使用mvn clean package打包或mvn install发布到本地;
(5)其他项目引入该依赖
<dependency>
<artifactId>annotation-processing</artifactId>
<groupId>com.gy</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
(6)在该项目中使用引入依赖的自定义注解
package com;
import com.gy.GyTest;
@GyTest
public class Hello {
}
(7)打包该项目,可以看到target/classes/com/gy下已经出现了我们要生成的class,内容为:
public final class AutomaticGenerationHelloWorld {
public AutomaticGenerationHelloWorld() {
}
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
(8)稍微复杂一点的:
package net.wf;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.util.Collections;
import java.util.Set;
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"*"})
public class WfProcessor2 extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnv.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(GyTest.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
try {
MethodSpec main = MethodSpec.methodBuilder("testx")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(String.class, "sql")
.addCode("try {java.sql.Statement stmt = dataSource.getConnection().createStatement();stmt.execute(sql);} catch (Exception e) {}")
.addAnnotation(ClassName.bestGuess("com.gy.constants.annotation.Token"))
.addAnnotation(AnnotationSpec.builder(ClassName.bestGuess("org.springframework.web.bind.annotation.RequestMapping")).
addMember("value","\"testx\"").build())
.build();
FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(Class.forName("javax.sql.DataSource")), "dataSource")
.addModifiers(Modifier.PRIVATE)
.addAnnotation(ClassName.bestGuess("org.springframework.beans.factory.annotation.Autowired"))
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("SqlController")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.addField(fieldSpec)
.addAnnotation(ClassName.bestGuess("org.springframework.web.bind.annotation.RestController"))
.build();
JavaFile javaFile = JavaFile.builder("com.gy.controller", typeSpec)
.addFileComment("This codes are generated automatically. Do not modify!")
.build();
javaFile.writeTo(filer);
} catch (Exception e) {
}
return true;
}
}
编译后:
@RestController
public class SqlController {
@Autowired
javax.sql.DataSource dataSource;
@Token
@RequestMapping("/testx")
public void testx(String sql) {
try {
java.sql.Statement stmt = dataSource.getConnection().createStatement();
stmt.execute(sql);
} catch (Exception e) {
}
}
}
本文由 GY 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2021/12/17 17:54