【设计模式】Proxy 代理模式

我认为设计模式是:一种思想,一种模式,一种套路,一种解决问题的高效策略。下边借用网上常见的一种例子来解释代理模式。
 
什么是代理模式:
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
 
为什么要用代理模式:
一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
在现实生活中,这种情形非常的常见, 当我们想要找明星表演时,不能直接找到,明星会说说:“具体事宜你找我经纪人商谈吧!”,所以只能去找她的经纪人,因此经纪人存在的价值就是拦截/阻止我们对明星的直接访问。
 
所以在这里我们必须要明白两个概念:
1.代理对象存在的价值主要用于拦截对真实业务对象的访问。
2.代理对象应该具有和目标对象(真实业务对象)相同的方法。
代理模式的关键特点是:代理对象是目标对象的扩展,并会调用目标对象。
java中的代理模式:
一、静态代理:
静态代理在使用时,需要定义接口或父类,目标对象(被代理对象,可以理解为明星)与代理对象一起实现相同的接口或者是继承相同的父类。
下面我们举个案例进行解释:
Subject接口,比如我们先定义一个唱歌的方法:
public interface Subject {
    //唱歌的方法
    void sing();
}

现在创建一个委托对象,它实现了Subject接口:

public class Star implements Subject {

    @Override
    public void sing() {
        System.out.println("明星开始唱歌!");
    }
}

然后再创建一个代理对象:

public class Broker implements Subject {
    private Subject subject;

    public Broker(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void sing() {
        //对目标对象的唱歌sing方法进行补充
        System.out.println("向大家问好!");
        this.subject.sing();
        System.out.println("谢谢大家,再见!");
    }
}

最后我们进行测试一下,编写测试类:

/**
* 测试类
*/
public class Main {
    public static void main(String[] args) {
        //创建一个目标对象--明星
        Star star = new Star();
        //创建一个代理对象--经纪人
        Broker tony = new Broker(star);
        //执行代理对象的方法
        tony.sing();
    }
}
运行结果:
 
通过以上代码及运行结果,我们可以看出代理模式的特点,代理类需要接收一个Subject接口对象,任何一个实现了该接口的对象,都可以通过代理类进行代理,增加了通用性。
但是!!!也有缺点,每一个代理类都必须实现一遍委托类的接口,如果接口增加方法,则代理类也必须跟着修改。代理类每一个接口对象对应一个委托对象,而且如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
也就是:
1、如果代理对象里面的方法少的时候可以,但是如果里面很多方法呢?则都需要在代理对象中一一实现,造成了代码的冗余
2、如果这时候这个经纪人同时也是唐嫣的经纪人则代理人中的部分方法不能重复利用
于是对代理进行了升级变为 动态代理,只改变代理本身其他保持不变
 
二、动态代理:
动态代理有以下特点:
  1. 代理对象,不需要实现接口
  2. 代理对象的生成,是利用Java的API,动态的在内存中构建代理对象。(需要我们指定创建代理对象/目标对象实现的接口的类型)
  3. 动态代理也叫做JDK代理、接口代理。
 
动态代理通过反射机制很好的实现代理可以重复调用重复使用,可以在不修改原来代码的基础上添加新特性比如新增话剧、演唱会、舞蹈等
前两步和静态代理一样
public interface Subject {
    void sing();
    void showTime();
}
public class Star implements Subject {
    @Override
    public void sing() {
        System.out.println("明星开始表演了!");
    }

    @Override
    public void showTime() {
        System.out.println("最近演出时间为:" + new Date());
    }
}

下面就有区别了,创建一个动态代理类,实现InvocationHandler接口,并重写该类的invoke方法 :

public class DynamicProxy implements InvocationHandler {
    private Object object;

    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的 handler对象的 invoke方法来进行调用
        Object result = method.invoke(object, args);
        return result;
    }
}

然后测试:

@Test
public void dt() {
    //我们要代理的真实对象
    Subject subject = new Star();
    //要代理哪个对象就把哪个对象传进去,最后是通过该真实对象来调用其方法的
    InvocationHandler handler = new DynamicProxy(subject);
    /*
     * 通过Proxy的newProxyInstance方法来创建我们的代理对象,需要传入三个参数:
     * 1,handler.getClass().getClassLoader(),这里我们使用Handler这个类的ClassLoader对象来加载我们的代理对象。
     * 2,subject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象实现的接口,表示我要代理的是这个真实对象,这样我就可以调用这组接口中的方法了。
     * 3,handler,这个参数的意思是将这个代理对象关联到了上方的InvocationHandler这个对象上。
     */
    Subject tony = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
    tony.sing();
    tony.showTime();
}
JDK中生成代理对象的API:
代理类所在的包:java.lang.reflect.Proxy,这是JDK1.5以后才开始提供的类。
JDK实现代理只需要使用newProxyInstance()方法,但是该方法需要接收三个完整的参数,如下:
 
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
 
该方法是在Proxy类中的静态方法,且接收的三个参数依次为:
ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型。
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
 
动态代理弥补了静态代理的不足,但是这个世界上没有十全十美的事物,我们可以看出静态代理和动态代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如不想让他们必须实现接口的话,那就可以使用CGLIB代理。
 
三、CGLIB代理( Code Generation Library ):
上面的静态代理和动态代理模式都是要求目标对象实现一个接口,但是有的时候目标对象是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式实现代理(可以先理解为让这个明星的儿子/女儿当这个明星的经纪人),这种方法就叫做:CGLIB代理。
 
CGLIB代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
  • JDK的动态代理有一个限制就是使用动态代理的对象必须实现一个或多个接口,如果想代理一些没有实现任何接口的类,就可以使用CGLIB实现。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期间扩展Java类与实现Java接口,它广泛的被众多AOP框架所使用,比如最常用的Spring AOP和synaop,可以为他们提供方法的interception。(拦截)
  • CGLIB 包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不建议直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
 
CGLIB子类代理需要注意的是,目标对象不能为final!
一定要确保你的项目有spring的依赖!!!否则不能使用。如果是个特别单纯的项目那就加一个spring-core-xxx.jar,下面进行代码实现CGLIB代理模式,先创建目标目标对象类:

public class HuGe {
    // 唱歌的方法
    public void sing() {
        System.out.println("====正在唱歌====");
    }


    public void showTime() {
        System.out.println("结束时间:" + new Date());
    }
}

然后创建CGLIB子类代理工厂,对HuGe在内存中动态构建一个子类对象:

 
public class ProxyFactory implements MethodInterceptor {
    //维护目标对象
    private Object object;


    public ProxyFactory(Object object) {
        super();
        this.object = object;
    }
    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(object.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类,也就是代理对象,然后返回
        return en.create();
    }

        @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy             
                            methodProxy) throws Throwable {
//        System.out.println("向大家问好!");
        // 执行目标对象的方法
        Object returnValue = method.invoke(object, args);
//        System.out.println("跟大家说再见!");
        return returnValue;
    }
}

测试:

//CGLIB子类代理
@Test
public void cglib() {
    // 目标对象
    HuGe target = new HuGe();
    // 代理对象
    HuGe proxy = (HuGe) new ProxyFactory(target).getProxyInstance();
    // 执行代理对象的方法
    proxy.sing();
    proxy.showTime();
}

控制台输出:

 
 
发布了30 篇原创文章 · 获赞 12 · 访问量 3477

猜你喜欢

转载自blog.csdn.net/zx1293406/article/details/103387318