面试题答案
一键面试1. Java注解实现原理
Java注解本质是一种元数据,它在编译期、运行期等不同阶段发挥作用。编译期注解可用于代码生成等,运行期注解通过反射获取注解信息。注解本身并不包含逻辑,只是一种标记。例如定义一个简单注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "";
}
在运行期通过反射获取注解:
import java.lang.reflect.Method;
public class Main {
@MyAnnotation("test")
public void testMethod() {
// method body
}
public static void main(String[] args) throws NoSuchMethodException {
Main main = new Main();
Method method = main.getClass().getMethod("testMethod");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
if (annotation != null) {
System.out.println(annotation.value());
}
}
}
2. 利用字节码增强技术动态增强
ASM实现思路
- 关键步骤:
- 解析字节码:使用ASM的
ClassReader
读取目标类的字节码。例如对于一个简单的UserService
类:
- 解析字节码:使用ASM的
public class UserService {
public void login() {
System.out.println("User is logging in");
}
}
- **转换字节码**:创建一个`ClassVisitor`,继承自`ClassVisitor`(如`MyClassVisitor`),在其方法中对需要增强的类或方法进行修改。如果要增强`login`方法,可在`visitMethod`方法中判断方法名,如果是`login`方法,在方法开始和结束位置插入性能监控代码。例如:
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("login".equals(name)) {
mv = new MyMethodVisitor(mv);
}
return mv;
}
}
class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Start monitoring performance");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("End monitoring performance");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
- **生成新字节码**:使用`ClassWriter`生成修改后的字节码,并将其写入文件或者加载到JVM中。例如:
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.FileOutputStream;
import java.io.IOException;
public class ASMExample {
public static void main(String[] args) throws IOException {
ClassReader cr = new ClassReader("UserService");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
MyClassVisitor cv = new MyClassVisitor(Opcodes.ASM9, cw);
cr.accept(cv, 0);
byte[] code = cw.toByteArray();
FileOutputStream fos = new FileOutputStream("UserService.class");
fos.write(code);
fos.close();
}
}
Javassist实现思路
- 关键步骤:
- 获取类池与目标类:使用
ClassPool
获取类池,通过类池获取目标类。例如:
- 获取类池与目标类:使用
import javassist.ClassPool;
import javassist.CtClass;
public class JavassistExample {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("UserService");
- **修改类或方法**:如果要增强`login`方法,获取该方法并在其前后插入性能监控代码。例如:
import javassist.CtMethod;
//...
CtMethod loginMethod = cc.getMethod("login", "()V");
loginMethod.insertBefore("System.out.println(\"Start monitoring performance\");");
loginMethod.insertAfter("System.out.println(\"End monitoring performance\");");
- **写入新类**:将修改后的类写入文件或加载到JVM中。例如:
//...
cc.writeFile();
3. 增强功能场景举例
性能监控
- 场景描述:在方法执行前后记录时间,计算方法执行耗时。如上述代码中,在方法开始和结束位置打印日志来标记性能监控的开始和结束。
事务管理
- 场景描述:在方法执行前开启事务,执行后根据执行结果提交或回滚事务。例如对于一个
AccountService
类中的transfer
方法:
public class AccountService {
public void transfer(String from, String to, double amount) {
// 转账逻辑
}
}
使用字节码增强技术,在transfer
方法开始前插入开启事务代码,在方法正常结束或异常时分别插入提交和回滚事务代码。以Javassist为例:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class TransactionExample {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("AccountService");
CtMethod transferMethod = cc.getMethod("transfer", "(Ljava/lang/String;Ljava/lang/String;D)V");
transferMethod.insertBefore("TransactionManager.beginTransaction();");
transferMethod.insertAfter("TransactionManager.commitTransaction();", true);
transferMethod.addCatch("{TransactionManager.rollbackTransaction(); throw $e;}", pool.get("java/lang/Exception"));
cc.writeFile();
}
}
这里假设存在TransactionManager
类,包含beginTransaction
、commitTransaction
和rollbackTransaction
方法用于事务管理。