透过源码学习设计模式2—Spring ProxyFactory和代理模式
静态代理与动态代理
代理就像我们生活中的房产中介,你不直接与房主,银行接触,而是通过中介与他们沟通联系。 代理的结构如图所示:
RealSubject 和 Proxy都实现了相同的接口Subject,Proxy持有RealSubject的引用,但Client 调用 request方法时,Proxy将请求转发给RealSubject,其中可以添加各种访问控制。
代理分为静态代理和动态代理,静态代理有一个弊端,需要为每一个目标类创建一个代理类,如果需要代理的对象很多的话,就得编写相应的代理类,在程序规模稍大时就无法胜任了。另外,代理类和被代理类实现了相同的接口,导致代码的重复,如果接口增加一个方法,那么除了被代理类需要实现这个方法外,代理类也要实现这个方法,增加了代码维护的难度。于是我们需要动态代理,它在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。这样我们就开启自动化之门,不需要经历手工定义一个代理类,实例化代理对象这样繁杂的流程。jdk已经实现了动态代理,它主要用了 java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
我们先实现java.lang.reflect.InvocationHandler,添加需要的横切逻辑
1. public class AccessControl implements InvocationHandler {2. @Override3. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {4. //调用前的处理5. System.out.println("调用前验证");6. Object obj = method.invoke(proxy, args);7. //调用后的处理8. System.out.println("调用后处理");9. return obj;10. }11. }
然后通过Proxy为不同的类型生成相应的代理对象。
1. Proxy.newProxyInstance(targertClass1.getClassLoader(), targertClass1.getInterfaces(),accessControl);2. Proxy.newProxyInstance(targertClass2.getClassLoader(), targertClass2.getInterfaces(),accessControl);
通过Proxy的newProxyInstance方法来创建代理对象:
第一个参数是一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
第二个参数是一个Interface对象的数组,为代理对象提供的接口是真实对象所实现的接口,表示代理的是该真实对象,这样就能调用这组接口中的方法了。
第三个参数是一个InvocationHandler对象,将这个代理对象关联到了 InvocationHandler 这个对象上。 不管targetClass有多少类型,都可以通过Proxy生成具有相同访问控制accesControl的代理对象。
从上面示例可知jdk动态代理需要被代理类实现接口(Interface),对于没有实现任何接口的目标对象,我们就要另找方法了。如在Spring Aop中,默认情况下,当发现目标对象没有实现任何接口时,会使用CGLIB,为目标对象动态生成代理对象,其实质就是对目标对象进行继承,生成子类,子类覆盖父类的方法,在其中加入额外的访问控制,不过如果类中的方法声明为final的话,就不能对它进行扩展。
源码深入
Spring ProxyFactory
看下面这幅图:
来源:http://images2015.cnblogs.com/blog/639520/201611/639520-20161129113959240-514594219.png
ProxyFactory集AopProxy和AdvisedSupport于一身,所以,可以通过ProxyFactory设置生成代理对象所需要的相关信息(AdvisedSupport的职责),也可以通过ProxyFactory取得最终生成的代理对象(AopProxy的职责)。
为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.spring- framework.aop.framework.ProxyCreatorSupport中,它自身就继承了AdvisedSupport,所以,生成代理对象的必要信息从其自身就可以搞到。为了简化子类生成不同类型AopProxy的工作,ProxyCreatorSupport内部持有一个AopProxyFactory实例,默认采用的是DefaultAopProxyFactory(也可以通过构造方法或者setter方法设置其他实现,如果有的话)。关于DefaultAopProxyFactory创建代理代码如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return CglibProxyFactory.createCglibProxy(config); } else { return new JdkDynamicAopProxy(config); } }
如果isOptimize()返回true,或者proxyTargetClass属性为true,或者目标对象没有接口实现,就采用cglib动态代理,否则就用jdk动态代理。再看看JdkDynamicAopProxy中的getProxy
1. public Object getProxy(ClassLoader classLoader) {2. if (logger.isDebugEnabled()) {3. logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());4. }5. Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);6. findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);7. return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);8. }
请看最后一行代码,就是使用的jdk动态代理。 Spring aop 仅作用于方法,如果你想对构造方法或字段作拦截处理,就要引入AspectJ,它支持在编译期间织入横切逻辑,提高运行期间的性能,但在易用性和灵活性上不如Spring aop。值得注意的是,Spring中@AspectJ注解区别的切面也是基于Spring aop 的代理机制实现的,不要被这个名称混淆了。
Proxy.newProxyInstance内部机制
/** *返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理handler。 */ @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); }
/* *1、查找或生成指定的代理类。
*/ Class<?> cl = getProxyClass0(loader, intfs);
/* * 2、获取构造器 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); }
final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //返回代理对象 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
/** * 生成代理类。在调用此方法之前,必须调用 checkProxyAccess方法来执行权限检查。 */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); }
// 如果给定加载器定义的实现给定接口的代理类存在,则只返回缓存的副本;否则,它将通过ProxyClassFactory创建代理类 return proxyClassCache.get(loader, interfaces); }
get():
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;
while (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value != null) { return value; } } // else no supplier in cache // or a supplier that returned null (could be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); }
if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory supplier = factory; } // else retry with winning supplier } else { if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory supplier = factory; } else { // retry with current supplier supplier = valuesMap.get(subKey); } } } }
valueFactory.apply调用
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
看apply方法内部:
/** * 一个工厂方法,根据类加载器和接口生成、定义和返回代理类。 */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 所有代理类名的前缀 private static final String proxyClassNamePrefix = "$Proxy";
// 下一个用于生成唯一代理类名的数字 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"); } /* 验证类对象是否实际表示接口。即如果通过接口名获得的类不是一个接口的,报异常 */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * 验证接口不重复 */ 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;
/* *保证所有非公共接口在同一个包内 */ 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) { // 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;
/* * 产生特定的代理类 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString()); } } }
- 如何理解云计算?很简单,就像吃货想吃披萨了……
- .NET 2.0 中使用Active Directory 应用程序模式 (ADAM)
- struts2: 通过流输出实现exce导出
- Google的数据交换协议:GData (Google Data APIs Protocol)
- C# 内部类
- 四字母.com域名均以五位数结拍
- mybatis 3.2.7 与 spring mvc 3.x、logback整合
- spring 3.2.x + struts2 + mybatis 3.x + logback 整合配置
- struts2使用Convention Plugin在weblogic上以war包部署时,找不到Action的解决办法
- 使用xfce4桌面系统
- 号外!号外!Python纳入高考内容了!人工智能时代就要来临了!
- 高颜值!域名5h.net和jb.cc纷纷易主
- 认识ASP.NET 5项目结构和项目文件xproj
- weblogic下部署应用时slf4j与logbak冲突的解决办法
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 构建的抽象
- 低成本个人建站系列二 —— 使用 Hexo+GitHub 搭建个人免费博客
- 42图揭秘,「后端技术学些啥」
- R-ggTimeSeries | ggplot2: 热力日历图
- R-wordcloud: 词云图
- 直播APP源码是如何实现音视频同步的
- 动态规划算法练习(5)--medium
- phpstudy漏洞分析原因到修复
- 哈?命令注入外带数据的姿势还可以这么骚?
- 记一次曲折的RCE挖掘
- pytest文档49-命令行参数--tb的使用
- pytest文档50-命令行参数--durations统计用例运行时间
- pytest文档51-内置fixture之cache使用
- pytest文档53-命令行实时输出错误信息(pytest-instafail)
- pytest文档52-命令行参数--setup-show查看fixture的执行过程