设计模式之代理模式

15 代理模式

15.1 代理模式简介

  • 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处
    是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  • 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  • 代理模式有不同的形式, 主要有三种 静态代理、理 动态代理 (JDK 代理、接口代理)和 Cglib 理 代理 (可以在内存
    动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。

代理模式UML类图:

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

15.2 静态代理

15.2.1 静态代理基本介绍

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

15.2.2 静态代理案例实现

需求:

模拟用户购买iPhone手机的例子。

用户买iPhone手机,去专卖店购买。

具体步骤:

  • 定义手机的规则,不能你说是手机就是手机。
/**
 * 接口的出现是为了定义规则用的
 */
public interface Phone {
    //打电话
    public void call();
    //玩游戏
    public void play();
    //拍照片
    public void photo();
}
  • 定义商店的规则,不可能谁都可以卖iPhone,要进行授权
public interface Store {
    public Phone selliPhone();
}
  • 具体手机实现类,iPhone
/**
 * 这个类用来描述手机
 * 就是自己的一些描述
 * 这些是电话的一些功能描述----方法
 */
public class iPhone implements Phone{

    //描述属性
    private String name;
    private String color;

    //构造方法
    public iPhone(){}
    public iPhone(String name,String color){
        this.name=name;
        this.color=color;
    }

    //打电话
    public void call(){
        System.out.println("真的iPhone 打电话很流畅");
    }
    //玩游戏
    public void play(){
        System.out.println("真的iPhone 玩游戏不卡顿");
    }
    //拍照片
    public void photo(){
        System.out.println("真的iPhone 拍照片很清晰");
    }
}
  • 具体iPhone专卖店
/**
 * 这个类是苹果专卖店
 * 理解为苹果的工厂-----苹果专卖店销售
 * 前店后厂
 */
public class AppleStore implements Store{

    //这是一个工厂店-----手机    关系  依赖
    //依赖关系体现  一个类的方法中使用到了另一个类的对象(传参数 方法内创建)

    //生产真机
    //  做事情之前是否需要提供什么条件---参数
    //  做事情之后是否需要留下什么结果---返回值
    private iPhone productPhone(){//可以理解为这个方法是工厂店自己的
        //创建一台手机
        iPhone iPhone = new iPhone("iPhone7","black");
        //将生出来的手机返回出去
        return iPhone;
    }

    //卖手机
    //  做事情之前是否需要条件---参数
    //  做事情之后是否需要结果---返回值
    public Phone selliPhone(){
        //找自己的工厂生产一台手机
        iPhone iPhone = this.productPhone();
        //将工厂方法生产的手机返回出去
        return iPhone;
    }
}
  • 提供具体代理类帮我们干活,苹果专卖店可能太多人了,我找人帮我卖
  • 代理类不仅能去专卖店拿手机卖而且还能造假货。
/**
 * 可以理解为这是一个代理商-------在中间转手一下
 *
 * 代理模式
 */
public class ProxyStore implements Store{

    //聚合关系  代理店--工厂店
    private AppleStore store = new AppleStore();

    //如果想要造假    自己在山沟沟里造一个山寨工厂
    private lPhone productlPhone(){
        lPhone lPhone = new lPhone();
        return lPhone;
    }

    //代购  代理------卖手机
    public Phone selliPhone(){//可以在真正用户不知情的情况下修改执行的过程
        //1.有一台手机????  真机  找工厂店
        Phone iPhone = this.store.selliPhone();
        //2.有一个山寨工厂     山寨手机
        Phone lPhone = this.productlPhone();
        //3.手机卖出去
//        return iPhone;
        return iPhone;
    }
}
  • 客户端测试
public class TestMain {
    public static void main(String[] args) {
        //1.有一个代理店
        Store store = new ProxyStore();
        //2.代理店卖手机
        Phone iPhone = store.selliPhone();
        //3.用户使用
        iPhone.call();
        iPhone.play();
        iPhone.photo();
    }
}

测试结果:
    真的iPhone 打电话很流畅
    真的iPhone 玩游戏不卡顿
    真的iPhone 拍照片很清晰
15.2.3 静态代理的优缺点

1、主要优点

  • 被代理对象只要和代理类实现了同一接口即可,代理类无须知道被代理对象具体是什么类、怎么做的,而客户端只需知道代理即可,实现了类之间的解耦合。

2、主要缺点

  • 代理类和被代理类实现了相同的接口,代理类通过被代理类实现了相同的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
  • 每个代理类只能为一个接口服务,如果程序开发中要使用代理的接口很多的话,必然会产生许多的代理类,造成类膨胀。

15.3 JDK动态代理

15.3.1 jdk动态代理基本介绍
  • 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  • 代理对象的生成,是由java内部的反射机制来实现的。
15.3.2 jdk动态代理案例实现

需求:

同样是用户进行手机购买,但是此时是去代工厂购买,代工厂卖各种手机

实现步骤:

  • 定义手机规则
/**
 * 这个类用来描述一个手机
 * 有一些功能(方法)
 * 打电话  玩游戏  拍照片
 */
public interface Phone {
    //描述一下手机的功能
     void call();

     void play();

     void photo();
}
  • 手机具体实现类
// iPhone手机
public class iPhone implements Phone {

    @Override
    public void call() {
        System.out.println("iPhone12打电话真好用");
    }

    @Override
    public void play() {
        System.out.println("iPhone12玩王者真好用");
    }

    @Override
    public void photo() {
        System.out.println("iPhone12照相真好用");
    }
}
// 华为手机
public class Mate40 implements Phone {

    @Override
    public void call() {
        System.out.println("华为Mate40打电话带劲");
    }

    @Override
    public void play() {
        System.out.println("华为Mate40打吃鸡带劲");
    }

    @Override
    public void photo() {
        System.out.println("华为Mate40照相带劲");
    }
}
  • 定义生产线规则
// 华为生产线
public interface HuaweiStore {
    Mate40 salePhone();
}

// 苹果生产线
public interface AppleStore{
    // 苹果专卖店只负责卖,找代工厂负责生产
    iPhone salePhone();
}

  • 创建代理工厂对象
public class ProxyStore {

    //描述一个方法 通过Proxy这个类获取一个代理对象(代工商店)
    //  1.方法是一件事情  做事情之前是否需要条件---参数   代理谁? AppleStore
    //  2.方法是一件事情  做事情之后是否需要结果---返回值  代理对象--利用泛型
    public static <T> T getStore(Class clazz) {
//        //分析创建proxy代理对象需要的条件
//        //0.类加载器---目的是把需要代理的AppleStore这个类加载进来
        ClassLoader classLoader = clazz.getClassLoader();
//        //1.代理谁 AppleStore(传递的参数clazz)  下面第二个问号需要数组
        Class[] classes = new Class[]{clazz};
//        //2.代理他之后 具体该做什么事情---原来真正店做的事情 卖手机
        InvocationHandler handler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                //内部类中方法的三个参数含义
//                //1.proxy代理对象  2.method代理的那个方法  3.args代理方法传递的参数
                // 想让这个工厂根据传递的参数,代工不同的产品
                // 1、获取需要被代工的类
                Class clazz = method.getDeclaringClass();
                // 2、判断clazz从而执行不同的代工
                if (clazz == AppleStore.class) {
                    return new iPhone();
                } else if (clazz == HuaweiStore.class) {
                    return new Mate40();
                } else {
                    return null;
                }
            }
        };
//        //1.通过Proxy类帮我们创建一个代理对象
        T proxy = (T) Proxy.newProxyInstance(classLoader, classes, handler);
//        //2.将这个proxy代理对象返回
        return proxy;
    }
}
  • 客户端测试
