代理模式

Posted 寻找风口的猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代理模式相关的知识,希望对你有一定的参考价值。

什么是代理模式

  对其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的组成

  抽象角色:声明真实对象和代理对象的共同接口

  代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他操作,相当于对真实对象进行封装

  真实角色:代理角色所代表的真实对象,是我们最终要应用的对象

代理模式使用场景

  1. 当我们想要隐藏某个类时,可以为其提供代理类
  2. 当一个类需要对不同的调用者提供提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断来进行不同权限的功能调用)
  3. 当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)

代理模式的分类

  静态代理、动态代理、Cglib代理

1、静态代理:

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

  类图:

  

  看一个例子来了解静态代理。

  步骤一、创建一个抽象角色 Subject.java

package org.burning.sport.design.pattern.proxypattern.notdynamic;
/**
 *  @ProjectName: base-project 
 */
public abstract class Subject {
    public abstract void request();
}

  步骤二、创建一个真实角色 RealSubject.java

package org.burning.sport.design.pattern.proxypattern.notdynamic;
/**
 *  @ProjectName: base-project 
 */
public class RealSubject extends Subject {
    @Override
    public void request() {
        System.out.println("from real subject...");
    }
}

  步骤三、创建一个代理角色 ProxySubject.java

package org.burning.sport.design.pattern.proxypattern.notdynamic;

/**
 *  @ProjectName: base-project 
 */
public class ProxySubject extends Subject {

    private RealSubject realSubject; //代理角色内部引用了真实角色
    @Override
    public void request() {
        this.preRequest();

        if(null == realSubject) {
            realSubject = new RealSubject();
        }

        realSubject.request(); //真实角色完成事情

        this.postRequest();
    }

    private void preRequest() {
        System.out.println("pre request...");
    }

    private void postRequest() {
        System.out.println ("post request...");
    }

}

   步骤四、测试

package org.burning.sport.design.pattern.proxypattern.notdynamic;

/**
 *  @ProjectName: base-project 
 */
public class StaticDynamicMain {
    public static void main(String[] args) {
        Subject subject = new ProxySubject();
        subject.request();
    }
}

 

输出:

pre request...
from real subject...
post request...

总结:

  1、可以做到在不修改目标对象的功能前提下,对目标功能扩展

  2、缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护,如何解决静态代理中的缺点呢?答案是可以使用动态代理方式。

2、JDK动态代理:

  动态代理的特点:

  1、代理对象,不需要实现接口

  2、代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

  3、动态代理也叫做:JDK代理,接口代理

  动态代理代理类:动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

  1、Interface InvocationHandler:该接口中仅定义了一个方法

    - public object invoke(Object obj, Method method, Object[] args) 在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。

  2、Proxy: 该类即为动态代理类,作用相当于静态代理中的ProxySubject

  动态代理UML图:

  

 

  动态代理的例子:

  步骤一、创建一个抽象角色

package org.burning.sport.design.pattern.proxypattern.dynamic;
/**
 *  @ProjectName: base-project 
 */
public interface SubjectInterface {
    public void request();
}

  步骤二、创建一个实体角色

package org.burning.sport.design.pattern.proxypattern.dynamic;
/**
 *  @ProjectName: base-project 
 */
public class RealSubject implements SubjectInterface {
    @Override
    public void request() {
        System.out.println("From real subject");
    }
}

  步骤三、实现InvocationHandler开始动态代理

package org.burning.sport.design.pattern.proxypattern.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 *  @ProjectName: base-project 
 */
public class DynamicSubject implements InvocationHandler {
    private Object sub;

    public DynamicSubject(Object obj) {
        this.sub = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before calling..." + method);
        method.invoke(sub, args);
        System.out.println("after calling..." + method);
        return null;
    }
}

  步骤四、写客户端测试

package org.burning.sport.design.pattern.proxypattern.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 *  @ProjectName: base-project 
 *  @Description: 动态代理
 */
public class DynamicProxyMain {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        InvocationHandler handler = new DynamicSubject(realSubject);
        Class<?> classType = handler.getClass();
        SubjectInterface subjectInterface = (SubjectInterface)Proxy.newProxyInstance(classType.getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);

        subjectInterface.request();

        System.out.println(subjectInterface.getClass());
    }
}

输出:

before calling...public abstract void org.burning.sport.design.pattern.proxypattern.dynamic.SubjectInterface.request()
From real subject
after calling...public abstract void org.burning.sport.design.pattern.proxypattern.dynamic.SubjectInterface.request()
class com.sun.proxy.$Proxy0

总结:

  动态代理相对于静态代理在使用上的优点主要是能够对一个对象的所有方法进行统一包装,而且后期被代理的类添加方法的时候动态代理类不需要改动。

  缺点是要求被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法。

  最后动态代理之所以叫做动态代理是因为java在实现动态代理的时候,动态代理类是在运行时动态生成和加载的,相对的,静态代理类和其他普通类一下,在类加载阶段就加载了

 3.cglib代理

  上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。代理的类不能是final,否则报错;目标对象的方法如果为final/static/private,那么就不会被拦截,即不会执行目标对象额外的业务方法

  步骤一、创建目标对象

package org.burning.sport.design.pattern.proxypattern.cglib;

public class Target {
    public void save(String data) {
        System.out.println("----已经保存数据:" + data + "----");
    }
}

  步骤二、创建cglib代理工厂: ProxyFactory

package org.burning.sport.design.pattern.proxypattern.cglib;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

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

    public ProxyFactory(Object target) {
        this.target = target;
    }

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

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始事务.....");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        return returnValue;
    }
}

  步骤三、测试

package org.burning.sport.design.pattern.proxypattern.cglib;

public class ClientMain {
    public static void main(String[] args) {
        //目标对象
        Target target = new Target();
        //代理对象
        Target proxy = (Target) new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.save("lala");
    }
}

 输出:

开始事务.....
----已经保存数据:lala----

 总结:

  CGLib创建的动态代理对象性能比JDK创建的动态代理对象性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为不需要频繁的创建对象,所以用CGLib合适,反之,使用JDK方式要更为合适一些。

同时,CGLib由于是采用动态创建子类的方法,对于final或者private的方法,无法进行代理

  CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的,诸如private的方法也是不可以作为切面的

  JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的,

诸如private的方法也是不可以作为切面的。

  静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。静态代理是编译阶段生成AOP代理类,也就是说生成的字节码就织入了增强后的AOP对象;动态代理则不会修改字节码,而是在内存中临时生成一个AOP对象,这个AOP对象

包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

  

 

https://gitee.com/play-happy/base-project

参考:

[1] 博客,http://www.cnblogs.com/cenyu/p/6289209.html

[2] 博客,http://blog.csdn.net/zghwaicsdn/article/details/50957474

[3] 技术文章,http://mp.weixin.qq.com/s?__biz=MzAxOTQxOTc5NQ==&mid=2650498033&idx=1&sn=347a1ae3f28fc872981c6da6b2c25d03&chksm=83c8840db4bf0d1b3be7ab5530c1b35515a855eba799cecc8201af686b16e9e4b14ca68f2028&mpshare=1&scene=23&srcid=11138GaO5CTFk6iRplE6NHHA#rd

以上是关于代理模式的主要内容,如果未能解决你的问题,请参考以下文章

scrapy按顺序启动多个爬虫代码片段(python3)

用于从 cloudkit 检索单列的代码模式/片段

java代码实现设计模式之代理模式

代理模式(静态代理动态代理)代码实战(详细)

Java设计模式-代理模式之动态代理(附源代码分析)

代理模式(静态代理)