JAVA静态代理和动态代理理解

一、什么是代理模式

代理模式是面向对象编程中比较常见的设计模式。

这是常见代理模式常见的 UML 示意图。

需要注意的有下面几点:
1. 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
2. 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
3. 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
4. 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。

如果难于理解的话,我用事例说明好了。值得注意的是,代理可以分为静态代理和动态代理两种。先从静态代理讲起。

二、静态代理

实现方式:先设计一个接口,被代理类实现这个接口。实现里面抽象方法,在创建一个代理类,也实现一样的接口,在实现抽象方法,在调用抽象方法前,先调用被代理类的方法,这样就完成了一个简单的静态代理功能。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

public interface Movie {
    void play();
}

然后,我们要有一个真正的实现这个 Movie 接口的类

public class RealMovie implements Movie {
    @Override
    public void play() {
        System.out.println("您正在观看电影 《肖申克的救赎》");
    }
}

创建一个代理类,也实现一样的接口,在实现抽象方法,在调用抽象方法前,先调用被代理类的方法

public class Cinema implements Movie {
	
    RealMovie movie;
	//通过构造函数把目标对象传进来
    public Cinema(RealMovie movie) {
        super();
        this.movie = movie;
    }

    @Override
    public void play() {
       System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
       //调用目标方法
	   movie.play();
       
	   System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
    }
}
public class ProxyTest {
    public static void main(String[] args) {
		//创建目标对象
        RealMovie realmovie = new RealMovie();
		//创建代理对象。
        Movie movie = new Cinema(realmovie);
		//不直接执行目标方法,通过执行代理方法执行目标方法
        movie.play();
    }
}

输出结果

电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。

动态代理

既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上面代码中Cinema类是代理,我们需要手动编写代码让Cinema实现Movie接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。
也许概念比较抽象。现在实例说明一下情况。


假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。

public interface SellWine {
     void mainJiu();
}

SellWine 是一个接口,你可以理解它为卖酒的许可证

public class MaotaiJiu implements SellWine {

    @Override
    public void mainJiu() {
        System.out.println("我卖得是茅台酒。");
    }
}

创建一个InvocationHandler类,为什么需要这么一个类,你就理解,动态代理需要这个一个类。后面我会介绍为什么需要这个类的作用。注意,这个类不是代理类。不要弄混淆。

public class GuitaiA implements InvocationHandler {

    private Object pingpai;

    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("销售开始  柜台是: "+this.getClass().getSimpleName());
        method.invoke(pingpai, args);
        System.out.println("销售结束");
        return null;
    }
}

下面开始运行

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        //创建目标类。
        MaotaiJiu maotaijiu = new MaotaiJiu();
        //创建InvocationHandler类实例
        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);

        //生成代理类。
        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);

        //调用目标方法
        dynamicProxy.mainJiu();
    }
}

输出

销售开始  柜台是: GuitaiA
我卖得是茅台酒。
销售结束

动态代理语法
放轻松,下面我们开始讲解语法,语法非常简单。
动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。

Proxy

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

下面讲解它的 3 个参数意义。
1、loader 自然是类加载器
2、interfaces 代码要用来代理的接口
3、h 一个 InvocationHandler 对象
初学者应该对于 InvocationHandler 很陌生,我马上就讲到这一块。

InvocationHandler作用:


InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的InvocationHandler实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler内部只是一个invoke()方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
1、proxy 代理对象
2、method 代理对象调用的方法
3、args 调用的方法中的参数
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler才是实际执行者。正是利用这个我们才完成对目标方法的进行增强。

 

在回到这行代码

//生成代理类。
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);

是怎么生成代理的。进到源码看下做了什么(省略了一些代码,为了方便阅读)

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {
	//clone被代理类的接口,为什么要clone
    final Class<?>[] intfs = interfaces.clone();
	//通过类加载器创建一个Class对象。这个Class类也是实现了intfs这个接口,且定义了一个有参的构造器,这个构造器的参数类型是InvocationHandler
    Class<?> cl = getProxyClass0(loader, intfs);

	//Constructor这个类型应该不陌生吧,代表类的构造方法。通过constructorParams这个类型去查询形参是constructorParams类型的构造方法。最终得到构造方法对象cons
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    
	//h是我们一开始定义的InvocationHandler实现类。
    final InvocationHandler ih = h;
	
	//然后在通过反射调用newInstance方法生成cl的实例对象。这个对象就是代理对象。
    return cons.newInstance(new Object[]{h});
}

先屡下他们的关系:

interfaces:是被代理类实现的接口

intfs:是interfaces接口Copy出来的接口

loader:是被代理类的类加载器

cl :实现intfs接口类的Class,也就是我们的代理类,这个时候已经被装在到JVM里面去,完成了初始化,但还没有实例化。

cons :一个构造方法对象(类的构造方法也可以用对象表示),是cl的构造方法。

h:InvocationHandler的实现类对象

最终生成了一个代理类cl的实例对象,当我们调用这个代理类的方法时,就会通知和转发给InvocationHandler的实现类GuitaiA的invoke方法来执行。

流程大致如下:

1、先通过interfaces克隆出一个intfs接口。
2、然后通过类加载器,调用getProxyClass0方法,这个方法会生成一个类cl,注意不是接口,这个类实现了intfs接口。
3、这个时候cl里面一定实现了intfs的所有方法。且cl里面只有一个构造方法,这个构造方法是有参数。参数类型是java.lang.reflect.InvocationHandler。为什么需要一个有参的构造方法,可以结合静态代理一起理解下。
4、在通过cl拿到带有InvocationHandler参数的构造方法cons。
5、在通过cl的构造方法cons调用newInstance方法,newInstance里面穿一个参数,这个参数对应的类型是InvocationHandler类型。这个类型对象从哪里来的。
6、调用完了newInstance方法后,就得到这个实例对象,这个实例对象是cl的实例。
7、我们为什么需要InvocationHandler,代理的实例都有一个与之关联的 InvocationHandler实现类,因为当我们调用代理类D的任何代理的方法时,那么代理便会通知和转发给InvocationHandler实现类,由invoke方法来决定处理。
8、收到通知的invoke方法里面会得到目标方法,和目标方法的参数,所以我们可以在invoke方法里面写目标方法的前置功能,在执行目标方法,在写目标方法的后置功能。这样就完成了对目标放的增强。所以很多框架的源码都是这样实现的。

 

代理的作用


可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?
主要作用,还是在不修改被代理对象的源码上,进行功能的增强。
这在 AOP 面向切面编程领域经常见。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。
同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?
这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。
至此,静态代理和动态代理者讲完了。

 

总结


代理分为静态代理和动态代理两种。
静态代理,代理类需要自己编写代码写成。
动态代理,代理类通过 Proxy.newInstance() 方法生成。
不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
代理模式本质上的目的是为了增强现有代码的功能。


 

 

 

 

转载来自:https://blog.csdn.net/briblue/article/details/73928350

本文转载:https://www.cnblogs.com/cC-Zhou/p/9525638.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值