代理模式与java动态代理

定义

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。有点类似于装饰者模式。但是与装饰者模式的区别是代理模式最终不一定调用目标对象的目标方法。但装饰者一定会。

图示:


理解

     通俗一点说,就是代理对象持有目标对象的引用,并对外提供所有目标对象的业务方法,在该方法中添加一些逻辑处理并决定是否调用目标对象方法。代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会根据情况调用目标对象。代理模式又分为静态代理和动态代理。下面我们分别做介绍。

静态代理

角色:

1、抽象角色:接口
2、真实角色:真正业务对象
3、代理角色:代理对象

静态代理的内容比较简单,这里我们先借鉴网上的一个很贴切的例子来说明,然后通过例子中总结一下。

实例

在开发中有这样三种角色能够很好的类比代理模式。
用户:提出需求的人 调用者
产品经理:把控需求的人 代理对象
开发人员:实现需求的人 真实角色
1、定义开发人员接口,它有一个实现用户需求的方法
public interface ICoder {
    public void implDemands(String demandName);

}

2、定义真实角色类:开发人员

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!");
    }
3、代理角色,其中持有真实角色对象。并同时实现开发接口,在实现方法中调用真实角色的业务方法,当然在调用前后会扩展逻辑,要不然岂不是白代理了。这里并没有添加业务逻辑,下面的代码会加。
public class CoderProxy implements ICoder{

    private ICoder coder;

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

    @Override
    public void implDemands(String demandName) {
        coder.implDemands(demandName);
    }

}
4、为代理的方法添加额外的业务逻辑
产品经理不仅仅只能调用真实角色的业务方法,肯定要扩展自己的逻辑。例如现在决定不接受新功能的增加,只处理bug。那么我们可以把代理类做一些修改。
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);
    }
}
这样就在代理这一层过滤掉了新增加功能的要求(扩展逻辑),而不是修改真实角色的逻辑。这样既扩展了逻辑也保证了业务方法的独立性和纯洁性。

4、测试

public class Customer {

    public static void main(String args[]){
        //定义一个java码农
        ICoder coder = new JavaCoder("Zhang");
        //定义一个产品经理
        ICoder proxy = new CoderProxy(coder);
        //让产品经理实现一个需求
        proxy.implDemands();
    }
}
我们对上面的事例做一个简单的抽象:

代理模式包含如下角色:
  • Subject:抽象主题角色。可以是接口,也可以是抽象类。
  • RealSubject:真实主题角色。业务逻辑的具体执行者。
  • ProxySubject:代理主题角色。内部含有RealSubject的引用,负责对真实角色的调用,并在真实主题角色处理前后做预处理和善后工作。
代理模式优点:
  • 职责清晰 真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可。
  • 高扩展性 不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动。

动态代理

    假设有这么一个需求,在方法执行前和执行完成后,打印系统时间。这很简单嘛,非业务逻辑,只要在代理类调用真实角色的方法前、后输出时间就可以了。像上例,只有一个implDemands方法,这样实现没有问题。但如果真实角色有10个方法,那么我们要在代理类的每一个代理方法中去写一遍相同的代码,这里我们就可以采用动态代理来解决。
    代理类在程序运行时创建的代理方式被称为动态代理。也就是说,代理类并不需要在Java代码中定义,而是在运行时动态生成的。 这样就可以根据真实角色来动态创建代理类,而不是静态的创建10个代理类。我们在动态代理中对代理类逻辑做统一处理,而不是修改每个代理类的方法。
与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。因此,抽象角色、真实角色,参考ICoder和JavaCoder。
在使用动态代理时,我们需要定义一个位于代理类与委托类(真是角色)之间的中介类,也叫动态代理类,这个类被要求实现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;
    }
}
当我们调用代理类对象的方法时,这个“调用”会转送到中介类的invoke方法中,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。此时我们便可以在动态代理对象执行方法前后扩展逻辑,例如这里添加打印时间。
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方法;这里就走到了CoderDynamicProxy.invoke()中
            proxy.implDemands("Modify user management");
        }
}

执行结果如下:

1501728574978
Zhang implemented demand:Modify user management in JAVA!
1501728574979
    这里我们重点看下动态产生代理类的方法,通过Proxy.newProxy.newProxyInstance()方法来创建动态代理对象:
  • 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
  •  第二个参数coder.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
  •  第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
首先,我们解释一下动态生成的代理类的类型为什么可以是ICoder,因为方法newProxyInstance第二个参数传入了真实对象所实现的接口。那么动态生成的代理类就会实现这组接口,那么生成的代理对象便可以转化为这组接口中的任一一个。
另外,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

接下来,我们调用动态代理对象的implDemands()方法。这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行。此时通过method.invoke(coder,args)来调用真实对象的业务方法。

总结一下,一个典型的动态代理可分为以下五个步骤:

1、创建抽象角色

2、创建真实角色

3、通过实现InvocationHandler接口创建中介类

4、通过Proxy.newProxyInstance()生成一个动态代理对象

5、通过代理对象调用业务方法

动态代理的使用基本就是这样,接下来我们深入JDK看看java是如何做到动态生成一个代理类,代码又是如何执行到中介类InvocationHandler.invoke()里面的。

进入Proxy.newProxyInstance()源码中查看主要逻辑如下:

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

我们一步一步分析上述几行代码

第一步生成代理类,该例中生成的便是实现ICoder接口的类,并将该类加载到jvm中,进入getProxyClass0()中核心逻辑如下:

 Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }

通过Class.forName()生成代理类并加载到jvm中,返回Class对象。Calss.forName()中调用的是native方法

第二步获取构造函数

我们可以看到constructorParams是Proxy维护的静态变量,其中包括InvocationHandler,源码如下:

    /** parameter types of a proxy class constructor */
    private final static Class[] constructorParams =
        { InvocationHandler.class };

第三步通过构造函数创建实例,参数为构造器和构造时需要使用的中介类InvocationHandler

    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException | InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

    这样,就解决了我们提出的第一个问题:如何生成代理对象。其实这部分涉及到类加载机制相关的知识,不熟悉的同学可以先学习这部分内容。那么,为什么我们调用代理对象的业务方法,就会执行到中介类InvocationHandler.invoke()方法中呢?这就有从生成的代理类代码中寻找答案了。我们可以手动模拟生成代理类,如下:

public class CodeUtil {

       public static void main(String[] args) throws IOException {
            byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", JavaCoder.class.getInterfaces());
            File file = new File("D:/aaa/TestProxyGen.class");
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(classFile);
            fos.flush();
            fos.close();
          }
            }

注意这里的类名TestProxyGen是我们模拟的,真正的类名还是$Proxy0

我们查看生成代理类的代码,其中,业务方法implDemands如下:


这里调用了h也就是InvocationHandler的invoke方法,这就能解释为何我们调用代理对象的业务方法,最终走到了中介类的invoke()方法中了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值