即Proxy Pattern,23种常用的面向对象软件设计模式之一。(设计模式的说法源自<设计模式>一书,原名<Design Patterns:Elements of Resuable Object-Oriented Software>,1995年出版,出版社:Addison Wesly Longman.Inc。 该书提出了23中基本设计模式) 代理模式的定义:为其他对象提供一种"代理"在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间提到中介的作用。
代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、远程代理、缓存代理、静态代理和动态代理,本文主要讲解动态代理。
近年来,代购已逐步成为电子商务的一个重要分支。何谓代购,简单来说就是找人帮忙购买所需要的商品,当然你可能需要向实施代购的人支付一定的费用。代购通常分为两种类型:一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或者直接携带回来;还有一种代购,由于消费者对想要购买的商品相关信息的缺乏,自已无法确定其实际价值而又不想被商家宰,只好委托中介机构帮其讲价或为其代买。代购网站为此应运而生,它为消费者提供在线的代购服务,如果看中某国外购物网站上的商品,可以登录代购网站填写代购单并付款,代购网站会帮助进行购买然后通过快递公司将商品发送给消费者。商品代购过程如图所示:
代沟.png
在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想活着不能直接访问一个对象,此时可以通过一个称之为"代理"的第三者来实现间接访问,该方案对应的设计某事被称为代理模式。
所以总结下代理模式的定义如下:
代理模式:给一个对象提供一个代理或者占位符,并由代理对象来控制原对象的访问。 Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.
代理模式是一种对象结构结构模式。在代理模式中银瑞一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者添加客户需要的额外的新服务。
更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
代理模式的结构比较简单,其核心就是代理类,为了让客户能够一致性地对待真是对象和代理对象,在代理模式中引入了抽象层,代理模式结构如下:
代理结构模式.png
由上图可知,代理模式包含如下三个角色:
Java代理模式就是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给为委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真实实现服务,而是通过调用委托类的对象关联,代理类的对象本身并不真正的实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期,代理类可以分为两种。
代理的使用场景很多,struts2中的action调用,hibernate的懒加载,spring的AOP无一不用到代理,当然还有咱们的主题Retrofit也用到了,总结起来就是可以分为以下几类:
接口UserManager
/***
* 用户控制接口
* @author Administrator
*
*/
public interface UserManager {
public void addUser(String userId,String userName);
public void modifyUser(String userId,String userName);
public void delUser(String userId);
public String findUser(String userId);
}
实现类
/****
* 用户管理真正的实现类
* @author Administrator
*
*/
public class UserManagerImpl implements UserManager {
/*****
* 添加用户
*/
public void addUser(String userId, String userName) {
System.out.println("正在添加用户,用户为:"+userId+userName+"……");
}
/*****
* 删除用户
*/
public void delUser(String userId) {
System.out.println("delUser,userId="+userId);
}
/***
* 查找用户
*/
public String findUser(String userId) {
System.out.println("findUser,userId="+userId);
return userId;
}
public void modifyUser(String userId, String userName) {
System.out.println("modifyUser,userId="+userId);
}
}
代理类Proxy
/***
* 代理类,提供用户实现类的访问控制
* @author Administrator
*
*/
public class Proxy implements UserManager{
private UserManager userManager;
public Proxy(UserManagerImpl ul)
{
userManager=ul;
}
public void addUser(String userId, String userName) {
System.out.println("正在进行添加用户前的准备工作,用户id为:"+userId+"……");
try {
userManager.addUser(userId, userName);
System.out.println("成功添加用户"+userId+",正在进行确认处理……");
} catch (Exception e) {
System.out.println("添加,userId="+userId+"失败!");
}
}
public void delUser(String userId) {
// TODO Auto-generated method stub
}
public String findUser(String userId) {
// TODO Auto-generated method stub
return null;
}
public void modifyUser(String userId, String userName) {
// TODO Auto-generated method stub
}
}
客户端Client
/****
* 客户端
* @author Administrator
*
*/
public class client {
public static void main(String []args)
{
UserManager userManager=new Proxy( new UserManagerImpl());
userManager.addUser("0001", "张三");
}
}
运行结果
结果.png
时序图.png
代理是客户端不需要知道实现类是什么,怎么做的,而客户端只需要知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。
为了解决上述问题,所以诞生了动态代理
我们来说一下动态代理,静态代理之所以扩展和维护比较困难,是因为代码写的太死,没有可替换的余地,针对代码写的死能想到什么解决办法?对,就是反射。使用反射就可以解决决定加载那个代理类的问题,避免了每个代理类都要重复写的问题。这里主要说一下Java动态代理的实现。
Java编译好Java文件之后,产生.class文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载内存中,解析.class文件内的信息,生成对应的Class对象 如下图:
class加载.png
由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力。
生成2进制字节码.png
Java中有很多的框架可以在运行时根据JVM规范动态的生成对应的.class字节码,比如ASM和Javassist等,这里就不详细介绍了,感兴趣的就可以去查询相关的资料。在Java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
首先我们先来看看Java的API帮助怎么对这个类进行描述:
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
注释翻译一下就是:
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
public class Proxy implements Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
throw new RuntimeException("Stub!");
}
//根据指定的类加载器和接口来获取代理类
public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
throw new RuntimeException("Stub!");
}
//根据指定的类加载器和接口生成动态代理类的对象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
throw new RuntimeException("Stub!");
}
//判断指定的对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl) {
throw new RuntimeException("Stub!");
}
//获取指定代理对象关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
throw new RuntimeException("Stub!");
}
}
Proxy
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
翻译一下就是:
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法。但是我们用的最多的就是 newProxyInstance 这个方法:
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler. This method is equivalent to:
* <pre>
* Proxy.getProxyClass(loader, interfaces).
* getConstructor(new Class[] { InvocationHandler.class }).
* newInstance(new Object[] { handler });
* </pre>
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
* {@code Proxy.getProxyClass} does.
*
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
* {@code null}
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 检查 h 不为空,否则抛异常
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
//获得与指定类装载器和接口相关的代理类类型对象
Class<?> cl = getProxyClass0(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
// 通过反射获取构造函数对象并生成代理类实例
final Constructor<?> cons = cl.getConstructor(constructorParams);
return newInstance(cons, h);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
看下注解大家就知道了newProxyInstance(ClassLoader,Class<?>[] ,InvocationHandler )这个方法的作用就是得到一个动态代理的对象,其接受三个参数,我们来看看这三个参数所代表的含义。
它还有一个方法也是我们经常会用到的就是
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
return getProxyClass0(loader, interfaces);
}
用来产生代理类 来说下对应的两个参数
由于篇幅有限,Proxy类中的代码还算简单,这里就不详解介绍Proxy类。
静态代理.png
上面就是静态代理模式的类图,当在代码阶段规定这个代理关系是,ProxySubject类通过编译器生成了.class字节码文件,当系统运行之前,这个.class文件就已经存在了。动态代理模式的结构和上面的静态代理模式的接口狗稍微有所不同,它引入了一个InvocationHandler接口和Proxy类。在静态代理模式中,代理类ProxySubject中的方法,都指定地调用了特定ReadSubject对应的方法;动态代理工作的基本模式就是讲自己方法功能的实现交给InvocationHandler角色,外界对Proxy角色中每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用了RealSubject的方法,如下所示:
动态代理.png
代码如下: Subject.java
public interface Subject {
String operation();
}
RealSubject.java
public class RealSubject implements Subject{
@Override
public String operation() {
return "operation by subject";
}
}
ProxySubject.java
public class ProxySubject implements InvocationHandler{
protected Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//do something before
return method.invoke(subject, args);
}
}
测试代码
Subject subject = new RealSubject();
ProxySubject proxy = new ProxySubject(subject);
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), proxy);
sub.operation();
以上就是动态代理模式的最简单实现代码,JDK 通过使用 java.lang.reflect.Proxy 包来支持动态代理
那么通过Proxy类的newProxyInstance方法动态生成的类是什么样子?JDK为我们提供了一个方法ProxyGenerator.enerateProxyClass(String proxyName,class[] interfaces) 来产生动态代理类的字节码,这个类位于sun.misc包中,是属于特殊的jar包,于是问题又来了,androdi studio创建的android工程是没有办法找到ProxxyGenerator这个类的,这个类在jre下,最后废了半天的力气,终于使用下面这段代码就可以将生成的类导出在制定路径下:
public static void generateClassFile(Class clazz,String proxyName)
{
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = "D:\\"; // 这里写死路径为 D 盘,可以根据实际需要去修改
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths+proxyName+".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
调用方式如下
generateClassFile(ProxySubject.class, "ProxySubject");
最后就会在 D 盘(如果没有修改路径)的根目录下面生成一个 ProxySubject.class 的文件,使用 jd-gui 就可以打开该文件:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class ProxySubject
extends Proxy
implements Subject
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public ProxySubject(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String operation()
{
try
{
return (String)this.h.invoke(this, m3, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("Subject").getMethod("operation", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以观察到这个生成的类继承自java.lang.reflect.Proxy,实现了Subject接口,我们再看看生成动态类的代码:
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), proxy);
可见这个动态生成的类实现了subject.getClass().getInterfaces()中的所有接口,并且还有一点是类中所有方法都是final,而且该类也是final,所以该类不可继承,最后就是所有方法都会调用到 InvocationHandler对象的h的invoke()方法,这就是为什么最后调用到ProxySubject类的invoke()方法了,画一下他们的简单类图如下:
类图.png
从这个类图可以很清楚的看明白,动态生辰的类ProxySubject(同名,所以后面加上了Dynamic)持有了实现InvocationHandler接口的ProxySubject类的对象h,然后调用代理对象的operation方法时,就会调用到对象h的invoke方法中,invoke方法中根据operation方法时,就会调用到对象的h的invoke方法中,invoke方法中根据method的名字来区分到底是什么方法,然后通过methode.invoke()方法来调用具体对象的对应方法。
有点:
缺点: