代理模式

一、为什么要使用代理模式

我们经常需要去数据库中进行一些读写操作,但是我们在编写程序的时候,会用到connection.close(),将数据库的资源关掉,但是这个操作又是必须去做的。代理模式就是为了解决这个问题的。

二、什么是代理模式

就是给某一个对象提供一个代理,并由代理对象控制对原对象的引用也就是加了一个中间人的概念,我们去引用一个对象的时候,可以不直接引用对象,而是去找这个代理对象。就如租房这个概

念,租房的时候我们不用直接去找房东,而是去找租房中介。

三、代理模式的结构

 

 

Subject(抽象主题):提供了真实主题和代理主题的共同接口,主要访问真实主题都可以通过这个定位到代理主题。我们一般需要去编写一个抽象主题对象。

Proxy(代理主题):

1.包含有真实主题的引用,可以操作真实主题

2.提供了一个与真实主题一样的接口,可以替代真实主题

3.可以对真实主题进行一些控制,需要的时候可以创建或者删除真实主题对象

4.在调用真实主题之前或者之后增加一些其他的操作

RealSubject(真实主题):定义了真实的业务操作,通过调用代理主题间接操作真实主题的业务操作

四、代理模式和装饰模式的异同

代理模式强调的是对真实对象的行为控制,装饰模式强调的是在真实对象上去动态添加方法

代理模式一般是在代理类中去创建一个真实实体,装饰模式是将真实对象变成一个参数传入装饰模式的构造器中。

代理模式虽然会添加一些方法,但是这些方法的侧重点还是在于约束,对真实对象的行为控制。

五、代理模式和委托

代理:是把一些事情交给某人帮忙去完成。

委托:是当某件事情发生的时候,顺便干某件事情。委托就相当于一个触发器罢了。

六、代理模式的种类

(1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。

(2) 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建

(3) 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限

(4) 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

(5) 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

七、不同代理模式的运用场景

1.客户需要访问远程主机的时候就用远程代理

2.比如一个对象需要很长时间才可以完成加载的时候,节约系统开销提高性能,可以采用虚拟代理先创建一个消耗相对较少的对象。

3.当一些操作频繁的被客户端所访问,可以运用缓冲存储,降低系统资源消耗,客户只需要在临时缓冲区获取操作结果。

4.涉及到多用户权限问题的时候就是用保护代理

5.对一个对象的引用加一些其他操作的时候就用智能引用代理

八、代理模式的优缺点

优点:

1.协调调用者和被调用者,降低系统的耦合性。多个用户的操作都只需要一个中间件来完成就可以了

2.客户端可以修改抽象主题类来更换或者增加代理主题,不需要修改源代码,增加了系统的灵活性和可扩展性。

缺点:

1.由于增加了代理主题这个中间件,可能会使请求处理变慢,比如说保护代理,需要先去判断用户的权限。

2.实现代理主题有时需要复杂的操作,比如远程代理。

九、代理模式的实现

1.定义一个女孩类给与一个name的单一属性

public class SchoolGirl {
    /**
     * 女孩类
     */
    private String name;

    public SchoolGirl(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  

2.创建抽象主题接口

public interface IGiveGift {
    /**
     * 抽象主题类:声明代理类和真实类共同方法,抽象主题类可以是
     * 类,接口,抽象类都可以
     */
    public void giveDolls();
    public void giveFlowers();
    public void giveChocolate();
}

  

3.创建真实主题

public class Pursuit implements IGiveGift{
    /**
     * 真实主题类继承抽象主题类
     */
    SchoolGirl girl;

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

    @Override
    public void giveDolls() {
        System.out.println("送你洋娃娃");
    }

    @Override
    public void giveFlowers() {
        System.out.println("送你花花");
    }

    @Override
    public void giveChocolate() {
        System.out.println("送你巧克力");
    }
}

  

4.创建代理主题

public class Proxy implements IGiveGift{
    /**
     * 代理主题
     */
    Pursuit pursuit;
    public Proxy(SchoolGirl girl){
        this.pursuit = new Pursuit(girl);
    }

    @Override
    public void giveDolls() {
        //这里可以加一些其他的操作
        pursuit.giveDolls();
    }

    @Override
    public void giveFlowers() {
        pursuit.giveFlowers();
    }

    @Override
    public void giveChocolate() {
        pursuit.giveChocolate();
    }
}

  

5.测试

public class Test {
    public static void main(String[] args) {
        SchoolGirl schoolGirl = new SchoolGirl("幺幺");
        Proxy proxy = new Proxy(schoolGirl);
        proxy.giveDolls();
        proxy.giveFlowers();
        proxy.giveChocolate();
    }
}

  

总结:首先我们一般会定义一个接口,然后让真实主题和代理主题都去实现这些接口中的方法。然后代理主题的构造方法其实里面就直接创建了一个真实主题的对象。最后main方法里只需要new代理对象,但是返回的是真实主题的结果。

 参考于:深入理解设计模式(23):代理模式 - 一指流砂~ - 博客园 (cnblogs.com)

补充:

有哪几种代理模式?

       我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。

1.静态代理     

 一步:创建服务类接口

 

复制代码
 1 package main.java.proxy;
 2 
 3 /**
 4  * @Auther: dan gao
 5  * @Description:
 6  * @Date: 22:40 2018/1/9 0009
 7  */
 8 public interface BuyHouse {
 9     void buyHosue();
10 }
复制代码

 

第二步:实现服务接口

复制代码
 1 import main.java.proxy.BuyHouse;
 2 
 3 /**
 4  * @Auther: dan gao
 5  * @Description:
 6  * @Date: 22:42 2018/1/9 0009
 7  */
 8 public class BuyHouseImpl implements BuyHouse {
 9 
10     @Override
11     public void buyHosue() {
12         System.out.println("我要买房");
13     }
14 }
复制代码

第三步:创建代理类

复制代码
 1 package main.java.proxy.impl;
 2 
 3 import main.java.proxy.BuyHouse;
 4 
 5 /**
 6  * @Auther: dan gao
 7  * @Description:
 8  * @Date: 22:43 2018/1/9 0009
 9  */
10 public class BuyHouseProxy implements BuyHouse {
11 
12     private BuyHouse buyHouse;
13 
14     public BuyHouseProxy(final BuyHouse buyHouse) {
15         this.buyHouse = buyHouse;
16     }
17 
18     @Override
19     public void buyHosue() {
20         System.out.println("买房前准备");
21         buyHouse.buyHosue();
22         System.out.println("买房后装修");
23 
24     }
25 }
复制代码

第四步:编写测试类

复制代码
import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.BuyHouseProxy;

/**
 * @Auther: dan gao
 * @Description:
 * @Date: 22:43 2018/1/9 0009
 */
public class ProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        buyHouse.buyHosue();
        BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHosue();
    }
}
复制代码

静态代理总结:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。                                             

2.动态代理

  在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。

第一步:编写动态处理器

复制代码
 1 package main.java.proxy.impl;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @Auther: dan gao
 8  * @Description:
 9  * @Date: 20:34 2018/1/12 0012
10  */
11 public class DynamicProxyHandler implements InvocationHandler {
12 
13     private Object object;
14 
15     public DynamicProxyHandler(final Object object) {
16         this.object = object;
17     }
18 
19     @Override
20     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
21         System.out.println("买房前准备");
22         Object result = method.invoke(object, args);
23         System.out.println("买房后装修");
24         return result;
25     }
26 }
复制代码

