类代理方式
- 反射: 有代理的地方几乎都有反射,它们是一套互相配合使用的功能类,在反射中可以调用方法、获取属性、拿到注解等相关内容,这些都可以与接下来的类代理组合使用,完成各种框架中的技术场景
五种类代理的方式
JDK
代理public class JDKProxy {
public static <T> T getProxy(Class clazz) throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (T) Proxy.newProxyInstance(classLoader, new Class[]{clazz}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " 你被代理了,By JDKProxy!");
return "结果";
}
});
}
}场景: 中间件开发、设计模式中的代理模式和装饰器模式应用
点评: 这种JDK自带的类代理方式是非常常用的一种,也是非常简单的一种,基本会在一些中间件代码看到,比如数据库路由组件、Redis组件等,也可以应用到设计模式中CGLIB
代理public class CglibProxy implements MethodInterceptor {
public Object newInstall(Object object) {
return Enhancer.create(object.getClass(), this);
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我被CglibProxy代理了");
return methodProxy.invokeSuper(o, objects);
}
}场景: Spring、AOP切面、鉴权服务、中间件开发、RPC框架等
点评:CGLIB
不同于JDK
,底层使用ASM字节码框架
在类中修改指令码实现代理,因此这种代理方式不需要像JDK
那样需要接口才能代理,同时得益于字节码框架的使用,速度也会快1.5-2.0倍ASM
代理方式public class ASMProxy extends ClassLoader {
public static <T> T getProxy(Class clazz) throws Exception {
ClassReader classReader = new ClassReader(clazz.getName());
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
classReader.accept(new ClassVisitor(ASM5, classWriter) {
@Override
public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {
// 方法过滤
if (!"queryUserInfo".equals(name))
return super.visitMethod(access, name, descriptor, signature, exceptions);
final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new AdviceAdapter(ASM5, methodVisitor, access, name, descriptor) {
@Override
protected void onMethodEnter() {
// 执行指令;获取静态属性
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!");
// 调用方法
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.onMethodEnter();
}
};
}
}, ClassReader.EXPAND_FRAMES);
byte[] bytes = classWriter.toByteArray();
return (T) new ASMProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}
}场景: 全链路监控、破解工具包、CGLIB、Spring获取类元数据等
点评: 使用字节码编程的规范进行处理,实现方式相对复杂,因为每一步代理操作都是在操作字节码指令,因此需要了解Java虚拟机规范相关的知识。但是是最接近底层的方式,也是最快的方式,在一些字节码插桩的全链路监控中非常常见Byte-Buddy
代理方式public class ByteBuddyProxy {
public static <T> T getProxy(Class clazz) throws Exception {
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(clazz)
.method(ElementMatchers.<MethodDescription>named("queryUserInfo"))
.intercept(MethodDelegation.to(InvocationHandler.class))
.make();
return (T) dynamicType.load(Thread.currentThread().getContextClassLoader()).getLoaded().newInstance();
}
}
@RuntimeType
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception {
System.out.println(method.getName() + " 你被代理了,By Byte-Buddy!");
return callable.call();
}场景: AOP切面、类代理、组件、监控、日志
点评: 也是一个字节码操作的类库,但是使用方式更加简单,无需理解字节码指令即可使用简单的API操作字节码控制类和方法。比起JDK动态代理、CGLIB
,Byte Buddy
在性能上具有一定的优势Javassise
代理方式public class JavassistProxy extends ClassLoader {
public static <T> T getProxy(Class clazz) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 获取类
CtClass ctClass = pool.get(clazz.getName());
// 获取方法
CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
// 方法前加强
ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}");
byte[] bytes = ctClass.toBytecode();
return (T) new JavassistProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}
}场景: 全链路监控、类代理、AOP
点评:Javassist
是一个使用非常广的字节码插桩框架,几乎大部分非侵入的全链路监控都会使用这个框架,因为它不像ASM
那样操作字节码导致风险,同时功能非常齐全,可以使用它所提供的方式直接编写插桩代码,也可以使用字节码指令进行控制生成代码
总结
- 反射与代理:
- 代理的实际目的就是通过一些技术手段,替换掉原有的实现类或者非原有实现类注入新的字节码指令,而这些技术最终都会被应用到一些框架应用、中间件开发以及类似非侵入的全链路监控中