Java设计模式-之代理模式(动态代理)

一、简介

1、什么叫代理模式:
       简而言之就是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,其特征是代理类与委托类有同样的接口。代理模式是一种设计思想。


2、举个例子:
a、以公司前台为例子。你去某个公司面试,首先找前台填表格,前台然后再找HR,真正面试的过程是HR来实现。这里前台就是个代理对象,供外部调用,而真实对象是HR。
b、运营给产品经理提需求,程序员来实现效果。产品经理就是代理类,程序员就是被代理类,实现由程序员来实现。
c、代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。


3、代理模式一般涉及到的角色有:
抽象角色:可以是接口,也可以是抽象类(真实对象和代理对象的共同调用)。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象,业务逻辑的具体执行者。
代理角色:内部含有真实角色的引用,负责对真实角色的调用,并在真实角色处理前后做预处理和善后工作。


4、代理模式优点:
职责清晰:真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可。
高扩展性:不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动。


5、java的代理模式分为静态代理模式和动态代理模式。


二、静态代理

这里以产品经理例子写个静态代理方法。相关代码如下:

/**
 * 抽象角色,实现需求
 */
public interface ICoder {
    void implDemands(String demandName);
}
/**
 * 真实角色,(程序员)实现真正业务
 */
public class JavaCoder implements ICoder{

    private String name;

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

    @Override
    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
}
/**
 * 代理角色 (产品经理)
 */
public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        if(demandName.startsWith("Add")){
            System.out.println("No longer receive 'Add' demand");
            return;
        }
        coder.implDemands(demandName);
    }

}
/**
 * 测试:产品经理实现编程
 */
public class Customer {
    public static void main(String args[]){
        //定义一个java码农(真实角色)
        ICoder coder = new JavaCoder("Zhang");
        //定义一个产品经理(代理角色)
        ICoder proxy = new CoderProxy(coder);
        //让产品经理实现一个需求
        proxy.implDemands("Add user manageMent");
    }
}

产品经理充当了程序员的代理,运营把需求告诉产品经理,并不需要和程序员接触。产品经理当然不只是向程序员转达需求,他还有很多事情可以做。比如,该项目决定不接受新增功能的需求了,对修CoderProxy类方法做一些开始前的检查。
关键点:真实角色和代理角色,都实现抽象角色的接口,然后代理角色调用真实角色的实现接口。


为啥要用动态代理?
静态代理的缺点显而易见:
1、代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有委托类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2、代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为JavaCoder类的访问提供了代理,但是如果还要为其他类如PhpCoder类提供代理的话,就需要我们再次添加代理类。

举例说明:假设有这么一个需求,在方法执行前和执行完成后,打印系统时间。这是非业务逻辑,只要在代理类调用真实角色的方法前、后输出时间就可以了。像上例,只有一个implDemands方法,这样实现没有问题。但如果真实角色有10个方法,那么我们要写10遍完全相同的代码。那么这里我们就需要用动态代理方法了。

三、动态代理

代理类在程序运行时创建的代理方式被称为动态代理。也就是说,代理类并不需要在Java代码中定义,而是在运行时动态生成的。

现在我们利用打印日志的例子来演示一个动态代理。与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。因此,抽象角色、真实角色分别对应ICoder和JavaCodr类内容不变。

/**
 * 定义一个位于代理类与委托类之间的中介类,也叫动态代理类
 * 要求实现InvocationHandler接口
 */
public class CoderDynamicProxy implements InvocationHandler {

    //被代理的实例
    private ICoder coder;
    public CoderDynamicProxy(ICoder _coder){
        this.coder = _coder;
    }

    //调用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(coder, args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}
/**
 * 模拟用户找产品经理更改需求
 */
public class DynamicClient {
    public static void main(String args[]){
        //要代理的真实对象
        ICoder coder = new JavaCoder("Zhang");
        //创建中介类实例
        InvocationHandler handler = new CoderDynamicProxy(coder);
        //获取类加载器
        ClassLoader cl = coder.getClass().getClassLoader();
        //动态产生一个代理类
        ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);
        //通过代理类,执行doSomething方法;
        proxy.implDemands("Modify user management");
    }
}

Proxy.newProxyInstance三个参数分别为:类加载器、代理实例接口、动态中介对象。