第二步:编写测试类

复制代码
 1 package main.java.proxy.test;
 2 
 3 import main.java.proxy.BuyHouse;
 4 import main.java.proxy.impl.BuyHouseImpl;
 5 import main.java.proxy.impl.DynamicProxyHandler;
 6 
 7 import java.lang.reflect.Proxy;
 8 
 9 /**
10  * @Auther: dan gao
11  * @Description:
12  * @Date: 20:38 2018/1/12 0012
13  */
14 public class DynamicProxyTest {
15     public static void main(String[] args) {
16         BuyHouse buyHouse = new BuyHouseImpl();
17         BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
18                 Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
19         proxyBuyHouse.buyHosue();
20     }
21 }
复制代码

 注意Proxy.newProxyInstance()方法接受三个参数:

  • ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

3.CGLIB代理

       JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

第一步:创建CGLIB代理类

复制代码
 1 package dan.proxy.impl;
 2 
 3 import net.sf.cglib.proxy.Enhancer;
 4 import net.sf.cglib.proxy.MethodInterceptor;
 5 import net.sf.cglib.proxy.MethodProxy;
 6 
 7 import java.lang.reflect.Method;
 8 
 9 /**
10  * @Auther: dan gao
11  * @Description:
12  * @Date: 20:38 2018/1/16 0016
13  */
14 public class CglibProxy implements MethodInterceptor {
15     private Object target;
16     public Object getInstance(final Object target) {
17         this.target = target;
18         Enhancer enhancer = new Enhancer();//获取字节码增强器
19         enhancer.setSuperclass(this.target.getClass());//设置要代理的类
20         enhancer.setCallback(this);//设置回调即MethodInterceptor的实现类
21         return enhancer.create();//生成一个代理对象,注意要强转一下,因为返回的是Object
22     }
23 
24     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
25         System.out.println("买房前准备");
26         Object result = methodProxy.invoke(object, args);
27         System.out.println("买房后装修");
28         return result;
29     }
30 }
复制代码

 

第二步:创建测试类

复制代码
 1 package dan.proxy.test;
 2 
 3 import dan.proxy.BuyHouse;
 4 import dan.proxy.impl.BuyHouseImpl;
 5 import dan.proxy.impl.CglibProxy;
 6 
 7 /**
 8  * @Auther: dan gao
 9  * @Description:
10  * @Date: 20:52 2018/1/16 0016
11  */
12 public class CglibProxyTest {
13     public static void main(String[] args){
14         BuyHouse buyHouse = new BuyHouseImpl();
15         CglibProxy cglibProxy = new CglibProxy();
16         BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
17         buyHouseCglibProxy.buyHosue();
18     }
19 }
复制代码

 

CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

参考于:https://www.cnblogs.com/daniels/p/8242592.html

posted @ 2021-01-22 12:07  Yaoyaoo  阅读(262)  评论(0)    收藏  举报