关注「Java视界」公众号,获取更多技术干货

代理模式(静态代理模式、动态代理模式、cgLib代理模式、拦截器)

目录

一、什么是代理?

二、代理的好处

三、实现代理的步骤

四、静态代理

五、动态代理

六、 Cglib动态代理

七、Spring的AOP中的动态代理

八、拦截器                                                                      

 

一、什么是代理?

现实生活中有哪些例子?比如:买房子找中介、打官司找律师等,都是代理模式,都不是你自己直接去干这件事,自己只负责付钱或者出庭,其它前期准备工作及收尾工作交个代理去完成。所以代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象。

代理模式包含三个角色:

  1. 目标对象
  2. 代理对象
  3. 用户

举个例子来说明下用代理去执行和自己亲自执行的不同,假设有个小伙要追求一个姑娘,不请他人帮忙的情况自己上的情况如下:

@Data
public class Pursuit {

    private Girl girl;

    public Pursuit(Girl girl) {
        this.girl = girl;
    }

    public void giveDolls(){
        Console.print("送玩具娃娃");
    }

    public void giveFlowers(){
        Console.print("送鲜花");
    }

    public void giveFood(){
        Console.print("送好吃的");
    }
}
@Data
public class Girl {
    private String name;

    public Girl(String name) {
        this.name = name;
    }
}
public class Client03 {
    public static void main(String[] args) {
        Girl Lily = new Girl("lily");

        Pursuit Jim = new Pursuit(Lily);
        Jim.giveDolls();
        Jim.giveFlowers();
        Jim.giveFood();
    }
}

这个就是追求者直接去追求女孩,这个代码的不好之处就是两者耦合了,这两个人是“互相认识”的。

想要解耦就需找个中间人,也就是代理:

public interface GiveGift {
    void giveDolls();
    void giveFlowers();
    void giveFood();
}
@Data
public class Pursuit implements GiveGift {

    private Girl girl;

    public Pursuit(Girl girl) {
        this.girl = girl;
    }

    public void giveDolls(){
        Console.print("送玩具娃娃");
    }

    public void giveFlowers(){
        Console.print("送鲜花");
    }

    public void giveFood(){
        Console.print("送好吃的");
    }
}
@Data
public class Proxy implements GiveGift {

    private Pursuit boy;

    public Proxy(Girl Lily) {
        boy = new Pursuit(Lily);
    }

    public void giveDolls(){
        boy.giveDolls();
    }

    public void giveFlowers(){
        boy.giveFlowers();
    }

    public void giveFood(){
        boy.giveFood();
    }
}
public class Client04 {
    public static void main(String[] args) {
        Girl Lily = new Girl("lily");

        Proxy proxy_wang = new Proxy(Lily);
        proxy_wang.giveDolls();
        proxy_wang.giveFlowers();
        proxy_wang.giveFood();
    }
}

这里Proxy类中:

  1. 这个代理类要代理Persuit去追求,所以直接持有了Persuit对象,也就是我要给谁代理
  2. 既然要代替别人,那我就要拥有和别人一样的能力,所以和Persuit一样都实现了GiveGift接口,这样就具有了一样的能力
  3. 我知道要为谁代理了,也知道怎么去执行了,那我执行的对象的是谁呢,这里在构造方法中体现了,即要去追求的对象是谁也展现了

这个过程就是代理,Jim和Lily中间隔着proxy_wang,proxy_wang和Jim具有一样的能力。

二、代理的好处

上面的例子有啥好处?那就好比结婚邀请媒人,媒人就是代理,好处你自己想想。

在代码中的好处呢?编程中有个思想:即开闭原则,对修改关闭,对扩展开放。

代理模式通过代理对象访问目标对象,可以在不修改目标对象的基础上,给目标对象扩展额外的功能操作。

举个现实生活中的例子,例如当你要租房的时候,就可以去找中介帮你完成房源筛选、租房协议办理等事情,而你自己只需要负责给钱签协议既可以了,其余时间该干啥就干啥(你不用改变),给你省下很多时间精力,这就是代理的好处。

