深入理解动态代理

1、代理特点

执行者、被代理人

对于被代理人来说,这件事情一定要做,但是又不想去做或者没时间去做

需要获取被代理人的个人资料

2、jdk动态代理

这里以一个买票的实例进行深入

买票服务

public interface Person {
    //买票
    void butTicket();

    String getBegin();

    String getName();

    String getDestination();

    String getDate();
}

具体买票对象 

public class Zhangsan implements Person {
    private String begin="贵阳";
    private String name="张三";
    private String destination="广州";
    private String date="2018-12-12";
    public void butTicket() {
        System.out.println("我叫"+name);
        System.out.println("我要买一张"+date+begin+"开往"+destination+"的票");
    }
    //..setter..getter
}

黄牛:

public class HuangNiu implements InvocationHandler {
    private Person target;
    //获取被代理人的个人资料
    public Object getInstance(Person target) throws Exception{
        this.target=target;
        Class<? extends Person> clazz = target.getClass();
        System.out.println("被代理对象的class是:"+clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("姓名:"+this.target.getName());
        System.out.println("起始地点:"+this.target.getBegin());
        System.out.println("出发日期:"+this.target.getDate());
        System.out.println("目的地:"+this.target.getDestination());
        System.out.println("-------------------------------------");
        this.target.butTicket();
        System.out.println("-------------------------------------");
        System.out.println("价格合适的话,请付尾款吧!!!");
        return null;
    }
}

测试类:

public class TestBuyTicket {
    public static void main(String[] args) {
        try {
            Person instance = (Person) new HuangNiu().getInstance(new Zhangsan());
            System.out.println(instance.getClass());
            instance.butTicket();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果

原理:

(1)拿到被代理对象的引用,获取其接口

(2)JDK代理重新生成一个类,同时实现其代理对象所实现的接口

(3)把被代理对象的引用也拿到了

(4)重新动态生成一个class字节码

(5)编译执行

证明:

在上述的测试类中作如下修改:

public class TestBuyTicket {
    public static void main(String[] args) {
        try {
            Person instance = (Person) new HuangNiu().getInstance(new Zhangsan());
            System.out.println(instance.getClass());
            instance.butTicket();

            /**
             * 原理:
             *
             * (1)拿到被代理对象的引用,获取其接口
             *
             * (2)JDK代理重新生成一个类,同时实现其代理对象所实现的接口
             *
             * (3)把被代理对象的引用也拿到了
             *
             * (4)重新动态生成一个class字节码
             *
             * (5)编译执行
             */

            //获取字节码内容
            byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{instance.getClass()});
            FileOutputStream os = new FileOutputStream("./$Proxy0.class");
            os.write(data);
            os.close();


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反编译工具打开生成的代理类:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.itboy.jdk.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m5;
    private static Method m3;
    private static Method m2;
    private static Method m6;
    private static Method m4;
    private static Method m7;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String getBegin() throws  {
        try {
            return (String)super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getName() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getDestination() throws  {
        try {
            return (String)super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getDate() throws  {
        try {
            return (String)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void butTicket() throws  {
        try {
            super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m5 = Class.forName("com.itboy.jdk.Person").getMethod("getBegin");
            m3 = Class.forName("com.itboy.jdk.Person").getMethod("getName");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("com.itboy.jdk.Person").getMethod("getDestination");
            m4 = Class.forName("com.itboy.jdk.Person").getMethod("getDate");
            m7 = Class.forName("com.itboy.jdk.Person").getMethod("butTicket");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到其实现了Person接口,并且也实现了接口的方法(这里实现了接口所有的方法,但是调用哪个方法他就代理哪个方法,这里要区分好)。到这里应该就比较清晰了!!接下来只要将这个类加载到jvm不就可以执行了吗。。不过接下来这一步也来实践操作一番。。。

(1)搞一个invocationHandler,最后的方法调用肯定需要这个嘛

public interface MyInvocationHandler {
    //调用
   Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

(2)Proxy中的返回实例也是必须的

//生成代理对象的代码
public class MyProxy {
    private static String ln="\r\n";
    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException, IOException {
        return null;
}

(3)类加载器

//加载字节码到JVM,编译执行
public class MyClassLoader extends ClassLoader{
}

(4)黄牛代理,注意其中的替换

public class MyHuangNiu implements MyInvocationHandler{
    private Person target;
    //获取被代理人的个人资料
    public Object getInstance(Person target) throws Exception{
        this.target=target;
        Class<? extends Person> clazz = target.getClass();
        System.out.println("被代理对象的class是:"+clazz);
        return MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("姓名:"+this.target.getName());
        System.out.println("起始地点:"+this.target.getBegin());
        System.out.println("出发日期:"+this.target.getDate());
        System.out.println("目的地:"+this.target.getDestination());
        System.out.println("-------------------------------------");
        this.target.butTicket();
        System.out.println("-------------------------------------");
        System.out.println("价格合适的话,请付尾款吧!!!");
        return null;
    }
}

(5)接下来肯定就是要动态生成.java文件嘛。动态代理不就是动态生成类然后编译执行吗?那么这个在哪生成的呢?毫无疑问在Proxy的new..方法中,因为实例是从这里返回的。。

//生成代理对象的代码
public class MyProxy {
    private static String ln="\r\n";
    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException, IOException {
        //1、生成源代码
        String proxySrc=generateSource(interfaces);
        //2、将生成的源代码输出到磁盘,保存为.java文件
        String filePath = MyProxy.class.getResource("").getPath();
        File file = new File(filePath+"$Proxy0.java");
        FileWriter fw=new FileWriter(file);
        fw.write(proxySrc);
        fw.flush();
        fw.close();
        //3、编译源代码,并且生成.class文件
        //4、将class文件中的内容,动态加载到JVM
        //5、返回被代理后的代理对象
        return null;
    }
    //生成源代码,这里为了简单仅一个接口作为例子
    private static String generateSource(Class<?>[] interfaces){
        StringBuffer sb = new StringBuffer();
        sb.append("package com.itboy.custom;"+ln);
        sb.append("import java.lang.reflect.Method;"+ln);
        sb.append("import java.lang.reflect.UndeclaredThrowableException;"+ln);
        sb.append("public class $Proxy0 implements "+interfaces[0].getName()+"{"+ln);
        sb.append("MyInvocationHandler h;"+ln);
        sb.append("public $Proxy0(MyInvocationHandler h) {"+ln);
        sb.append("this.h=h;"+ln);
        sb.append("}"+ln);
        for (Method m:interfaces[0].getMethods()){
            sb.append("public "+m.getReturnType().getName()+" "+m.getName()+"() {"+ln);
            sb.append("try{" + ln);
            sb.append("Method m="+interfaces[0].getName()+".class.getMethod(\""+m.getName()+"\",new Class[]{});"+ln);
            //需要返回类型的
            if (!m.getReturnType().getName().equals("void"))
                sb.append("return ("+m.getReturnType().getName()+")");
            sb.append("this.h.invoke(this,m,null);"+ln);
            sb.append("} catch (RuntimeException | Error var2) {"+ln);
            sb.append("throw var2;"+ln);
            sb.append("} catch (Throwable var3) {"+ln);
            sb.append("throw new UndeclaredThrowableException(var3);"+ln);
            sb.append("}"+ln);
            sb.append("}"+ln);
        }
        sb.append("}");
        return sb.toString();
    }
}

测试类:

            new MyHuangNiu().getInstance(new Zhangsan());

执行后,在bin目录下可以看到一个名为$Proxy0的.java文件,内容如下:

package com.itboy.custom;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
public class $Proxy0 implements com.itboy.jdk.Person{
MyInvocationHandler h;
public $Proxy0(MyInvocationHandler h) {
this.h=h;
}
public java.lang.String getName() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("getName",new Class[]{});
return (java.lang.String)this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public java.lang.String getDate() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("getDate",new Class[]{});
return (java.lang.String)this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public void buyTicket() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("buyTicket",new Class[]{});
this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public void butTicket() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("butTicket",new Class[]{});
this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public java.lang.String getBegin() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("getBegin",new Class[]{});
return (java.lang.String)this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public java.lang.String getDestination() {
try{
Method m=com.itboy.jdk.Person.class.getMethod("getDestination",new Class[]{});
return (java.lang.String)this.h.invoke(this,m,null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}

将其放到src中检验其正确性。。。。。。

(6)这一步肯定就是将这个Java文件编译为字节码.class文件咯,在MyProxy中添加如下代码

        //3、编译源代码,并且生成.class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable interable = manager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, interable);
        task.call();
        manager.close();

继续执行上述测试类,可以看到了一个$Porxy0.class文件,反编译打开如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.itboy.custom;

import com.itboy.jdk.Person;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;

public class $Proxy0 implements Person {
    MyInvocationHandler h;

    public $Proxy0(MyInvocationHandler var1) {
        this.h = var1;
    }

    public String getName() {
        try {
            Method var1 = Person.class.getMethod("getName");
            return (String)this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public String getDate() {
        try {
            Method var1 = Person.class.getMethod("getDate");
            return (String)this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public void buyTicket() {
        try {
            Method var1 = Person.class.getMethod("buyTicket");
            this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public void butTicket() {
        try {
            Method var1 = Person.class.getMethod("butTicket");
            this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public String getBegin() {
        try {
            Method var1 = Person.class.getMethod("getBegin");
            return (String)this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public String getDestination() {
        try {
            Method var1 = Person.class.getMethod("getDestination");
            return (String)this.h.invoke(this, var1, (Object[])null);
        } catch (Error | RuntimeException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

(7)接下来就要用到类加载器加载.class文件到jvm中了

//加载字节码到JVM,编译执行
public class MyClassLoader extends ClassLoader{
    private File baseDir;
    public MyClassLoader(){
        String path = MyClassLoader.class.getResource("").getPath();
        this.baseDir=new File(path);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (baseDir!=null){
            File classFile=new File(baseDir,name.replaceAll("\\.","/")+".class");
            if (classFile.exists()){
                FileInputStream in=null;
                try {
                    in=new FileInputStream(classFile);
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    byte[] buff=new byte[1024];
                    int len;
                    while ((len=in.read(buff))!=-1){
                        out.write(buff,0,len);
                    }
                    return defineClass(className,out.toByteArray(),0,out.size());
                } catch (Exception e){
                    e.printStackTrace();
                } finally {
                    if (null!=in){
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }
}

然后再MyProxy中添加:

        //4、将class文件中的内容,动态加载到JVM
        //简单起见,写死哈哈
        Class<?> proxyClass = loader.findClass("$Proxy0");
        Constructor<?> constructor = proxyClass.getConstructor(MyInvocationHandler.class);
        //5、返回被代理后的代理对象
        return constructor.newInstance(h);

测试类:

            Person obj = (Person) new MyHuangNiu().getInstance(new Zhangsan());
            System.out.println(obj.getClass());
            obj.butTicket();

执行后输出如下:

和第一个输出有所不同吧???????????,当然将这个过程中生成的动态类执行完删除就更完美了,这里就不粘贴了。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值