java中的代理

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时官方自带的也最弱。这是一个非常合理的排名。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值