在代码世界代理也是一样的作用,我们想增强某个类的功能,不需要直接修改这个类,利用代理模式可以在不改变这个类的基础上 增强这个类的功能。具体的,代理模式有以下应用场景:

  • 远程代理 :为两个不同地址空间对象的访问提供了一种实现机制,可以将消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
  • 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  • 缓冲代理:为某一个操作的结果提供临时缓存存储空间,以便在后续使用中共享这些结果,优化系统性能,缩短执行时间。
  • 保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
  • 智能引用:要为一个对象的访问(引用)提供一些额外的操作时可以使用。

三、实现代理的步骤

实现代理必须遵循下面两步:

  1. 代理对象和真实对象建立代理关系
  2. 实现代理对象的代理逻辑方法

四、静态代理

什么是静态代理?

举个例子:王富贵的丈母娘告诉他想娶自己女儿就要买房,否则别想娶自己的小棉袄,王富贵一下就慌了,丈母娘突然来这一手自己一点准备都没有,房子在哪买、买什么样的、每个地区房价怎样、交通、教育、医疗、保值等一点头绪都没有,于是王富贵儿准备去找房产中介张狗蛋帮忙,让中介替自己完成以上繁琐费时的找房任务,而自己坐等有满意的房子直接交钱签字就行了。

静态代理就是目标类和代理类的关系在编译时已经确定,例如现在王富贵已经委托张狗蛋来代理自己去找房子,事先已经确定。

1、定义代表买房的接口

public interface BuyHouse {
    // 付钱
    void pay ();
}

2、目标类/被代理类:王富贵

public class FuGuiWang implements BuyHouse{

    @Override
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

3、代理类:张狗蛋

public class GouDanZhangProxy implements BuyHouse{
    // 持有被代理对象(目标对象)
    private FuGuiWang fuGuiWang;
    public GouDanZhangProxy(FuGuiWang fuGuiWang) {
        this.fuGuiWang = fuGuiWang;
    }

    @Override
    public void pay() {
        System.out.println("中介找房源!");
        fuGuiWang.pay();
        System.out.println("中介处理后续事宜...");
    }
}

4、测试类与结果

public class TestStatic {
    public static void main(String[] args) {
        FuGuiWang fuGuiWang = new FuGuiWang();
        GouDanZhangProxy gouDanZhangProxy = new GouDanZhangProxy(fuGuiWang);
        gouDanZhangProxy.pay();
    }
}
中介找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
中介处理后续事宜...

上面的实例可以看出,可以在不改变FuGuiWang目标类的基础上,为它增加“找房源!”和“处理后续事宜...”的功能,这就是代理的作用。

但仔细观察上面代码,可以看到静态代理中目标类和代理类要实现同一个接口,这会带来一个缺陷:试想下,若王富贵的同事赵铁柱也要买房,采用静态代理的话就需要再增加两个类:一个是赵铁柱类,一个是赵铁柱的代理类,这样会使得代码变得臃肿,重用性很差。动态代理可以避免这样的问题。

五、动态代理

静态代理是目标对象和代理对象的关系是一开始就确定的,而动态代理中,目标对象和代理对象的关系是动态确定的。

动态代理的实现有两种方式:

  1. JDK动态代理
  2. Cglib动态代理

先看JDK动态代理实例:

1、定义代表买房的接口

public interface BuyHouse {
    // 付钱
    void pay ();
}

2、目标类/被代理类:王富贵

public class FuGuiWang implements BuyHouse{

    @Override
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

3、增强类:要增加的功能

public class ExtraWork {
    public static void findHouse(){
        System.out.println("找房源!");
    }

    public static void other(){
        System.out.println("处理后续事宜...");
    }
}

4、调用处理,合并所有功能,按流程串起来

public class PackageWork implements InvocationHandler {
    // 持有被代理对象
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ExtraWork.findHouse();
        method.invoke(target, args);
        ExtraWork.other();
        return null;
    }
}

5、获取代理对象

public class ProxyFactory {
    public static Object getProxy(Object target){
        PackageWork packageWork = new PackageWork();
        packageWork.setTarget(target);
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), packageWork);
        return proxy;
    }
}

 6、测试与结果

public class TestDynamic {
    public static void main(String[] args) {
        FuGuiWang fuGuiWang = new FuGuiWang();
        BuyHouse proxy = (BuyHouse) ProxyFactory.getProxy(fuGuiWang);
        proxy.pay();
    }
}
找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
处理后续事宜...

上面的JDK动态代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理,因此这也算是一种缺陷,若想代理没有实现接口的类,就需要使用Cglib实现。

