代理模式和JDK动态代理源码分析

(一)介绍

代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。 所谓的代理者是指一个类别可以作为其它东西的接口

代理模式是最常用的java设计模式,它的特点是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期,代理类可以分为俩种。

静态代理:由开发人员创建或特定工具自动生成源码,在对其编译,在程序运行前,代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成

(二)核心总结

1:代理对象的目的是为了增强原有对象功能,扩展原有对象额外的功能
2:JDK动态代理需要使用接口,生成的字节码的代理对象继承了Proxy,要想持有被代理对象必须通过实现接口
3:当前对象不愿意干的,没法干的东西委托给别的对象来做,原有对象做好自己任务即可

(三)优点

1:设计原则,对扩展开放,对修改关闭

2:代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用

3:职责清晰 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰

4:学习代理模式能够让我们了解框架的原理,不仅可以为我们学习框架打好基础,同时也更能让我们深入框架的底层实现

(四)静态代理

当前描述这么一个场景:
1:存在一个歌手类,功能是唱歌
2:有一个歌手代理类,给歌手负责代理
3:代理者除了负责歌手的唱歌工作外,自身扩展了宣传工作,演唱结束后的费用结算工作

定义人接口,代理类个被代理者都需要实现统一实现该接口

public interface People {

    /**
     * 定义每个人功能,工作
     */
    void work();

}

定义歌手类,歌手会唱歌

/**
 * 歌手(被代理对象)
 **/
public class Singer implements People {
  public void work() {
        System.out.println("我是歌手,我会唱歌");
    }
}

定义代理类,代理者在歌手唱歌前后分别做了自己的工作

/**
 * 歌手代理者
 **/
public class MyProxy implements People {

    private Singer singer;


    public MyProxy(Singer singer) {
        this.singer = singer;
    }

    public void publicize() {
        System.out.println("进行宣传工作");
    }

    public void work() {
        publicize();
        singer.work();
        charge();
    }

    public void charge() {
        System.out.println("结算相关费用");
    }


}

测试类

public class Main {
    public static void main(String[] args) {
        //被代理的对象
        Singer singer = new Singer();
        //代理者
        People people = new MyProxy(singer);
        //由代理者处理各个事项
        people.work();
    }
}

在这里插入图片描述

(五)透明代理

上面的模式是需要在后面构建一个歌手对象,而这个歌手对象是可以进行更换的,即单个代理者可以持有不同的歌手,那么现在要说的这种情况是,歌手对象隐藏在代理类中,即被代理类进行限制,在创建代理者时就被创建出来,这种情况代理者只为特定的歌手服务

代码整体没有改变,只是在代理类中持有的被代理对象设值调整了

public class MyProxy implements People {

    private Singer singer;

    //在内部构建了特定的被代理者
    public MyProxy() {
        singer = new Singer();
    }

    public void publicize() {
        System.out.println("进行宣传工作");
    }

    public void work() {
        publicize();
        singer.work();
        charge();
    }

    public void charge() {
        System.out.println("结算相关费用");
    }


}

测试类

public class Main {
    public static void main(String[] args) {
        //只需要创建代理者,真实对象对外的透明的
        People people = new MyProxy();
        //由代理者处理各个事项
        people.work();
    }
}

核心:真实对象对外的透明的

(六)动态代理

Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,该方法需要三个参数:

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

参数一:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】

参数二:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】

参数三:生成的代理对象的方法里干什么事【实现handler接口,代理功能在这里扩展】

public class MyProxy {

    private Object target;

    public MyProxy(Object target) {
        this.target = target;
    }

    public Object getInstance() {
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object res = null;
                //可以在这里扩展其他业务逻辑,调用方法前后
                System.out.println("进行宣传工作");
                res = method.invoke(target, args);
                System.out.println("结算相关费用");
                return res;
            }
        });
        return proxy;
    }


}

测试:

public class Main {
    public static void main(String[] args) {

        Singer singer = new Singer();
        MyProxyFactory factory = new MyProxyFactory(singer);
        People people = (People) factory.getInstance();
        people.work();

    }
}

在这里插入图片描述
总结:
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型

代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】

用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】

使用JDK动态代理必须要有接口【参数二需要接口】

(七)应用场景

1:Spring的AOP功能,面向切面特性就是不修改被代理对象的实现,通过动态代理,将切面类中的方法织入到被调用方法前、后、环绕,Spring中如果被代理对象存在接口则使用的JDK动态代理,否则使用的CGLIB

2:在本次实例中某些对象(比如歌手类)他拥有特定的功能唱歌,当我们需要进行扩展的时候为了避免去修改原有对象,我们就可以通过代理模式来扩展,这样更符合设计原则,且满足面向对象原则

(八)JDK动态代理为什么必须要有接口

通过前面的学习可以看到,代理类和被代理对象都实现了接口,但是这个接口的意义在哪呢,先不管面向接口的原则,这里进入到Proxy.java代码进行观

 /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

在Proxy类中有这么一段代码,它的目的是生成特定的代理class,返回是字节码文件,现在我们获取到这些字节码,并进行反编译

byte[] aClass = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserService.class});
FileOutputStream file = new FileOutputStream("$Proxy.class");
file.write(aClass);
    file.flush();
    file.close();

反编译得到如下对象

public final class $Proxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy(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 void findAllUser() throws  {
        try {
            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 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"));
            m3 = Class.forName("com.octopus.test.UserService").getMethod("findAllUser");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

1:生成的新类$Proxy 继承了Proxy,实现了接口UserService
2:由于每个运行是生成的代理类都必须继承Proxy,但同时这些代理类又需要拥有原类的所有方法,所以必须通过实现
3:因为单继承,必须指定接口采用在代理类中操作具体的方法

(九)为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法呢?

其实是因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中最终调用invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。

JDK生成的代理类方法

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

JDK生成的代理类构造函数

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

通过构建函数传入了InvocationHandler ,之后在调用每个方法的时候都会交给h(即InvocationHandler )去调用,而这里就是我们之前传入的第三个参数实现类
在这里插入图片描述

原创文章 105 获赞 33 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Octopus21/article/details/105670137