目录
0.背景介绍
NBA巨星,鹈鹕队的当家球星戴维斯通过自己的经纪人里奇-保罗宣布将不会与鹈鹕队续约,顿时引起轩然大波,外界内行对他的行为褒贬不一,为荣誉也好,金钱也罢,球星转会想要去到更大的舞台展现自己无可厚非。对于此类新闻我们不置可否,只是引以为例,其实对于大部分名人来说,不管是演艺界还是体育界,他们都有自己的经纪人,大部分的明星都会通过自己的经纪人来转述或者表达一些本人的想法,经纪人就相当于代理人的作用,代理自己的对象来做一些事情。
在软件设计中,为了解决一个问题或者控制对一个对象行为的访问,我们经常会为此类对象设计一个替身(代理),用以与客户端发生实际的交互,比如网络代理,我们为了解决上网的权限问题,会使用网络代理来访问Internet上面的一些信息,又比如远程代理,我们需要通过远程代理访问远程主机的对象,这些都是我们今天要介绍的代理模式的一种。
1.代理模式介绍
1.1代理模式定义
代理模式:为另一个对象提供一个替身或者占位符来控制对这个对象的访问(比如上面的例子中,明星的所有公共行为都是通过自己的经纪人来转述的,其他人无法代替)
1.2代理模式分类
以下分类引自Java与模式一书
远程代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是本机器长,也可以在另一台机器中。
虚拟代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
Copy-on-Write代理:虚拟代理的一种,把复制(克隆)拖延到只有在客户端需要时,才真正创建。
保护代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
防火墙代理:保护目标,不让愿意用户接近。
同步化代理:使几个用户能够同时使用一个对象而没有冲突。
智能引用代理:当一个对象被引用时,提供一些额外的操作,比如将此对象的调用次数记录下来等。
本文对上述几种代理模式不会一一详细的介绍,只会介绍所有代理的共同点。
1.3代理模式UML结构图
Subject:抽象主题接口,定义了真实主题以及代理主题的公共接口。
ProxySubject:代理主题,实现了抽象主题接口,内部持有一个具体主题的引用,可以操作真实主题对象,与客户端直接交互,解耦了客户端与真实主题的交互。
RealSubject:真实主题,实现了抽象主题接口。
2.代理模式的Java实现
以下是用Java简单的实现代理模式的用例代码(jdk版本为Java10)
2.1用例UML结构图
Person相当于抽象Subject接口,Star是真实的RealSubject,Broker是代理类
2.2用例代码
抽象接口:Person
public interface Person {
void request();
}
Person实现类Star
public class Star implements Person {
@Override
public void request() {
System.out.println("大家好我想换一只球队了");
}
}
Person实现类Broker
public class Broker implements Person {
private Person person;
public Broker(Person person) {
this.person = person;
}
@Override
public void request() {
System.out.println("大家好,我是经纪人XXX,我的被代理人想说:");
this.person.request();
}
}
Client客户端类:
public class Client {
public static void main(String[] args) {
Person star=new Star();
Person broker=new Broker(star);
broker.request();
}
}
运行结果如下:
3.JDK对代理模式的支持
以上是用Java简单的实现类代理模式,其实在JDK中天然增加了对代理模式的支持,主要相关的类有两个,一个是Proxy,另一个是InvocationHandler,实现动态代理的主要步骤如下:
- 开发人员需要实现InvocationHandler接口来定义自己的InvocationHandler实现类。并且持有一个被代理类的引用。
- 通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法,传入真实主题的信息以及自定义实现的InvocationHandler,动态生成一个代理类。
- 通过动态生成的代理类来调用真实主题的目标方法。
3.1JDK动态代理用例实现
3.1.1动态代理UML结构图
其中,Proxy,InvocationHandler是JDK支持的动态代理工具,MyInvocationHandler是我们自己实现的InvocationHandler接口的自定义实现。
3.1.2动态代理用例代码
Person以及Star的代码见上一节
MyInvocationHandler代码
public class MyInnovationHandler implements InvocationHandler {
private Person person;
MyInnovationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("大家好,我是经纪人XXX,我的被代理人想说:");
return method.invoke(this.person, args);
}
}
Client代码
public class ProxyTest {
public static void main(String[] args) {
//创建一个真实主题
Person star = new Star();
//创建自定义InvocationHandler实现类
InvocationHandler invocationHandler = new MyInnovationHandler(star);
//通过Proxy创建动态代理类
Person person = (Person) Proxy.newProxyInstance(Star.class.getClassLoader(), Star.class.getInterfaces(), invocationHandler);
//调用主题方法
person.request();
}
}
运行结果如下:
可以看到,通过JDK对代理模式的支持,运行结果与上一节中结果是一致的。
3.2JDK动态代理原理解析
通过JDK自带的动态代理来实现代理模式非常简单,本节,我们来剖析以下JDK动态代理的原理,让大家知其然,更知其所以然
动态代理类的主要生成是在Proxy的newProxyInstance(...)方法生成的,下面从这个方法开始讲解下动态代理的原理。
方法源码如下(JDK源码版本为Java10)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
//方法入参有3个,分别是真实主题的类加载器,真实主题实现的接口,以及自定义实现的
//InvocationHandler接口
//判空操作
Objects.requireNonNull(h);
//获取SecurityManager,系统的安全校验,不知道是干啥的,可以忽略
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
//生成Constructor对象,与类的构造方法有关
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
//根据Constructor对象,以及caller和自定义的InvocationHandler 生成一个代理类
return newProxyInstance(caller, cons, h);
}
以上是newProxyInstance方法的源码,在主要代码上面写了注释,里面主要涉及的方法有两个,分别是getProxyConstructor()方法以及newProxyInstance()方法。
下面是getProxyConstructor方法的源码,改方法主要的作用是生成与主题相关的Constructor对象,下面是源码以及源码注释
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// 判断真实主题实现的抽象接口数量,1个走下面逻辑,大于1个走else接口
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
//callser不为空,做安全校验
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
//先走缓存,通过ProxyBuilder生成Constructor对象
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
下面看下ProxyBuilder类的build方法源码
Constructor<?> build() {
//获取代理类
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
//创建代理类的带参构造函数,传入参数是constructorParams
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}
以上代码片段主要做了以下几件事情
通过defineProxyClass(Module m, List<Class<?>> interfaces)方法动态生成一个代理类,入参主要有两个,一个是Module,一个是需要实现的接口,Module的主要作用是给代理类的包名的时候使用,interfaces是生成的代理类需要实现的接口。看下这个方法的源码
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
//验证需要实现的所有接口的属性是否是public,如果有一个不是public,则生成的代理类
//所在的包名跟此非public的接口的包名一致
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL; // non-public, final
//给pkg赋值,为非public的接口的包名
String pkg = intf.getPackageName();
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 所有的接口都是public,则包名取默认值为:com.sun.proxy
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
: PROXY_PACKAGE_PREFIX;
} else if (proxyPkg.isEmpty() && m.isNamed()) {
throw new IllegalArgumentException(
"Unnamed package cannot be added to " + m);
}
if (m.isNamed()) {
if (!m.getDescriptor().packages().contains(proxyPkg)) {
throw new InternalError(proxyPkg + " not exist in " + m.getName());
}
}
//为生成的代理类取名字的后缀数字,从0开始,
long num = nextUniqueNumber.getAndIncrement();
//代理类的名字为包名.$Proxy+后缀数字 比如 com.sun.proxy.$Proxy0
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
ClassLoader loader = getLoader(m);
trace(proxyName, m, loader, interfaces);
/*
* Generate the specified proxy class.
*/
//生成代理类的字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
//放入cache中
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
以上代码主要生成了动态代理类,主要有代理类的包名,类名,以及字节码文件,字节码文件,生成字节码的过程在此不详细 介绍了。
接下来就是调用代理类的construct方法生成构造器对象,传入参数是constructorParams(此参数是个常量 { InvocationHandler.class })
最后通过调用newProxyInstance方法,返回一个动态代理类的具体实例
下面看下生成的代理类的源码(反编译之后的代码)
import base.pattern.proxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void request() throws {
try {
//调用我们之前传入的MyInnovationHandlerde的invoke方法,方法名是m3,m3在下面
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("base.pattern.proxy.Person").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
上面是生成的动态代理类的反编译代码,我们需要注意几点
- 此动态代理类继承了Proxy类,实现了Person接口,因此也实现了request()方法。
- 在request方法中,我们调用了MyInnovationHandler的invoke()方法(在生成代理类的时候传入的),Method是m3。
- m3是接口Person的request方法的反射。
- 在MyInnovationHandler方法里面我们调用的是Star类的request()方法(看上面MyInnovationHandler的代码即可发现)
UML结构图如下:
3.3JDK动态代理总结
3.1以及3.2节是对JDK动态代理的解析,由于源码比较多,可能解析的没有那么透彻,下面来动态代理的优缺点
优点:实现简单,不需要自己定义代理类,可以动态的生成代理类
缺点:从用例可以看出,JDK动态代理仅仅针对的是接口,为什么?看下最后生成的动态代理类的源码,动态代理类继承了Proxy类,由于Java本身只支持单继承,所以只适用于接口,那么如果针对的不是接口,应该怎么做呢,此时应该使用Cglib来实现(本文暂不讲解Cglib了,将会另外文章讲解)
4.总结
本文主要讲解了代理模式(静态代理)以及JDK支持的动态代理模式的原理以及用例实现,讲解动态代理的时候源码较多,逻辑优点混乱,也可能自身理解的也没有特别透彻,会继续加深学习。