JDK提供的代理(动态代理)
核心是InvocationHandler和Proxy。具体方法为,对要设置代理的类,扫描其类对象,获取所有实现的接口,然后根据这些接口新建一个实现了被代理类所有接口的代理类(用java代码创建新的java类的字节码然后加载)。在代理类中,所有实现接口的方法都采用反射的方式调用被代理类中的代码。
生成代理对象是用的Proxy类的静态方newProxyInstance
public class JDKProxyTest {
public static void main(String[] args) {
//我们要代理的真实对象
Subject realSubject = new RealSubject();
//我们要代理哪个真实对象,就将该对象传进去,最后通过该真实的对象调用该方法
MyHandler handler = new MyHandler();
handler.setRealSubject(realSubject);
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler); //subject是我们实现的接口
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}
interface Subject{
void rent();
void hello(String str);
}
class RealSubject implements Subject{
@Override
public void rent(){
System.out.println("hello");
}
@Override
public void hello(String str){
System.out.println(str);
}
}
class MyHandler implements InvocationHandler {
private Object realSubject;
public void setRealSubject(Subject realSubject) {
this.realSubject = realSubject;
}
public Object invoke(Object proxy, Method method, Object[] args){
Object result = null;
before();
try {
result = method.invoke(realSubject, args);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
after();
return result;
}
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
}
原理Proxy.newProxyInstance可以根据创建一个gdl.$Proxy0
类的对象。该类是JDK利用字节码操作建立的。realSubject.getClass().getInterfaces()必须有,所以务必要实现接口。如果你反编译$Proxy0
类,你可以发现该类包含所有被代理类的所有接口中的所有方法,这些方法的具体实现是通过反射进行调用。请参考
https://segmentfault.com/q/1010000011719035/a-1020000011737397
特点:
- 创建方便快速
- 采用反射调用,由一定的性能消耗
- 被代理类一定要实现接口!!!这是因为生成的代理类继承自Proxy类,java不支持多继承,所以,如果你想把他当作愿对象用,就需要一个接口。所以,这种方法也被成为面向接口的代理。
应用:
SPRING AOP
JAVA MOCKITO
MyBaits
ASM
asm的使用需要开发者对于JVM字节码有深入了解。
通过使用org.objectweb.asm包,开发者可以在运行时任意改变虚拟机中的字节码。
package samples;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyGenerator {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通过visit方法确定类的头部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 类修饰符
"Programmer", // 类的全限定名
null, "java/lang/Object", null);
//创建构造函数
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定义code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter类已经完成
// 将classWriter转换成字节数组写到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("D://Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
CGLIB提供的代理(动态代理)
cglib是一个第三方jar包。他同样采取了字节码技术,方法是利用ASM这个第三方字节码编辑框架,读取被代理类的字节码,然后根据该字节码创建一个被代理类的子类(代理类)。并在子类中完成对被代理类方法的拦截和增强。
特点:
- 可以代理没有实现接口的类
- 生成代理类比较慢,但是执行代理类非常快。
应用
SPRING AOP
JAVASIST
同样时基于ASM构建
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("com.samples.Programmer");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("d://temp");
}
}
ASPECTJ(静态代理)
在java代码编译器完成代理
- 需要aspectj的编译器
##java调试(动态代理)
当然,你也可以通过JVM提供的java agent和native agent实现你任意想要的代理
各种动态代理之间的性能比较
- JDK动态代理使用简单,它内置在JDK中,因此不需要引入第三方Jar包
- ASM>Javassist>CGLIB>JDK。ASM是最底层的基础,javaassit也是操作字节码的,cglib则时包装一下专门用来生成代理类的。jdk时官方自带的也最弱。这是一个非常合理的排名。