代理模式
代理模式:为另一个对象提供一个替身或者占位符以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制某个对象的访问,被代理的对象可以是远程的对象或需要安全控制的对象。我觉得,在C语言中,指针就是一个代理对象,真正存储数据的是内存中的另外一块区域。Java提供了API可以生成动态代理,所以把标准的代理模式称为静态代理模式。在静态代理模式中有三类角色,理解了这三类角色也就理解了代理模式:
a.抽象对象:定义了真实角色和抽象角色的公共接口(可以是类,也可以是接口)Subject;
b.代理角色:代理角色内部包含有对真实角色的引用,通过这个引用去执行真实角色想要完成的任务;除此之外,代理角色可以完成其他的一些功能;
c.真实角色:实际要完成任务的角色,是我们始终要引用的对象。
代理角色和真实角色均实现了(或继承了)抽象角色。
写一段代码来举例:
//抽象角色,定义公共部分
interface Subject {
public void request();
}
//真实角色
class RealSubject implements Subject {
public void request() {
do something;
}
}
//抽象角色,内部保存真实角色的引用
class ProxySubject implements Subject {
private RealSubject sub;
public ProxySubject(RealSubject obj) {
this.sub = obj;//获得真实角色的实例引用
}
public void request() {
sub.request();//通过真实角色的引用去执行最终要完成的任务
}
}
public static void main(String[] args) {
Subject proxy = new ProxySubject();
proxy.request();
}
代理模式有很多中变形,这个很值得研究一番,不过今天我的重点是Java中的动态代理。
动态代理
上面的代理模式存在一个问题,那就是需要为不同的接口单独提供了一个代理类。现在有这样一个问题,系统中有10个类的方法在调用之前,需要进行安全验证,这10个类分别实现了不同的接口。这种情况恰好是代理模式发挥作用的地方,在真正的方法调用之前,现在代理类里面进行安全验证,如果通过了就去调用真正的方法,否则返回。这样既达到了安全验证的目的,又不违反开闭原则。可是,安全验证都是一样的,难道我们真的要去实现十个代理类,然后去做同样的安全验证吗?重复代码就意味着坏的气息啊。Java的动态代理就可以帮助我们搞定这件事情。动态代理,顾名思义就是动态的生成代理类,去代理服务。在看Java的动态代理之前,还是先分析一下如果要实现动态代理,需要哪些条件:
(1)在代理模式中,客户端实际上是在跟代理角色打交道,它以为它操作的是真实对象,但其实是代理对象,但是这种代理对客户端是透明的,客户端其实是面向接口编程,它只知有抽象接口,而不知具体的实现类。所以我们要定义抽象接口,真实角色和代理角色全部的都实现抽象接口,这样就实现了对客户端的透明代理。
(2)动态代理也只是代理,代理无非就是在真正的方法执行之前或者之后添加一些逻辑,但是主要的业务逻辑还需要真实角色去实现,所以必须要有真实角色。这个和静态代理模式是一样。我们还是需要按照业务逻辑去定义RealSubject。
(3)动态代理同样需要代理类,它和静态代理的区别是:静态的代理类是由程序员生成的,而动态代理类是动态生成的。但是,动态代理类的性质和普通的代理类是一样的,它都需要:a.实现抽象角色接口,由于它是动态生成的,所以不知道要实现哪些接口,所以它应该实现真实角色实现的所有接口;b.保存角色的引用;
前面两个条件跟静态代理模式是一样的,我们不用关心,我们需要弄明白的问题是:如果给动态代理类实现所需的接口以及它是怎样代理方法调用的?
Proxy来生成动态代理类
为了实现动态代理,Java提供了Proxy类和InvocationHandler接口,Proxy类用于生成动态代理类,InvocationHandler接口是代理真正发生的地方,它只有一个方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable,代理类所有的方法调用都会转到invoke()方法里,后面我们在说这个方法,先看Proxy类是如何生成动态代理类的。Proxy提供了如下的方法:
//返回动态代理类proxy关联的InvocationHandler
static InvocationHandler getInvocationHandler(Object proxy)
//返回由loader加载,实现了interfaces接口的代理类
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//判断cl是否是代理类
static boolean isProxyClass(Class<?> cl)
//返回由loader加载,实现了interfaces接口的动态类的实例,其关联的InvocationHandler是h
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
可以看出,Proxy提供了的4个方法全是static,它其实应嘎叫做ProxyFactory。关于以上方法具体的内容可以看JavaDoc,不过为了看清楚动态代理的实现,我决定研究一下Proxy的源码。处于篇幅的考虑,只是节选了部分源码:
class Proxy implements java.io.Serializable {
/*所有生成的代理类的名称是:$Proxy紧接一个数字,这个数字就是nextUniqueNumber++*/
private static long nextUniqueNumber = 0;
private final static String proxyClassNamePrefix = "$Proxy";
/**动态代理类构造方法的参数 */
private final static Class[] constructorParams = { InvocationHandler.class };
/*映射每个ClassLoader所生成的代理类*/
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
= new WeakHashMap<>();
/**标记一个动态代理类正在生成 */
private static Object pendingGenerationMarker = new Object();
/** next number to use for generation of unique proxy class names */
private static Object nextUniqueNumberLock = new Object();
/** 生成的代理类,在查询一个类是否是代理类时就是通过判断是否在proxyClasses中 */
private static Map<Class<?>, Void> proxyClasses =
Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
/*关联的InvocationHandler*/
protected InvocationHandler h;
/*留给子类扩展的构造方法*/
protected Proxy(InvocationHandler h) {
doNewInstanceCheck();
this.h = h;
}
/*生成代理类,这个代理类由loader加载,实现了interfaces中所有的接口,主要逻辑由getProxyClass0()实现*/
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}
return getProxyClass0(loader, interfaces);
}
/**
* 真正生成代理类的方法:
* 1.首先检查需要继承的接口是否大于65535,这是Java中能实现接口的上限
* 2.遍历interfaces(需要实现的接口),获得每个接口的名称interfaceNames,如果:
* 使用反射加载interfaceNames所对应的Class对象,如果与接口不一致,抛异常
* 如果当前遍历的Class对象不是接口,抛异常
* 如果其中有重复的接口,抛异常(interfaceSet就是用来保存已经遍历的接口,通过它可以判断是否包含当前接口)
* 3.使用loader去loaderToCache中获得有该类加载器生成的所有代理类的map,如果没有,则创造一个空的Map放进去
* 4.将所有需要实现的接口转换成List,去第3步中的map中查找:
* 如果已经生成了由loader加载的且实现了interfaces所有接口的代理类,直接把该代理类返回
* 如果正在生成有loader加载且实现了interface接口的代理类,等待,直到代理类生成//point1
* 如果还没有生成符合条件的代理类,执行第5吧
* 注意上面是放在一个while(true)循环中,所有point1最终都会从这里返回
* 5.将该类加入包里,如果interfaces中由non-public的接口,则该代理类就就放在该non-public所在的包中,
* 所以interfaces中所有的non-public接口应该在同一包中,否则就无法生成代理类了;
* 如果全部都是public接口,则代理类默认放在com.sun.proxy包里
* 6.代理类的名称是:$Proxy加序号
* 7.调用本地方法生成代理类的字节码,同时将生成的代理类放入proxyClasses,方便isProxy()方法调用
* 8.将生成的代理类放入第3步的map中,以便point1可以顺利返回,同时也把该代理类保存起来
* @param loader
* @param interfaces
* @return
*/
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
//检查需要继承的接口是否大于65535,这是Java中能实现接口的上限
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//需要生成的代理类
Class<?> proxyClass = null;
/* 所有需要实现的接口名称的数组 */
String[] interfaceNames = new String[interfaces.length];
// 用于检查是否有重复的接口
Set<Class<?>> interfaceSet = new HashSet<>();
//遍历每个接口,对应注释中的第2步
for (int i = 0; i < interfaces.length; i++) {
//判断传递进来的接口与通过反射生成的接口是否相同
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
/*判断传递进来的是否是进口*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*判断是否有重复的接口*/
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
/*将要实现的接口组装成List,到时候作为key去map中查找,判断该动态代理类是否已经生成*/
List<String> key = Arrays.asList(interfaceNames);
/*获得由loader类加载器生成所有代理类的map*/
Map<List<String>, Object> cache;
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
/**
* 下面的代码是在while(true)循环中:
* (1)如果已经生成了复合条件的动态代理类,直接把该类返回,循环结束
* (2)如果需要生成的动态代理类正在创建中,等待,直到创建完成,则会跳到(1),循环会结束
* (3)否则,去创建该动态代理类,并标记正在创建中,创建完成后,也会跳到(1)
*/
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
if (proxyClass != null) {
return proxyClass;
} else if (value == pendingGenerationMarker) {
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
//代理类的包
String proxyPkg = null;
/*所有要实现的接口有non-public的,则该代理类就和这个non-public接口在同一个包里,否而就在com.sun.proxy*/
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].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) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
{
/*获得一个序号,加到$Proxy后面的序号*/
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*生成代理类*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// 加到proxyClasses中,isProxy()方法就是靠它判断
proxyClasses.put(proxyClass, null);
} finally {
/*将生成的代理类加入缓存中*/
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
/**
* 生成代理类的实例
* 1.首先调用getProxyClass0()获得代理类的Class对象
* 2.获取动态东来类的构造方法的Constructor对象
* 3.调用Constructor.newInstance()
* 它需要InvocationHandler参数
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//获取由loader家在,且实现了interfaces接口的动态代理类
Class<?> cl = getProxyClass0(loader, interfaces);
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
return newInstance(cons, ih);
}
//创建代理类的实例,代理对象,调用代理类的构造方法
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
return cons.newInstance(new Object[] {h} );
}
/*判断cl是否是代理类,如果在proxyClasses中就是,否则就不是*/
public static boolean isProxyClass(Class<?> cl) {
if (cl == null) {
throw new NullPointerException();
}
return proxyClasses.containsKey(cl);
}
/*获得与proxy关联的InvocationHandler,就是获得proxy.h,然后返回*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
final Proxy p = (Proxy) proxy;
final InvocationHandler ih = p.h;
return ih;
}
}
Proxy在生成代理类的时候主要的工作就是让代理类实现所需的接口,生成字节码的工作是ProxyGenerator做的。在生成动态代理类时,我们需要传递给它类加载器,需要实现的接口,以及与之关联的InvocationHandler对象,InvocationHandler对象要做的就是我们希望实现的代理逻辑,比如,我们希望在每个方法调用之前做安全检查,那就在InvocationHandler的invoke()方法里调用安全检查的逻辑,invoke()是代理发生的地方。我们看一下invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
其中proxy是代理类,不用去管它。method是要真实对象要执行的方法,也就是我们要代理的方法,args是method的参数,所以如果代理需要做安全检查的话,可以这么写:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(安全检查通过)
method.invoke(obj,args);
else
return null;
}
例子
说了这么多,我们还是实现一个动态代理的例子,逻辑是这样的:有三个模块,分别是添加,删除,修改,这些代码已经完成;在做实际的工作之前,需要进行安全验证,如安全验证通过,就去调用相应的方法,否则本次操作失败。首先定义各个接口以及实现://添加模块
interface Add {
public void add(String s);
}
class AddImp implements Add {
public void add(String s) {
System.out.println(s + "已经被添加进系统");
}
}
//删除模块
interface Delete {
public void delete(String s);
}
class DeleteImp implements Delete {
public void delete(String s) {
System.out.println(s + "已经被删除");
}
}
//修改模块
interface Update {
public void update(String s);
}
class UpdateImp implements Update {
public void update(String s) {
System.out.println(s + "已经被修改");
}
}
安全检查模块
/**
* 安全检查模块,随机的返回检查通过或者不通过
*/
class SafeCheck {
static Random rand = new Random(25);
public static boolean check(Object obj) {
if(rand.nextInt(20) > 10)
return false;
return true;
}
}
生成动态代理,并代理执行:
/**
* 可以把它理解为代理角色,内部保存真实对象的引用
* 在调用真实对象的方法之前或之后执行相应的控制逻辑
* 虽然真正的动态代理类是由Proxy生成的,但是代理的逻辑却是在这里实现的
*/
class DynamicProxy implements InvocationHandler {
//真实对象,只能是Object
private Object originalObj;
/**
* 为传递进来的对象生成代理类,并实现originalObj的接口
*/
public Object bind(Object originalObj) {
this.originalObj = originalObj;
//originalObj对应的Class对象
Class<?> clazz = originalObj.getClass();
//自身现在就是处理器,所以把this传递给代理类
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
/**
* 方法的返回值是method的返回值,如果没有则返回null
* 代理真正发生的地方,可以在这里添加控制逻辑
* 注意,proxy是代理类,method是在真实对象上执行的,也就是originalObj
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果通过了安全验证,就去执行真正的逻辑
if(SafeCheck.check(originalObj))
return method.invoke(originalObj, args);
else
System.out.println(originalObj + "不符合安全要求!");
//否则就什么也不执行
return null;
}
}
测试代码:
public class Client {
public static void main(String[] args) {
String name = "cxy";
DynamicProxy handler = new DynamicProxy();
//真实对象
Add add = new AddImp();
Update update = new UpdateImp();
Delete delete = new DeleteImp();
//生成Add的代理类
Add pa = (Add) handler.bind(add);
//代理执行
pa.add(name);
//生成update的代理类
Update pu = (Update)handler.bind(update);
pu.update(name);
//生成Delete的代理类
Delete pd = (Delete)handler.bind(delete);
pd.delete(name);
}
}
输出结果:
----正在给com.understanding.loaderandengine.AddImp@f0a3e8做安全检查-----
cxy已经被添加进系统
----正在给com.understanding.loaderandengine.UpdateImp@a22e0c做安全检查-----
com.understanding.loaderandengine.UpdateImp@a22e0c不符合安全要求!
----正在给com.understanding.loaderandengine.DeleteImp@1b56bda做安全检查-----
com.understanding.loaderandengine.DeleteImp@1b56bda不符合安全要求!
我们可以看到,虽然系统中有三个模块,而且各自实现不同的接口,只要他们需要代理的逻辑是相同的,那么只需要给出一个处理程序,就可以动态代理全部的模块了,这是动态代理的强大之处。这个例子已经有点AOP的味道了,安全检查以不改变侵入原来模块的方式做到了为每个模块服务,Spring中的AOP有一大部分是通过动态代理实现的。
动态代理的执行前面已经介绍了动态代理类是如何生成的,也演示了动态代理的例子,其实我们还想看看动态代理类是如何代理服务的,这就需要看动态代理类的代码了,可以通过下面这个工具类来生成动态代理类:
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
import sun.misc.ProxyGenerator;
public class ProxyUtils {
/*
* 将根据类信息 动态生成的二进制字节码保存到硬盘中,
* 默认的是clazz目录下
* params :clazz 需要生成动态代理类的类
* proxyName : 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz,String proxyName)
{
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
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();
}
}
}
}
或者在main()中加入一句来生成代理类:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
通过反编译,得到代理类的源码,有了源码那么一切都暴露在我们面前了,先看看为AddImp生成的代理类:
/**
* 动态代理类,它继承了Proxy类,同时实现了Add接口
*/
public final class AddProxy extends Proxy
implements Add
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
//接收一个InvocationHandler做参数,它是代理真正发生的地方
public AddProxy(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
/**
* 实现Add接口而生成的add方法
*/
public final void add(String paramString)
throws
{
try
{
//它其实就是去执行InvocationHandler的invoke()方法了
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
//通过反射的反射获得add对应的Method对象,在InvocationHandler的invoke()方法中,当代理逻辑完成后,就是调用m3去执行真正的业务逻辑
m3 = Class.forName("com.understanding.loaderandengine.Add").getMethod("add", new Class[] { Class.forName("java.lang.String") });
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
以上代码删除了为继承Object而生成的hashCode(),toString(),equals()方法,接着再看看Delete代理类的源码,节选:
//为实现delete()代理而实现的方法
public final void delete(String paramString)
throws
{
try
{
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//通过反射获得delte()的Method对象
m3 = Class.forName("com.understanding.loaderandengine.Delete").getMethod("delete", new Class[] { Class.forName("java.lang.String") });
Update的代码就不贴了,从上面的代码中,我们可以发现动态代理的执行过程:
(1)动态代理类需要保存一个InvocationHandler的引用;
(2)动态代理类实现被代理类实现的全部接口,并获得相应方法的Method对象,生成相应的方法
(3)在执行代理的时候,动态代理类其实转到InvocationHandler.invoke()方法中去了,这也是为什么我说InvocationHandler是代理角色的原因。
到此,我们已经知道了动态代理类是如何生成的,动态代理是如何实现的,并且举了一个小例子,这个小例子很有AOP的思想。但是Java中动态代理已经很完美了吗?如果回过来头来去看Proxy.getProxyclass)()方法,我们发现Java的动态代理是根据被代理类所实现的接口来代理的,可以说Java的动态代理是“面向接口的代理”,如果一个类没有实现接口,那么就无法生成动态代理类,这是它的缺陷,但是不得不说,Java的动态代理还是很强大的。
转载请注明:喻红叶《Java与模式-代理模式》