浅析代理模式
来源:互联网 发布:大司马淘宝 编辑:程序博客网 时间:2024/06/16 10:08
概述
在面向对象系统中,有些对象由于某些原因(比如对象的创建开销很大,或者某些操作需要安全控制),直接访问会给使用者或者系统结构带来很多麻烦,我们在访问此对象时加上一个对此对象的访问层,这种方式被我们称做代理模式或者委托模式;而根据程序运行前代理类是否已经存在,我们又将代理分为静态代理和动态代理。
角色划分
- Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,它负责定义对外暴露的接口信息。
- RealSubject具体主题角色:也叫做被委托角色或者被代理角色,不折不扣的anonymous。
- Proxy代理主题角色:也叫做委托类或者代理类,它持有真实角色的引用,把所有抽象主题类定义的方法委托给真实主题角色。
模式类图
静态代理
静态代理相对其他模式还是比较容易理解的,这里给出一个简单的demo帮助理解该模式概念。
抽象主题
public interface Subject { void request();}
真实主题
class RealSubject implements Subject { private final static String TAG = RealSubject.class.getSimpleName(); @Override public void request() { Log.d(TAG, "Real processing"); }}
主题代理
public class ProxySubject implements Subject { private final static String TAG = ProxySubject.class.getSimpleName(); private Subject realSubject; public ProxySubject() { realSubject = new RealSubject(); } @Override public void request() { Log.d(TAG, "other operation"); realSubject.request(); Log.d(TAG, "other operation"); }}
场景类
public class MainActivity extends AppCompatActivity { Button requestBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestBtn = (Button) findViewById(R.id.request); requestBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { request(); } }); } private void request(){ ProxySubject proxySubject = new ProxySubject(); proxySubject.request(); }}
细想一下,每个代理方法中都要重复真实主题代码,如果要想为多个类进行代理,则需要建立多个代理类,维护成本增加;倘若事先并不知道真实角色呢?这些问题可以通过动态代理解决。
动态代理
public class CCInvocationHandler implements InvocationHandler { private Object target; public CCInvocationHandler() {} public CCInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d(TAG, "other operation"); Object obj = method.invoke(target, args); Log.d(TAG, "other operation"); return obj; }}
- target 委托类对象。
- InvocationHandler 该接口的实现负责连接代理类和委托类。
- proxy 代理类对象。
- method 代理对象被调用的函数。
- args 代理对象被调用的函数的参数。
- invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。
public class MainActivity extends AppCompatActivity { private final static String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CCInvocationHandler ccInvocationHandler = new CCInvocationHandler(new RealSubject()); Subject operate = (Subject) (Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, ccInvocationHandler)); operate.request(); }}
- loader 当前类的类加载器。
- interfaces 委托类所实现的接口。
- ccInvocationHandler InvocationHandler实现类对象,连接代理类和委托类的中间类对象。
我想你应该和我一样对此很好奇,动态代理机制是怎么运作的,那就一探究竟吧!
从Proxy.newProxyInstance()切入
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler) throws IllegalArgumentException { if (invocationHandler == null) { throw new NullPointerException("invocationHandler == null"); } Exception cause; try { return getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class) .newInstance(invocationHandler); } catch (NoSuchMethodException e) { cause = e; } catch (IllegalAccessException e) { cause = e; } catch (InstantiationException e) { cause = e; } catch (InvocationTargetException e) { cause = e; } AssertionError error = new AssertionError(); error.initCause(cause); throw error; }
newProxyInstance代码段还是比较直观的,首先对invocationHandler做非空判断,之后把loader和interfaces传入getProxyClass()后获得代理类,然后拿到代理类的构造函数,最后将invocationHandler作为newInstance参数传入生成代理类对象。关于如何得到代理类的呢?继续跟进getProxyClass()。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException { if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } if (interfaces == null) { throw new NullPointerException("interfaces == null"); } // Make a copy of the list early on because we're using the list as a // cache key and we don't want it changing under us. final List<Class<?>> interfaceList = new ArrayList<Class<?>>(interfaces.length); Collections.addAll(interfaceList, interfaces); // We use a HashSet *only* for detecting duplicates and null entries. We // can't use it as our cache key because we need to preserve the order in // which these interfaces were specified. (Different orders should define // different proxies.) final Set<Class<?>> interfaceSet = new HashSet<Class<?>>(interfaceList); if (interfaceSet.contains(null)) { throw new NullPointerException("interface list contains null: " + interfaceList); } if (interfaceSet.size() != interfaces.length) { throw new IllegalArgumentException("duplicate interface in list: " + interfaceList); } synchronized (loader.proxyCache) { Class<?> proxy = loader.proxyCache.get(interfaceList); if (proxy != null) { return proxy; } } String commonPackageName = null; for (Class<?> c : interfaces) { if (!c.isInterface()) { throw new IllegalArgumentException(c + " is not an interface"); } if (!isVisibleToClassLoader(loader, c)) { throw new IllegalArgumentException(c + " is not visible from class loader"); } if (!Modifier.isPublic(c.getModifiers())) { String packageName = c.getPackageName$(); if (packageName == null) { packageName = ""; } if (commonPackageName != null && !commonPackageName.equals(packageName)) { throw new IllegalArgumentException( "non-public interfaces must be in the same package"); } commonPackageName = packageName; } } List<Method> methods = getMethods(interfaces); Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); validateReturnTypes(methods); List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods); Method[] methodsArray = methods.toArray(new Method[methods.size()]); Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]); String baseName = commonPackageName != null && !commonPackageName.isEmpty() ? commonPackageName + ".$Proxy" : "$Proxy"; Class<?> result; synchronized (loader.proxyCache) { result = loader.proxyCache.get(interfaceList); if (result == null) { String name = baseName + nextClassNameIndex++; result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray); loader.proxyCache.put(interfaceList, result); } } return result; }
代码段稍微有些长,那就一点点分析吧……
if (loader == null) { loader = ClassLoader.getSystemClassLoader(); }
如果传入的加载抽象主题的类加载器对象为null,就获取系统类加载器,关于类加载,我建议同学们有必要去了解一下,比如类的双亲委派机制,这些概念有助于理解动态加载apk文件。
synchronized (loader.proxyCache) { Class<?> proxy = loader.proxyCache.get(interfaceList); if (proxy != null) { return proxy; } }
尝试从缓存中去代理类Class对象,如果存在需要的代理类Class对象则直接返回,否则继续执行。继续往下分析…
String commonPackageName = null; for (Class<?> c : interfaces) { if (!c.isInterface()) { throw new IllegalArgumentException(c + " is not an interface"); } if (!isVisibleToClassLoader(loader, c)) { throw new IllegalArgumentException(c + " is not visible from class loader"); } if (!Modifier.isPublic(c.getModifiers())) { String packageName = c.getPackageName$(); if (packageName == null) { packageName = ""; } if (commonPackageName != null && !commonPackageName.equals(packageName)) { throw new IllegalArgumentException( "non-public interfaces must be in the same package"); } commonPackageName = packageName; } }
如果interfaces中存在非public的接口,则所有非public接口必须在同一包下面,后续生成的代理类也会在该包下面。
String baseName = commonPackageName != null && !commonPackageName.isEmpty() ? commonPackageName + ".$Proxy" : "$Proxy";
得到代理类的类名
Class<?> result; synchronized (loader.proxyCache) { result = loader.proxyCache.get(interfaceList); if (result == null) { String name = baseName + nextClassNameIndex++; result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray); loader.proxyCache.put(interfaceList, result); } }
generateProxy() native层实现,是JVM加载代理类并返回其Class对象,得到Class对象之后存入缓存。
- 获取RealSubject上的所有接口列表。
- 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX。
- 根据需要实现的接口信息,动态创建该Proxy class文件。
- 将字节码信息转换为对应的class对象。
- 创建InvocationHandler实例handler,用来处理Proxy所有方法调用。
- Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。
结语
- 静态代理模式的使用场景比较常见,比如android appcompat体系或者说context体系。
- 动态代理可以对代理类的函数做统一或特殊处理,比如所有函数执行前添加验证判断、对某个特殊函数进行特殊操作。
朋友的新书《Android源码设计模式解析与实战》已经出版,购买链接
- 浅析代理模式
- 浅析代理模式
- 代理模式浅析
- 静态代理模式浅析
- 浅析代理模式
- 设计模式之代理模式浅析
- 浅析设计模式之代理模式
- 浅析“代理模式”实现spring事务管理
- 浅析JAVA设计模式之代理模式(一)
- 浅析JAVA设计模式之代理模式(二)
- 浅析JAVA设计模式之代理模式(三)
- 浅析JAVA设计模式之代理模式(四)
- 浅析JAVA设计模式之代理模式(五)
- 浅析JAVA设计模式之代理模式(六)
- 浅析JAVA设计模式之代理模式(七)
- java 设计模式 —— 浅析代理模式
- 浅析正向代理、反向代理
- IOS代理浅析
- 如何在eclipse中查看源码?
- [2] 数组和指针的前世今生 - 数组篇
- 实验四 看电视
- Redis-util 转换函数
- 【MySQL】统一控制台-pma-PHP编码!解决中文乱码问题
- 浅析代理模式
- [有上下界的网络流]
- c++实验2-标准体重
- 联想昭阳E47A无线网卡指示灯不亮,搜不到无线信号,解决办法
- BZOJ 1856 SCOI 2010 字符串 卡特兰数
- 如何让eclipse恢复默认布局
- 大小端模式
- Genymotion提示To find out the cause of the problem,start the virtual device from VirtualBox
- lintcode:Permutations II