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

java动态代理

2016-04-05 10:42 513 查看
代理模式是常见的设计模式之一

为什么要使用代理模式?

(1)授权机制: 不同级别的用户对同一对象具有不同的访问权限,可以通过代理模式来控制用户的访问权限;

(2)某个客户端不能直接操作某个对象,但是又必须操作这个对象,这时候需要代理来完成这个工作,因为备操作对象可以将权利授予代理,代理可以和客户端进行交互,起到中间桥梁的作用;

代理模式分为静态代理和动态代理

代理模式中的角色划分:

(1)抽象角色(Subject):用来定义RealSubject和Proxy应该实现的接口;

(2)真实角色(RealSubject):用于真正完成业务服务的功能;

(3)代理角色(Proxy):将自身的Request请求调用RealSubject对应的Request功能来实现业务,自己并不做真正的业务;

下面分别举例说明静态代理和动态代理:

静态代理实例:

很简单的火车票代售点买票就是一种代理模式的应用,这里涉及到三个角色:

(1)抽象角色(Subject):买票、改签、退票这些抽象的方法,在接口中实现;

(2)真实角色(RealSubject):火车站,他实现了抽象角色中的买票、改签、退票

(3)代理角色(Proxy):代售点,他本身并不卖票,只是火车站授予了他卖票的权利,真实的卖票、改签、退票行为还是需要调用火车站的这些方法;

我们定义TicketService接口为抽象角色,里面有buyTicket( ),changeTicket( ),sellTicket( )三个方法:

public interface TicketService {
public void buyTicket();
public void changeTicket();
public void refundTicket();
}
定义Station类为真实角色,实现TicketService接口

public class Station implements TicketService {

@Override
public void buyTicket() {
System.out.println("买票");
}

@Override
public void changeTicket() {
System.out.println("改签");
}

@Override
public void refundTicket() {
System.out.println("退票");
}
}
定义TicketAgent为代理角色,实现TicketService接口,通过构造函数传递进对Station的引用

public class TicketAgent implements TicketService{
public Station station;
public TicketAgent() {}
public TicketAgent(Station station)
{
this.station = station;
}
@Override
public void buyTicket() {
System.out.println("通过代理买票");
station.buyTicket();
System.out.println("收好买票凭据");
}
@Override
public void changeTicket() {
System.out.println("通过代理改签");
station.changeTicket();
System.out.println("收好改签凭据");
}
@Override
public void refundTicket() {
System.out.println("通过代理退票");
station.refundTicket();
System.out.println("收好退票凭据");
}
}
测试函数:

public class Test {
public static void main(String[] args) {
Station station = new Station();
TicketAgent agent = new TicketAgent(station);
agent.buyTicket();
agent.changeTicket();
agent.refundTicket();
/**
* 输出结果:
* 通过代理买票
* 买票
* 收好买票凭据
*
* 通过代理改签
* 改签
* 收好改签凭据
*
* 通过代理退票
* 退票
* 收好退票凭据
*/
}
}

静态代理的优点:

任何时候想要将额外的操作从实际对象中分离到不同的地方,并且希望能够很容易的进行修改,从先前的不使用额外操作转向使用额外操作的时候,代理模式显得特别有用,比如某一时刻,你想跟踪一下RealSubject中某些方法的调用,比如查看这些方法调用的时间开销,显然你不希望修改RealSubject中的方法,这时候可以把哪些跟踪方法放在代理类中,比如上面在代售点购票的一些提示信息等;

静态代理的缺点:

当在代码中规定了代理关系之后,Proxy类通过编译器编译成了.class文件,当系统运行时,.class文件是已经存在的,这种静态代理虽然能够实现访问无法访问的资源,比呢且能够增强现有的业务功能,但是大量使用这种静态代理会使系统内类规模增大,并且不易于维护,此外Proxy和RealSubject的功能本质上是一致的,Proxy只是起到了中介的作用而已,这种代理模式在系统中存在会导致系统结构臃肿和松散;

因此JDK本身为我们提供了动态代理的方式来为我们动态地创建代理并且动态地处理对所代理方法的调用,这里动态创建代理是通过调用Proxy.newProxyInstance( )静态方法实现的,动态的处理代理方法是通过InvocationHandler接口中的invoke方法实现的;

在面向对象的语言中,我们想要实现Proxy和RealSubject有相同的功能有两种方法:

(1):两个类分别实现相同的接口(JDK实现动态代理的方式)

(2):其中一个类继承自另一个类,同时这个类还可以重写原先类中的方法来实现多态(CGLIB实现动态代理的方式)

在JDK实现的动态代理模式中,重要的角色是实现InvocationHandler接口的类,这个类可以作为触发管理器,外界对Proxy角色的调用都会统一由InvocationHandler处理,InvocationHandler会调用真实角色的方法;

JDK为我们动态创建代理类所要做的工作有:

(1):获取到RealSubject所实现的所有接口;

(2):确定要生成的代理类的名字:默认情况下是com.sun.proxy.$Proxy*****;

(3):根据需要实现的接口信息,动态生成该代理类的字节码;

(4):将对应的字节码转换为相应的class对象;

(5):创建实现InvocationHandler接口的实例,用来处理对Proxy方法的调用;

(6):Proxy的class对象以InvocationHandler的实例为参数,实例化一个Proxy对象;

JDK通过java.lang.reflect.Proxy包来支持动态代理,我们使用newProxyInstance来生成代理类的实例;

/**
* loader:类加载器
* interfaces:该代理实现的接口列表
* h:InvocationHandler接口的一个实现
* 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
*/
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

