您的位置:首页 > 编程语言 > Java开发

java代理模式-动态代理学习

2016-11-04 00:21 302 查看
尊重原创:http://blog.csdn.net/bingju328/article/details/53028729

前言:

通过上一篇 java代理模式-静态代理学习 对静态代理的学习,我们知道静态代理模式中的每个类在编译后就会生成一个class文件,即代理类所实现的方法在编译完成后就都被固定了。如果用这种代理方式,当我们在代理多个对象时,就需要为每个实体类都编写一个新的代理类,这样势必会导致项目中的类越来越多,比如说:在 java代理模式-静态代理学习 一篇中存在的 房天下的房东类:FTXRentSubject(具体主题角色)所对应的代理类为房天下公司类:ProxyFTXSubject(代理对象角色) , 此时若添加一个新的 链家的房东类:LjRentSubject(具体主题角色)必须为这个类新写一个代理类 链家公司类:ProxyLjSubject(代理对象角色),这个时候有人说了,这还好,就加一个新的代理类我能接受啊。那如果需求是有100个具体主题需要被代理呢~~~是不是要疯了。。~_~|||。

那有木有办法在程序运行的时候动态的创建代理类呢?肯定有了,那就是本文将要了解的动态代理技术。为了更好的理解,动态代理要和上一篇的静态代理对比着学习,所以例子依然是在上一篇 文章例子的基础下修改的,不太熟悉的静态代理的同学可以先看一下上一篇 java代理模式-静态代理学习

动态代理介绍:

动态代理可以根据具体主题对象在运行时动态的创建代理类,即一个代理类可以代理多个不同的具体主题对象。

动态代理主要涉及的类:

Proxy : 这个类提供了动态创建代理对象的方法,它也是所有通过它的方法创建的代理对象的父类.

主要方法有:public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);

第一个参数是被代理的对象的类加载器 。

第二个参数是被代理的对象实现的接口集合 。

第三个参数是 InvocationHandler接口的一个实现类,当被代理的具体对象的方法被调用的时候,会调用InvocationHandler的实现类中的invoke()方法。

InvocationHandler:一个代理实例调用处理的接口。

主要方法有:public Object invoke(Object proxy, Method method, Object[] args);

第一个参数是被调用方法的代理实例。

第二个参数是需要代理的方法(即代理实例中要调用的方法)。

第三个参数是第二个参数 method 中所需传入的参数。

InvocationHandler、Proxy、被代理的目标对象三者的关系:

通过Proxy方法返回一个目标对象的代理实例,这个代理实例动态的实现了目标对象所实现的接口,同时也持有InvocationHandler的一个实现类的引用,当代理实例调用目标对象中的方法的时候,这个方法的调用在运行时被编码并且委派给 InvocationHandler的一个实现类中的invoke()方法(就是代理实例调用代理方法的时候其实是调用的InvocationHandler中的invoke()方法),由invoke() 来统一的处理目标对象对方法的调用。

实现代码如下:

抽象主题

/**
* 抽象主题,定义主要功能的接口
* 功能:rent() 租房
* */
public interface RentSubject {

public String rent(String operation);

}


(房天下公司房子的房主)第一个具体主题

/**
* (房天下公司房子的房主)具体主题,是对抽象主题的实现
* */
public class FTXRentSubject implements RentSubject {
public String rent(String operation) {
//房主具体的操作
System.out.println("FTXRentSubject--"+operation);
return operation;
}
}


(房天下公司)第一个代理对象角色

/**
* (房天下公司)代理对象角色,替目标对象(即具体主题,{@link FTXRentSubject})来做相关的操作
* */
public class ProxyFTXSubject implements RentSubject {
private FTXRentSubject ftxRentSubject;

public ProxyFTXSubject(FTXRentSubject ftxRentSubject) {
this.ftxRentSubject = ftxRentSubject;
}

public String rent(String operation) {
//房东租房前的操作
System.out.println(operation+"-before");
//调用目标对象的方法来操作
//此调用方式实质上也是类ProxyFTXSubject和FTXRentSubject之间的一种"组合方式" 的调用
//主要作用是:在需求变更的时候操作代理对象ProxyFTXSubject而不用改变FTXRentSubject类
//从而达到了解耦的效果
ftxRentSubject.rent(operation);
//房东租房后的操作
System.out.println(operation+"-after");
return operation;
}

}


(链家公司房子的房主)第二个具体主题

/**
* (链家公司房子的房主)具体主题,是对抽象主题的实现
* */
public class LjRentSubject implements RentSubject {
public String rent(String operation) {
//房主具体的操作
System.out.println("LjRentSubject--"+operation);
return operation;
}

}


(链家公司)第二个代理对象角色

/**
* (链家公司)代理对象角色,替目标对象(即具体主题,{@link LjRentSubject})来做相关的操作
* */
public class ProxyLjSubject implements RentSubject {
private LjRentSubject ljRentSubject;

public ProxyLjSubject(LjRentSubject ljRentSubject) {
this.ljRentSubject = ljRentSubject;
}

public String rent(String operation) {
//房东租房前的操作
System.out.println(operation+"-before");
//调用目标对象的方法来操作
//此调用方式实质上也是类ProxyFTXSubject和FTXRentSubject之间的一种"组合方式" 的调用
//主要作用是:在需求变更的时候操作代理对象ProxyFTXSubject而不用改变FTXRentSubject类
//从而达到了解耦的效果
ljRentSubject.rent(operation);
//房东租房后的操作
System.out.println(operation+"-after");
return operation;
}

}


客户端类

/**
* 客户端类
* */
public class Client {

public static void main(String[] args){
//房天下的房东客户不能直接找房东租
FTXRentSubject ftxRentSubject = new FTXRentSubject();
ProxyFTXSubject proxyFTXSubject = new ProxyFTXSubject(ftxRentSubject);
//客户给房天下公司发出需求:租房子
proxyFTXSubject.rent("房天下客户租房子");
//链家的房东客户不能直接找房东租
LjRentSubject ljRentSubject = new LjRentSubject();
ProxyLjSubject proxyLjSubject = new ProxyLjSubject(ljRentSubject);
proxyLjSubject.rent("链家客户租房子");

//从现在开始所有所有客户都要办理居住证才可以租房子
proxyFTXSubject.rent(new HandleJuZhuPermit("房天下客户租房子").handlePermit("先办理居住证"));
}
}


上面这个客户端类是静态代理的操作调用,对目标对象FTXRentSubject、LjRentSubject ,我们需要新建两个代理类 ProxyFTXSubject、ProxyLjSubject 来分别代理不同的目标对象,就像文章开头所说那样,如果目标对象有10个20个,那我们需新建10个20个代理类来分别代理不同的目标对象,这样势必会导致代码冗余,所以才需要动态代理的。

下面是动态代理的相关类:

InvocationHandler接口的实现类

/**
* InvocationHandler 这个接口是被Proxy 实例实现的接口,通过Proxy 把每个被代理的
* 对象和InvocationHandler联系起来,当被代理对象的方法被调用的时候会调用 被实现的{@code invoke}
* 方法。
* 此类就是InvocationHandler的类
* */
public class RealInvocationHandler implements InvocationHandler{
//  private RentSubject rentSubject;
private Object object1;

public RealInvocationHandler(Object rentSubject) {
this.object1 = rentSubject;
}
/**
* @param Object proxy 被代理的角色对象
*
* @param Method method 被代理的角色对象将要调用的方法
*
* @param Object[] args 被代理的角色对象调用的方法的参数 集合
* */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy:"+proxy.getClass().getName());
System.out.println("method:"+method.getName());

System.out.println(((args != null && args.length > 0) ? args[0] : "租房子-")+"before");
Object object = method.invoke(object1, args);
System.out.println(((args != null && args.length > 0) ? args[0] : "租房子-")+"after");
return object;
}

}


动态代理的客户端操作类

/**
* 客户端类
* */
public class Client {

public static void main(String[] args){

//动态代理FTXRentSubject目标对象
System.out.println("动态代理--------------");
FTXRentSubject ftxRentSubject = new FTXRentSubject();
/**
* newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
*
* ClassLoader loader 被代理的对象的类加载器
* Class<?>[] interfaces 被代理的对象实现的接口集合
* InvocationHandler h 当被代理对象的方法被调用的时候会调用 被实现的InvocationHandler中的 invoke()方法。
* 根据传入的参数返回一个动态生成的 代理类实例对象
* @return
* */
RentSubject ftxsubject = (RentSubject) Proxy.newProxyInstance(ftxRentSubject.getClass().getClassLoader(),
ftxRentSubject.getClass().getInterfaces(), new RealInvocationHandler(ftxRentSubject));
//调用此方法实质上是调用了 实现的InvocationHandler中的 invoke()方法
ftxsubject.rent("房天下客户租房子");

//动态代理LjRentSubject目标对象
System.out.println("动态代理--------------");
LjRentSubject ljRentSubject = new LjRentSubject();

RentSubject ljsubject = (RentSubject) Proxy.newProxyInstance(ljRentSubject.getClass().getClassLoader(),
ljRentSubject.getClass().getInterfaces(), new RealInvocationHandler(ljRentSubject));
//调用此方法实质上是调用了 实现的InvocationHandler中的 invoke()方法
ljsubject.rent("链家客户租房子");
}
}


上面这个客户端类是动态代理的操作调用,对目标对象FTXRentSubject、LjRentSubject ,只需用同一个方法Proxy.newProxyInstance();这个方法返回一个接口RentSubject 的一个实现类,当代理实例调用目标对象中的rent()方法的时候,这个方法的调用在运行时被编码并且委派给 InvocationHandler的一个实现类(RealInvocationHandler)中的invoke()方法(就是代理实例调用代理方法rent()的时候其实是调用的InvocationHandler中的invoke()方法),由invoke() 来统一的处理目标对象对方法的调用。

后记:

以上就是我学习动态代理模式的相关记录,设计模式只是整个程序大厦中的基础,基础牢固了,再来了解一些基于相应的设计模式编写的开源框架就会比较容易了。比如:了解完了动态代理,这个时候再来看Retrofit的源码就相对容易了,其实Retrofit框架主要的思想就是通过动态的代理每个ApiService的接口,然后在InvocationHandler中的invoke()方法中做相应的处理。这理只做简单的说明,具体分析放在后续的笔记中进行说明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息