结构型模式之代理模式


在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是 代理对象
例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如买房, 你不想直接去售楼部,可以去房产中介代办;想找女朋友找不到你可以把需求告诉媒人,让她给你找;等等。

代理模式的定义与特点

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2.代理对象可以扩展目标对象的功能;
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

其主要缺点是:
1.在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
2.增加了系统的复杂度;

代理模式的结构

代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。
代理模式的主要角色如下:
抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的实现

静态代理

定义:显式声明被代理的对象,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
缺点:重用性不强
1.如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。显而易见,增加了代码维护的复杂度。
2.代理类只服务一个真实主题类:比如下边案例中,张三他爹只给张三代理找对象,来个李四张三他爹就代理不了了,只能在写个李四他爹的代理类来代理给李四找对象。

拿找女朋友来举例:张三单身多年,今天就想找个女朋友,苦于羞涩,不想自己去找。他爹也是着急啊:儿子这么大也没女朋友,着急抱孙子呢还。于是他爹就问儿子想找什么样的女朋友啊?儿子就说:我要肤白貌美大长腿。他爹就替他儿子去物色女朋友啦。

1.抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法

/**
 * 抽象主题:人类
 */
public interface IPerson {
    //自带找女朋友的方法
    void findGirlfriend();
}

2.真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象

/**
 * 真实主题:张三
 */
public class ZhangSan implements IPerson {
    public void findGirlfriend() {
        System.out.println("找个肤白貌美大长腿的女朋友");
    }
}

3.代理(Proxy)类:内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能


/**
 * 代理类:张三他爹,张老三
 */
public class ZhangLaosan implements IPerson {
    private ZhangSan zhangsan;
    //代理:给儿子找女朋友
    public void findGirlfriend() {
        if (zhangsan == null){
            zhangsan = new ZhangSan();
        }
        before();
        zhangsan.findGirlfriend();
        after();
    }
    private void before() {
        System.out.println("张老三开始给儿子物色");
    }
    private void after() {
        System.out.println("儿子和女朋友开始交往");
    }
}

4.测试类:

public class Test {
    public static void main(String[] args) {
        ZhangLaosan zhangLaosan = new ZhangLaosan();
        zhangLaosan.findGirlfriend();
    }
}

5.运行结果:
在这里插入图片描述

动态代理

为了解决静态代理的缺点,才有了动态代理的诞生:动态的配置和替换被代理对象。

JDK 动态代理

JDK 动态代理,代理的必须是接口,因为生成的代理类本身已经继承了Proxy类,java是不允许多继承。

JDK 动态代理的实现原理:
1.拿到被代理类的引用,并且获取他的所有接口(反射获取)。
2.JDK Proxy类重新生成一个新的类,实现了被代理类所有接口的方法。
3.动态生成Java代码,把增强逻辑加入到新生成的代码中。
4.编译生成新的Java代码的class文件。
5.加载并运行新生成的class,得到的类就是全新的类。

演示代码:
还是拿找女朋友来说事,既然是动态代理了,那是不是张三过来也能代理,李四过来也能代理,代理类就相当于媒婆了是吧,不管谁来都能给他找到女朋友啦。

/**
 * 抽象主题:人类
 */
public interface IPerson {
    //自带找女朋友的方法
    void findGirlfriend();
}

2.真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象

/**
 * 真实主题:张三
 */
public class ZhangSan implements IPerson {
    public void findGirlfriend() {
        System.out.println("找个肤白貌美大长腿的女朋友");
    }
}
/**
 * 真实主题:赵六
 */
public class ZhaoLiu implements IPerson {
    public void findGirlfriend() {
        System.out.println("找个有车有房学历高的女朋友");
    }
    
}

3.代理(Proxy)类:

/**
 * 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法。
 */
public class JdkMeipo implements InvocationHandler {
    private IPerson target;
	//获取代理对象的方法
    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz =  target.getClass();
        //调用Proxy的静态方法newProxyInstance(ClassLoader loader,   类加载器
        //                                   Class<?>[] interfaces, 改类所实现的接口
        //                                   InvocationHandler h    当前类实现了InvocationHandler,所以传入当前类
        //                                   ),创建一个代理类。
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target,args);//反射:传入执行该方法的对象,执行该方法需要的参数
        after();
        return result;
    }

    private void after() {
        System.out.println("双方同意,开始交往");
    }

    private void before() {
        System.out.println("我是JDK媒婆,开始给客户物色女朋友");
    }
}

4.测试类:

public class Test {
    public static void main(String[] args) {
        JdkMeipo jdkMeipo = new JdkMeipo();
        //其实返回的zhangsan已经是被代理的全新的类了
        IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
        zhangsan.findGirlfriend();

        IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());
        zhaoliu.findGirlfriend();

    }
}

5.执行结果:
在这里插入图片描述

cglib动态代理

cglib动态代理只需要真实的主题就行了,不需要抽象主题。通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时> > 对这些方法增强,这就是cglib的思想。
切记:CGLib不能代理final的方法。

/**
 * 张三
 */
public class Zhangsan  {

    public void findGirlfriend() {
        System.out.println("找个肤白貌美大长腿的女朋友");
    }
}
/**
 * 李四
 */
public class Lisi {
    public void findGirlfriend() {
        System.out.println("找个有车有房学历高的女朋友");
    }
}
/**
 * cglib动态生成代理类
 */
public class CGlibMeipo implements MethodInterceptor {
    public Object getInstance(Class<?> clazz) throws Exception{
        //相当于Proxy,代理的工具类
        //它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,不同于jdk动态代理,jdk动态代理要求对象必须实现接口
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }
    private void before(){
        System.out.println("我是cglib媒婆,开始给客户物色女朋友");
    }
    private void after(){
        System.out.println("双方同意,开始交往");
    }
}

/**
 * 测试类
 */
public class CglibTest {
    public static void main(String[] args) {
        try {
            Zhangsan zhangsan = (Zhangsan) new CGlibMeipo().getInstance(Zhangsan.class);
            zhangsan.findGirlfriend();

            Lisi lisi = (Lisi) new CGlibMeipo().getInstance(Lisi.class);
            lisi.findGirlfriend();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

JDK 和cglib动态代理的区别
1.JDK动态代理是实现了被代理对象的接口,cglib是继承了被代理的对象,重写他的方法。
2.JDK和cglib动态代理都是在运行期生成字节码,JDK是直接写class字节码,而cglib是使用ASM框架写class字节码,所以cglib的生成代理类的实现更复杂,比JDK效率低
3.JDK调用代理方法,是通过反射机制调用的。cglib是通过fastClass机制直接调用方法,所以cglib调用方法的效率更高。
fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。
另外注意:
spring默认使用jdk动态代理,如果类没有接口,则使用cglib。
可以通过配置来强制使用cglib。
xml:<aop:aspectj-autoproxy proxy-target-class="true"/>
properties:spring.aop.proxy-target-class=true

<<上一篇:建造者模式
>>下一篇:适配器模式

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值