对于实现接口InvocationHandler的类需要实现其invoke方法,在调用代理对象的每一个方法时,在代码内部都是调用InvocationHandler的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法;

// 在代理实例上处理方法调用并返回结
/**
* proxy:代理对象,以防止你需要区分请求的来源,但是很多情况下并不关心这点
* method:实际调用的真实对象的方法
*/
Object invoke(Object proxy,Method method,Object[] args)


动态代理实例:

以买洗衣粉为例,假如我们想买不同品牌的洗衣粉,我们不可能去产家那里去买吧,我们想到的就是去商店,这里的商店就相当于是代理的角色了,他可以实现不同品牌的接口;

奇强洗衣粉接口QiQiang.java

public interface QiQiang {
public void qiQiang();
}
立白洗衣粉接口LiBai.java

public interface LiBai {
public void liBai();
}
商店Store.java用来实现上面两个接口

public class Store implements QiQiang,LiBai{

@Override
public void liBai() {
System.out.println("立白洗衣粉");
}

@Override
public void qiQiang() {
System.out.println("奇强洗衣粉");
}
}
创建实现InvocationHandler接口的类,用来作为触发管理器,需要在他的构造函数中传入真实角色用以能够调用其方法;

public class BuyInvocationHandler implements InvocationHandler{

Store store = null;
public BuyInvocationHandler() {}
public BuyInvocationHandler(Store store)
{
this.store = store;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始调用的方法"+method.getName());
method.invoke(store, args);
System.out.println("结束的调用方法"+method.getName());
return null;
}
}
测试函数:

import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {
//(1)创建真实角色
Store store = new Store();
//(2)获取真实角色的类加载器
ClassLoader classLoader = store.getClass().getClassLoader();
//(3)获取到真实角色的所有接口
Class[] interfaces = store.getClass().getInterfaces();
//(4)将真实角色作为参数设置一个来自代理传过来的方法调用请求器,用以处理所有代理对象上的方法调用
BuyInvocationHandler handler = new BuyInvocationHandler(store);
//(5)根据上面信息生成代理对象
/**
* 这个过程实际上是:
* (1)JDK根据传入的参数信息动态的在内存中创建和.class文件等同的字节码
* (2)将生成的字节码文件转换为对应的class
* (3)调用newInstance创建实例
*/
Object object = Proxy.newProxyInstance(classLoader, interfaces, handler);
QiQiang qiQiang = (QiQiang)object;
qiQiang.qiQiang();
LiBai liBai = (LiBai)object;
liBai.liBai();

/**
* 输出结果:
* 开始调用的方法qiQiang
* 奇强洗衣粉
* 结束的调用方法qiQiang
*
* 开始调用的方法liBai
* 立白洗衣粉
* 结束的调用方法liBai
*/
}
}
到此我们见识到了JDK进行动态代理的整个流程,最主要的一个思想就是将所有代理操作的功能全部交给了实现InvocationHandler接口的触发管理器来实现的;
接下来我们通过:

sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)

提供的底层方法来生成动态代理类的字节码,进而反编译查看一下动态代理到底做了些

什么事;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import sun.misc.ProxyGenerator;
public class GenerateByteCode {
/**
* 生成动态代理的字节码
* @param clazz 真实角色类
* @param name 生成代理类的类名
*/
public void generateByteCode(Class clazz,String name)
{
//根据指定的类信息以及代理类的名字生成代理类的字节码
byte[] classByte = ProxyGenerator.generateProxyClass(name, clazz.getInterfaces());
FileOutputStream stream = null;
try {
stream = new FileOutputStream(new File("d:/"+name+".class"));
stream.write(classByte);
stream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

测试类:

public class Test {
public static void main(String[] args) {
new GenerateByteCode().generateByteCode(new Store().getClass(), "BuyProxy");
}
}


如果上面代码不会出现ProxyGenerator的话,记得将rt.jar包导入到eclipse里面,原因是这个包作为sun公司的内部包,不会默认导入工程中的,这个包在你安装的jre路径的lib目录下面;

这样就会生成一个BuyProxy.class的字节码文件,我们对他放入DJ Java Decompiler中进行反编译得到下列代码:

import com.hzw.day32.LiBai;
import com.hzw.day32.QiQiang;
import java.lang.reflect.*;

public final class BuyProxy extends Proxy
implements QiQiang, LiBai
{

public BuyProxy(InvocationHandler invocationhandler)
{
super(invocationhandler);
}

public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final void qiQiang()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final void liBai()
{
try
{
super.h.invoke(this, m4, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}

private static Method m1;
private static Method m3;
private static Method m0;
private static Method m4;
private static Method m2;

static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("com.hzw.day32.QiQiang").getMethod("qiQiang", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m4 = Class.forName("com.hzw.day32.LiBai").getMethod("liBai", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
我们可以看到这个字节码文件实现了LiBai和QiQiang接口,里面存在5个方法,其中两个方法是m3和m4分别是我们在两个接口中定义的方法,这些方法都是final类型的,也就是说这些方法是不允许重写的,所有方法的实现都是统一调用了InvocationHandler的invoke( )方法的;

现在我们大致理解了JDK动态代理的流程,下面再次捋一遍:

(1)通过为Proxy.newProxyInstance(ClassLoader,Interface,InvocationHandler);传入三个参数来生成动态代理类,这个类中有我们传入的Interface中的方法;

(2)在我们需要这个动态代理类的地方对他进行强制类型转换,转换成相应的类型调用相应接口中的方法,从动态代理类生成的.class反编译后的结果可以看出这个类中的相应接口方法实际上调用的就是InvocationHandler的invoke方法,因此明白了为什么会通过invoke方法来实现过滤的功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: