ProxyFacotroy源码解析
来源:互联网 发布:sql约束表达式大全 编辑:程序博客网 时间:2024/06/07 22:39
ProxyFacotroy源码解析
简述
ProxyFacotory是spring中生成动态代理的工厂方法,其中包含很丰富的方法,本文主要在梳理ProxyFactory的继承关系,并简述ProxyFactory的相关用法。(注意本文是基于3.2.5-RELASE版本的spring)
背景
我们在工作使用spring的过程中,有的时候,我们会遇到这样的需求,有一个方法,我们需要在这个方法的外围添加一些功能,比如打日志。但是直接把外围的代码添加到方法中,会破坏方法本身的语义,重写一个新的方法包含原有的方法,又会使得原有调用方法的地方修改为调用新的方法。为了解决这样的矛盾,我们就会借助使用动态代理的技术,用动态代理生产的对象,替换旧的对象,在动态代理对象调用的时,会触发观察者,调用观察者(建议Advice)的相关方法,通过这样的方式动态的注入代码,而不用修改原有的代码,其实spring AOP就是一套动态代理的方案。
spring的动态代理
在spring的动态代理中,主要包含三个成分
- 原始的对象
- 代理对象
- 对象的建议者(Advisor),每个建议者都会携带一个建议Advice
可以看出其实spring的动态代理使用了一个观察者模式,各个建议者Advice对应观察者模式中的观察者,而对象的本身,则是被建议者,Advised则是被观察者。代理对象,会在实际调用的时候,获得建议者的”建议”,然后再调用实际被建议者。既然ProxyFactory作为动态代理的工厂方法,而建议者作为被建议者的一部分,它的配置,自然可以通过ProxyFactory来管理。
创建一个简单的代理
在具体学习前,我们先来看一个生产spring代理的例子,如下
public static class Login{ public void login(Long userId,Long password){ System.out.println("userId is " + userId + " password is " + password); }}@Testpublic void simpleProxy(){ ProxyFactory factory = new ProxyFactory(); MethodInterceptor logInterceptor = new MethodInterceptor() { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("userId " + invocation.getArguments()[0] + " login ...."); return invocation.proceed(); } }; factory.setTarget(new Login()); factory.addAdvice(logInterceptor); Login login = (Login) factory.getProxy(); login.login(1L,2L);}
在上述的代码中,我们创建了一个代理工厂,然后设置了一个Login实例为被代理对象,然后添加一个方法的拦截器,或者说添加了一个建议,建议内容为在调用实际方法前,获取入参的userId信息。运行返回的结果为
userId 1 login ....userId is 1 password is 2
可以看到,拦截器或者说建议,确实在方法实际的调用之前进行了。一般来说,我们使用spring的动态代理ProxyFactory生成的代理的常见方案是
- 设置被代理的对象
- 添加建议者对被代理的对象做建议
- 生产代理对象被建议者(Advised),替换老的对象
被建议者,可以动态的添加建议者,这是Spring动态代理最迷人的地方,有了它我们可以自由的添加逻辑,而不用改变原有的代码。
总览
为了能更好的学习ProxyFactory的相关知识,我们先看一下ProxyFactory总的继承关系的图。
上图展示了ProxyFactory基础的继承关系,我们先来个快速了解。
ProxyFactory
作为一个生产AOP代理的工厂类来使用。这个类提供一个简单的方式用于获取和配置AOP的代理。
ProxyCreatorSupport
代理工厂的基础类,提供一个便利的方式去访问AopProxyFacotory(Aop代理工厂)的配置。
AdvisedSuport
AOP代理的配置管理的基础类,这不是代理类本身,但是这个类的子类,一般都是用于生产AOP动态代理的工厂。
ProxyConfig
创建代理的时候的配置的超类,用于确保所有的代理创建者有一致性的属性。
Advised
被建议者
TargetClassAware
用于暴露被代理的类(target class)的接口。
MethodCacheKey
对method的简单包装类,用于缓存method。
ProxyFactory
由上述创建一个简单实例中,我们可以看到,ProxyFactory生成一个代理的有两个要素,一个是被代理的对象,第二个是被代理对象的接口类型。ProxyFactory主要是提供了生成动态代理的基本方法,但是这样生成的动态代理,往往离我们实际需要的代理,还是有些远的。
ProxyFactory可以生产的代理类型
- JdkDynamicAopProxy jdk自身的动态代理
- CglibAopProxy cglib动态代理
一般来说我们更常用的动态代理是Cglib的代理。我们使用动态代理,更多的时候,是为了使用建议者的功能,使得可以在调用方法的前后去做一些额外的工作。使用ProxyFactory我们可以快速生产代理,可惜默认只使用ProxyFactory类里的方法生成的代理,并没有被建议,这不是我们想要。实际上有两种方法可以让被代理的对象得到建议。
使得被代理对象被建议的方法
- 直接在生成的代理对象上添加。
- 在工厂创建的时候添加。
直接添加在被代理对象上
假设我们现在有一个类如下
public class SendCheck { public void sendMsg(){ System.out.println("I am sendCheck"); }}
我们要给这个添加代理者
SendCheck sendCheck = new SendCheck(); ProxyFactory factory = new ProxyFactory(); factory.setTarget(sendCheck); Object proxy = factory.getProxy(); SendCheck check = (SendCheck)proxy; Advised advised = (Advised)proxy; advised.addAdvice(new MethodInterceptor() { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("I am interceptor"); return invocation.proceed(); } }); check.sendMsg();
上述代码的执行结果为
I am interceptorI am sendCheck
使用factory工厂配置
使用factory工厂,可以直接在创建初期就创建
SendCheck sendCheck = new SendCheck(); ProxyFactory factory = new ProxyFactory(); factory.setTarget(sendCheck); factory.addAdvice(new MethodInterceptor() { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("I am interceptor"); return invocation.proceed(); } }); Object proxy = factory.getProxy(); SendCheck check = (SendCheck)proxy; check.sendMsg();
在这种情况下,两种方式是等价的,但是实际上factory可以调控的范围更广,例如factory可以设置opaque,
factory.setOpaque(true);
来阻止生成的代理对象被转换为Advised。这个时候如果还想添加建议Advice,就只能靠反射强行突破了。
ProxyCreatorSupport
实际生成代理的工厂来自于AopProxyFactory,ProxyCreatorSupport是ProxyFactory的超类,提供访问aopProxyFactory工厂更为便利的方式,主要包含以下几个功能
- 快捷的访问aopProxyFactory的方法
- aop代理配置管理的监听器的管理
ProxyCreatorSupport含有三个组成部分
- aopProxyFactory 实际的创建工厂
- listeners 配置的监听器,当代理以及代理的配置发生改变的时候会被触发
- active 第一个代理是否被创建的标识
AdvisedSupport
AdvisedSupport是ProxyCreateSupport的超类,AdvisedSupport主要的作用是做AOP代理的配置管理工作。主要的职责有
- 管理建议者Advisor
- 管理建议者的调用连
- 建议者变更的时候,通知代理监视器
注意在Spring的代理中,有Advised,Advice,Advisor,要注意区分。
- Advised 被建议者
- Advice 建议本身
- Advisor 提出建议者(其一定会携带建议)
AdvisedSupport.MethodCacheKey
这个子类是一个Method的简单封装类,用于缓存method的时候,提供一些必要信息,因为建议者的调用链工厂方法在获取调用链的时候会耗费比较多的性能,所以需要本地Cache,用于提高性能。
ProxyConfig
AdvisedSupport的超类,正如它的名字那样,ProxyConfig管理者代理的配置信息。其包含的配置如下
proxyTargetClass
是否直接代理对象本身,而不是仅仅代理对象的接口,默认该值为false,表示代理对象的接口。如果设置为true,则会强制代理TargetSource中暴露出来的目标类(Target Class),如果目标类是接口,则会创建JDK的代理,否则创建CGLIB类型的代理。尽管如此实际行为还是要依赖具体的代理工厂的配置,如果配置中没有接口,那么也会直接代理对象,而不是其接口。
optimize
设置是否要对代理做强性能优化,一般来说强性能优化会区别于普通代理,这在某种意义上也是一种折中,所以默认使用的为false。举例来说,优化通常会导致当建议发生改变的时候,已经被创建的代理将不会获知建议的最新内容。因为这个原因该值默认被置为false。并且及时optimize为true,它也可能因为一些其它的预设置而被忽略。比如说设置了exposeProxy为true,而这个设置与optimize为true是互相不兼容的,optimize并不会发生作用。
注意:
有些文章声称optimize为true表示强制使用CGLIB代理,我不知道他们是在哪个版本得出这个结论的,在我所阅读的3.2.5的版本中,Aop默认的代理工厂为
DefaultAopProxyFactory,其中createAopProxy的代码,如下
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); }}
可以看出,设置optimize并不意味着一定使用CGLIB代理的。虽然一般来说,大部分情况使用的还是CGLIB代理。
opaque
是否要是的代理不透明化。在文章开始我们提到过,代理工厂生产的代理对象,是可以被转换(cast)成Advised的,然后通过Advised来修改建议,因为理论上生产的代理对象都是被建议者。但是有的时候我们不希望被建议者再添加其它建议者,这个时候可以设置opaque=true,表示被建议者不对外部透明,也就是说这个时候,如果试图将生产的代理转换(cast)为Advised,将会得到失败异常。
exposeProxy
是否将代理暴露给Spring AOP 的ThreadLocal。这个需要一些特别的解释。
假设我们现在有如下需求,我们现在要设计一个登入系统,并且需要通过日志去记录登入每一次登入的参数,当然登入肯定还会有一些校验的逻辑,假设我们如下设计代码。我们使用动态代理,会有如下代码。
public static class Login{ public void login(Long userId){ check(userId); System.out.println("userId user logining"); } public void check(Long userId){ System.out.println("check userId is " + userId); }}public static class LogInterceptor implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("I am Log method name is " + invocation.getMethod().getName() + " and arg is " + Arrays.toString(invocation.getArguments())); return invocation.proceed(); }}@Testpublic void test(){ ProxyFactory factory = new ProxyFactory(); factory.setTarget(new Login()); factory.addAdvice(new LogInterceptor()); Login login = (Login) factory.getProxy(); login.login(123L);}
如上代码所示,我们需要在每次登入之前,都会记录当前登入用户的userId,上述代码的执行如下。
I am Log method name is login and arg is [123]check userId is 123userId user logining
现在我们需求变了,我们希望除了能记录login的参数外,也能记录check的入参,同样要通过动态代理实现。通过上面的介绍我们知道,之所以对象能调用建议的方法,是因为外层实际是被代理的,但是到对象的实际内部,他们之间的调用是不会经过代理的。所以如果希望check能在内部也被建议,那么唯一的办法,就在是内部调用代理对象。而Spring AOP给我们提供了该方法。通过ThreadLocal来实现。
使用如下
public static class Login{ public void login(Long userId){ ((Login)AopContext.currentProxy()).check(userId); System.out.println("userId user logining"); } public void check(Long userId){ System.out.println("check userId is " + userId); }}
这个功能默认不会开启,如果要开启,就要设置exposeProxy = true。这就是exposeProxy的作用。
frozen
frozen 顾名思义表示固化,当我们希望代理的对象的配置不再被改变的时候,我们可以设置frozen=true,阻止被添加新的建议。
Advised
被建议者,提供了一系列用于查询配置信息等一系列is功能,
- ProxyFacotroy源码解析
- 源码解析
- 源码解析
- 【JDk源码解析之一】ArrayList源码解析
- 【源码解析】-- ArrayList的源码解析
- EventBus源码解析(史上最全的源码解析)
- 【源码】Vector、Stack源码解析
- Sping源码解析-源码下载
- <Android源码>IntentService源码解析
- JAVA源码解析-String源码
- JAVA源码解析-ArrayList源码
- JAVA源码解析-LinkedList源码
- Spark源码-SparkContext源码解析
- Jboss源码解析
- 网页病毒源码解析
- strlen源码解析
- chrome源码解析系列
- gnash源码解析
- 朴素_贝叶斯分类算法(网络)
- sqlite 打开扩展名为.DB 文件
- JSON数据保存与加载
- Android开发书籍推荐:从入门到精通系列学习路线书籍介绍
- 外网访问 Ubuntu下的 tomcat
- ProxyFacotroy源码解析
- 字符与字符串
- EasyPusher Android实时推送当前屏幕画面
- 不再以讹传讹,GET和POST的真正区别
- 折腾树莓派(四)使用gitosis创建git托管服务器
- 装饰器(Decorator)
- TypeError document.getElementById(...) is null错误原因
- 亚像素边缘检测评述
- 加快 iTunes 从苹果 App Store 下载速度