public class TestMain {
    //动态代理
    //  可以理解为是一个代工厂
    //  富士康公司(代工 什么品牌都代工  好多生产线  做苹果 做华硕 做索尼)
    //  可以理解为  可以出了苹果之外还能卖别的品牌手机
    //  由于这个对象什么都可以做    我们不自己创建---找人帮忙(创建一个这个代理商店)
    //  Proxy类  Java给我们提供的 Math String ArrayList
    //  Proxy类可以帮我们创建一个代理对象(这个对象就是动态代理的对象 什么都能做)
    
    public static void main(String[] args) {
        //1.需要有一个店----可以找代理店(代理店不是new 是通过自己写的类方法创建出来)
        AppleStore store = ProxyStore.getStore(AppleStore.class);
//        HuaweiStore store = ProxyStore.getStore(HuaweiStore.class);
        //2.商店做事情----卖手机
        Phone phone = store.salePhone();
//        Phone phone = store.saleiPhone();
        //3.拿去使用
        phone.call();
        phone.play();
        phone.photo();
    }
}

测试结果:
    iPhone12打电话真好用
    iPhone12玩王者真好用
    iPhone12照相真好用

注意:

这里我们如果想要华为手机,只需要HuaweiStore store = ProxyStore.getStore(HuaweiStore.class);

15.4 Cglib动态代理

15.4.1 Cglib动态代理简介
  • 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个 单独的对象,并没 有实
    现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
  • Cglib代理也叫作 子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代
    理归属到动态代理。
  • Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的
    框架使用,例如 SpringAOP,实现方法拦截
  • 在 AOP 编程中如何选择代理模式:
    • 目标对象需要实现接口,用 JDK 代理
    • 目标对象不需要实现接口,用 Cglib 代理
  • Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程

低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代

理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

15.4.2 Cglib动态代理实现步骤
  • 需要引入cglib的jar文件
  • 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

代码实现:

  • 明星类
public class Star {
    private String name;

    public Star() {
    }

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

    public void Sing(String singName) {
        System.out.println(singName + ": 唱歌中...");
    }

    public void Dance(String name) {
        System.out.println(name + "跳舞中...");
    }

    final public void rap(String name) {
        System.out.println(name + "说唱中...");
    }

}
  • 代理类
public class CglibProxyFactory<T> implements MethodInterceptor {
    //目标对象
    private T target;

    public CglibProxyFactory(T target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        //工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(target.getClass());
         设置enhancer的回调对象,实现了MethodInterceptor接口的类的对象
        enhancer.setCallback(this);
        //创建子类代理对象
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("表演前签合同1~");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("表演后收取费用2~");
        return obj;
    }
}
  • 客户端测试
public class Client {
    public static void main(String[] args) {
        Star star = new Star();
        CglibProxyFactory<Star> proxyFactory = new CglibProxyFactory<Star>(star);
        Star proxyInstance = (Star) proxyFactory.getProxyInstance();
        proxyInstance.Dance("刘德华");
        proxyInstance.Sing("忘情水");
        proxyInstance.rap("刘德华");
    }
}

测试结果:
    表演前签合同1~
    刘德华跳舞中...
    表演后收取费用2~
    表演前签合同1~
    忘情水: 唱歌中...
    表演后收取费用2~
    表演前签合同1~
    刘德华说唱中...
    表演后收取费用2~

小结:

  • CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。
  • 所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
  • 同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值