执行结果如下:
Java设计模式-之代理模式(动态代理)_第1张图片

通过上述代码,就实现了,在执行委托类的所有方法前、后打印时间。还是那个熟悉的小张,但我们并没有创建代理类,也没有时间ICoder接口。这就是动态代理。


一个典型的动态代理可分为以下四个步骤:
创建抽象角色
创建真实角色
通过实现InvocationHandler接口创建中介类
通过场景类,动态生成代理类( Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler))

三、动态代理的两种实现方式

两种方式:jdk动态代理和cglib动态代理

1、jdk动态代理
如上面的例子就是一个jdk动态代理的方法。
jdk动态代理是由java内部的反射机制来实现的。 JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。
JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。这是jdk动态代理的局限性。

2、cglib框架动态代理
cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。
上面的案例用该种方式实现代码如下:

package proxy.cglibProxy;

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

import java.lang.reflect.Method;

/**
 * 1、首先定义业务类
 * 无需实现接口(当然,实现接口也可以,不影响的)
 */
class JavaCoderImpl {
    private String name;

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

    public JavaCoderImpl() {

    }

    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
}

/**
 * 2、实现 MethodInterceptor方法代理接口,创建代理类
 */
class JavaCoderCglib implements MethodInterceptor {

    private Object obj;//业务类对象,供代理方法中进行真正的业务方法调用

    //相当于JDK动态代理中的绑定
    public Object getInstance(Object target, Class[] argumentTypes, Object[] arguments) {
        this.obj = target;  //给业务对象赋值
        Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
        enhancer.setSuperclass(this.obj.getClass());  //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        // 创建动态代理类对象并返回
        return enhancer.create(argumentTypes, arguments);//不带参数则方法enhancer.create()
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(System.currentTimeMillis());
        methodProxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
        System.out.println(System.currentTimeMillis());
        return null;
    }

}

/**
 * 测试
 */
public class TestCglib {
    public static void main(String[] args) {
        JavaCoderImpl javaCoder = new JavaCoderImpl();//创建业务类对象
        JavaCoderCglib javaCoderCglib = new JavaCoderCglib();//
        JavaCoderImpl javaCoderProxy = (JavaCoderImpl) javaCoderCglib.getInstance(javaCoder, new Class[]{String.class}, new Object[]{"zhangsan"});//创建代理对象
        javaCoderProxy.implDemands("create proxy by cglib");
    }
}


jdk方式必须实现接口,cglib方式不能代理final修饰的类;

四、生成分析

上面的jdk生成动态代理类的场景类中,通过

//动态产生一个代理类
ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);

动态产生了一个代理类。那么这个代理类是如何产生的呢?我们通过代码一窥究竟。

Proxy类的newProxyInstance方法,主要业务逻辑如下:

//生成代理类class,并加载到jvm中
Class cl = getProxyClass0(loader, interfaces);
//获取代理类参数为InvocationHandler的构造函数
final Constructor cons = cl.getConstructor(constructorParams);
//生成代理类,并返回
return newInstance(cons, ih);

上面代码做了三件事:

1、根据传入的参数interfaces动态生成一个类,它实现interfaces中的接口,该例中即ICoder接口的implDemands方法。假设动态生成的类为$Proxy0。
2、通过传入的classloder,将刚生成的$Proxy0类加载到jvm中。
3、利用中介类,调用$Proxy0的$Proxy0(InvocationHandler)构造函数,创建$Proxy0类的实例,其InvocationHandler属性,为我们创建的中介类。

最后总结:
1、一个代理模式主要有几个角色:抽象角色、具体角色(被代理角色)、代理角色、场景类(调用代理角色)
2、静态代理,首先定义一个接口,然后代理角色和被代理角色都要实现这个接口,最后调用方调用代理角色的方法。缺点是都要实现接口,扩展性不好。
3、动态代理四步骤:建抽象角色、建真实角色、创建动态中介类(jdk或cglib动态代理)、场景类动态生成代理类
4、jdk动态代理实现接口InvocationHandler来创建动态中介类
5、cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理
6、jdk动态代理只能针对接口来生成代理类(从Proxy的接口方法newProxyInstance(classLoader,interfaces,invocationHandler)可以看出);cglib采用底层的字节码技术,针对类来实现代理;


参考:说说 JAVA 代理模式

你可能感兴趣的:(设计模式)