代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问
使用场景:当无法或者不想直接访问某个对象或访问某个对象存在困难时可以同一个代理对象来简介范根,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
静态代理
代理模式的通用模式代码
/**
* 抽象主题类
*/
public abstract class Subject {
/**
* 一个普通的业务方法
*/
public abstract visit();
}
/**
* 实现抽象主题的真实主题类
*/
public class RealSubject extends Subject {
@Override
public void visit() {
// RealSubject中visit的具体逻辑
System.out.println("Real subject");
}
}
/**
* 代理类
*/
public class ProxySubject extends Subject {
// 持有真实主题类的引用
private RealSubject mSubject;
public ProxySubject(RealSubject subject) {
this.mSubject = subject;
}
@Override
public void visit() {
// 通过真实主题引用的对象调用真实主题中的方法
mSubject.visit();
}
}
/**
* 客户类
*/
public class Client {
public static void main(String[] args) {
// 构造一个真实主题对象
RealSubject realSubject = new RealSubject();
// 通过真实主题对象构造一个代理对象
ProxySubject proxy = new ProxySubject(realSubject);
// 调用代理的相关方法
proxy.visit();
}
}
代理模式的角色介绍
Subject:
抽象主题类
该类的主要职责是声明真实主题与代理的共同接口方法,该类既可以是一个抽象类也可以是一个接口。
RealSubject:
真实主题类
该类也称为被委托类或被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法 而客户类则通过代理类间接地调用了真实主题中定义的方法。
ProxySubject:
代理类
该类也称为委托类或代理类,该类持有一个真实主题类的引用,在其所实现的接口方法中调用真实主题中相应的接口方法执行,以此起到代理的作用
Client:
客户类,即使用代理类的类型
这样看来静态代理的实现也比较简单,就是构造一个代理对象持有一个这是主题对象的引用,通过代理对象调用真实主题的方法。这样实现起来是比较容易,但是如果后面又有多个真实主题对象呢?要用静态代理模式实现的话,是不是要创建多个代理类呢?然后修改代码通过各自的代理类来调用各个真实主题的方法。这时我们就发现了静态代理模式的弊端,可维护性、可扩展性差,也就是违反了面向对象的六大原则之一的开闭原则(开放增加,关闭修改),这样一来就引出了动态代理。
动态代理
举个例子,比如你有一个女朋友要买鞋子,找某鞋子的品牌代理也就是代购,如果你又有一个女朋友要买包包,找某包包的品牌代理,如果你有多个女朋友,每个要买的东西都不一样,要通过不同渠道找不同的代购是不是很麻烦,可不可以找一个平台或中介,每次买东西,就找他们,由平台根据客户要买的东西,再去找具体商品的代购。例子可能不太恰当,但是个人认为已经足够解释动态代理的大概意思了,接下来是代码环节。
/**
* 抽象主题(买包包)接口类
*/
public interface IBuyBag {
// 买包包
void buyBag();
}
/**
* 真实主题类(老婆要买包包)
*/
public class Wife implements IBuyBag {
@Override
public void buyBag() {
System.out.println("给老娘买个包...");
}
}
/**
* 抽象主题(买鞋子)接口类
*/
public interface IBuyBag {
// 买鞋子
void buyShoes();
}
/**
* 真实主题类(女朋友要买鞋子)
*/
public class GirlFriend implements IBuyShoes {
@Override
public void buyShoes() {
System.out.println("给人家买双鞋子啦...");
}
}
上面分别是两个抽象主题类和真实主题类,如果是静态代理,要两个创建代理对象(类), 来分别代理实现,也就是说在我们的代码运行之前代理类的class
编译文件就已经存在;而动态代理则与静态代理相反,通过反射机制动态地生成代理者的对象,也就是在code
阶段压根就不需要知道代理谁,代理谁将会在执行阶段决定。
/**
* 动态代理类
*/
public class DynamicProxy implements InvocationHandler {
// 被代理的引用
Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用被代理对象的方法
Object result = method.invoke(obj, args);
return result;
}
}
如上述代码所述,我们声明了一个Object
的引用,该引用指向被代理类,而我们调用被代理类的具体方法在invoke
方法中执行。也就是说原来由代理类所做的工作现在由InvocationHandler
来处理,不需要再关心到底代理谁,下面是使用代理类的客户类。
public class Client {
public static void main(String[] args) {
// 创建wife对象
IBuyBag wife = new Wife();
// 构造一个动态代理
DynamicProxy proxy1 = new DynamicProxy(wife);
// 动态构造一个处理wife购买需求的代理者
IBuyBag wifeProxy = (IBuyBag) Proxy.newProxyInstance(wife.getClass().getClassLoader(),
new Class[]{IBuyBag.class}, proxy1);
// 代理者买包包
wifeProxy.buyBag();
// 创建girlfriend对象
IBuyShoes girlfriend = new GirlFriend();
// 构造一个动态代理
DynamicProxy proxy2 = new DynamicProxy(girlfriend);
// 动态构造一个处理girlfriend购买需求的代理者
IBuyShoes girlfriendProxy = (IBuyShoes) Proxy.newProxyInstance(girlfriend.getClass().getClassLoader(),
new Class[]{IBuyShoes.class}, proxy2);
// 代理者买鞋子
girlfriendProxy.buyShoes();
}
}
动态代理实现原理
通过上面的例子总结一下的话,就是通过反射机制运行时动态地生成代理者对象,实现动态代理接口InvocationHandler
,调用该接口需要重写其调用方法invoke
,来达到调用我们真实主题类的方法。JDK的动态代理具体是如何实现的呢?下面进入源码环节,首先我们应该知道java中类的大致生命周期。
代理对象具体是如何实例化的呢?首先通过debug看一下,代理对象到底是什么东西?如下图,可以发现两个代理对象的类名信息分别以
Proxy1开头的,但是有找不到相关信息。
接下来就进入源码查看环节了,先从Proxy的newProxyInstance方法开始,毕竟生成代理对象的从它开始的。
Sdk/sources/android-28/java/lang/reflect/Proxy.java
public class Proxy implements java.io.Serializable {
...
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
// 获取代理类的类类型即Class对象
Class<?> cl = getProxyClass0(loader, intfs);
// 获取构造
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
// 通过构造方法获取实例对象
return cons.newInstance(new Object[]{h});
...
}
...
}
为了便于理解,只保留了与本文相关的逻辑。通过getProxyClass0()
方法获取类类型即Class对象cl, 然后
通过构造cons来获取实例对象。接下来进入getProxyClass0()
方法:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
通过代码注释不难发现,对接口数量有限制,超过65535会抛异常。接下来会从proxyClassCache()
中获取,貌似还进行了缓存,当第一次执行,还没有实例的时候,是如何创建的呢?进入其ger
方法
Sdk/sources/android-28/java/lang/reflect/WeakCache.java
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
// 前面都是关于缓存的相关操作,核心代码就是这一句
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
...
如果缓存中没有,则会调用ProxyClassFactory(Proxy的内部类)的apply方法创建一个代理类。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
// 所有jdk代理类的前缀名
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
// 一个无锁的long
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 校验类加载器是否能通过接口名称加载该类
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* 校验该类是否接口类型
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 校验接口是否重复
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
// 代理类的包名
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* 对于非public类,代理类的包名和接口的包名相同
* 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.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
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包名
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 给生成的代理类起一个名字
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* 这个方法是真正生成字节码的地方
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 使用类加载器将代理类的字节码加载到JVM中
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} 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());
}
}
}
java动态代理是利用反射机制生成一个实现代理接口的匿名类,注意只能是接口,不能是类,代码中也进行了校验,如果不是接口,会抛异常。还会进行一些其他的检查。核心就是以下三处代码
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
...
//1
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
...
//2
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
...
//3
// 使用类加载器将代理类的字节码加载到JVM中
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
通过注释1处代码,给生成代理类起一个名字,就对应了上文debug时,代理对象的类名信息。注释2处,通过generateProxyClass()方法生成代理类的字节码bute数组proxyClassFile,再通过defineClass0方法生成代理类的class对象,即使用类加载器将代理类的字节码加载到jvm中。这是一个native方法,后续生成的具体细节就看不到了,但是我们可以通过将字节码byte数字反编译的方式,来看生成代理类的信息。由于该类没有开源(因为这个代理是sun公司的,从包名中就可以看出),可以通过反编译工具来看。
要想将JDK动态代理生成的class文件 Proxy1保存到本地,需要进行设置,网上搜到的办法是加入:System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); 要保证你的代码在运行的时候,该行代码执行了,就会在如下目录:工程目录\com\sun\proxy路径下生成class文件了
通过反编译工具查看如下:
package com.sun.proxy;
import com.example.designpatterns.chapter_18_ProxyPattern.IBuyBag;
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 IBuyBag {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(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 error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void buyBag() {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.example.designpatterns.chapter_18_ProxyPattern.IBuyBag").getMethod("buyBag", 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 noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
从代码中可以看到代理类$Proxy0继承了Proxy实现了接口。所以它可以实现被代理类的一切功能,this.h.invoke,这里的h是InvocationHandler,通过反射调用对应的方法m3. 至此动态代理的JDK实现就大致分析完了。
关于java动态代理有两种实现方式:JDK动态代理和cglib动态代理,作为搞Android的,貌似接触前者比较多,至于后者高java接触的多,本文不做分析,有兴趣的可以点击以下链接进行查看。