六、 Cglib动态代理

上面的静态代理和动态代理模式有个相同点就是都要求目标对象是实现一个接口的对象,然而并不是任何对象都会实现一个接口,也存在没有实现任何的接口的对象,这时就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做:Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

使用Cglib动态代理需要导入下面两个包:

目标类: 

public class FuGuiWang {
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

代理类工厂:

public class CglibProxyFactory implements MethodInterceptor {
    // 持有被代理对象(目标对象)
    private Object fuGuiWang;
    public CglibProxyFactory(FuGuiWang fuGuiWang) {
        this.fuGuiWang = fuGuiWang;
    }

    // 为目标对象创建代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(fuGuiWang.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("找房源!");
        // 反射执行目标对象的方法
        Object result = method.invoke(fuGuiWang, args);
        System.out.println("处理后续事宜...");
        return result;
    }
}
public class TestCglib {
    public static void main(String[] args) {
        FuGuiWang fuGuiWang = new FuGuiWang();
        FuGuiWang proxy = (FuGuiWang) new CglibProxyFactory(fuGuiWang).getProxyInstance();
        proxy.pay();
    }
}
找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
处理后续事宜...

这里目标类没有实现任何接口,它的代理类实际是自己的子类,由这个代理子类完成任务。

CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法(因此cglib不能代理final方法)。在子类中实现了MethodInterceptor接口,可以拦截所有父类(目标类)方法的调用,在调用方法时还可以添加好切面,完成完整的功能,它比使用java反射的JDK动态代理要快。

七、Spring的AOP中的动态代理

动态代理完成了在不修改目标类的条件下,增强目标的功能,这点和Spring中的AOP思想是贴合的,那到底用的是哪种代理呢?下面是AOP的部分源码:

可以看出,在Spring的AOP编程中两种动态代理都用了:如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理。

八、拦截器                                                                      

动态代理是比较复杂难理解的,实际操作中会在外面封装一层拦截器供大家使用,只要知道拦截器接口的方法、参数、含义即可,无需知道动态代理具体实现。流程如下:

定义拦截器接口:

public interface Interceptor {
    public boolean before (Object proxy, Object target, Method method, Object[] args);
    public void around (Object proxy, Object target, Method method, Object[] args);
    public void after (Object proxy, Object target, Method method, Object[] args);
}

这里参数:proxy是代理对象,target真实对象,method方法,args参数

before()方法的值是boolean,返回false时调用around()方法;最后执行after()方法

定义拦截器接口实现类:

public class MyInterceptor implements Interceptor {

    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("真实对象执行前逻辑");
        return false;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代真实对象的方法");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("真实对象执行后逻辑");
    }
}

以JDK动态代理为例,在其中实现拦截器:

public class JDKProxy implements InvocationHandler {

    // 真实对象
    private Object target;
    // 拦截器全限定名(用于反射取得拦截器类)
    private String interceptClass = null;

    // 构造方法
    public JDKProxy(Object target, String interceptClass) {
        this.target = target;
        this.interceptClass = interceptClass;
    }

    // 获取代理对象
    public static Object getProxy(Object target, String interceptClass) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JDKProxy(target, interceptClass));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 若没有拦截器,则直接反射原有方法
        if (interceptClass == null) {
            return method.invoke(target, args);
        }
        Object result = null;
        // 反射取得拦截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptClass).newInstance();
        // 调用前置方法
        if (interceptor.before(proxy, target, method, args)){
            result = method.invoke(target, args);
        }else {
            interceptor.around(proxy, target, method, args);
        }
        // 调用前置方法
        interceptor.after(proxy, target, method, args);
        return result;
    }
}
public class MainTest {
    public static void main(String[] args) {
        BuyHouse proxy = (BuyHouse) JDKProxy.getProxy(new FuGuiWang(), "com.wo.domain.proxy.interceptor.MyInterceptor");
        proxy.pay();
    }
}
真实对象执行前逻辑
取代真实对象的方法
真实对象执行后逻辑

开发者需要完成的任务交给拦截器就可以了,也就是说设计者只需要暴露拦截器给开发者就可以了。

所以总的来说,代理模式就是在访问对象时引入一定程度的间接性,因为这个间接性,可以附加多种用途。

 

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(185)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货