面试真题--------spring源码解析AOP
接着上一章对IOC的理解之后,再看看AOP的底层是如何工作的。
1.实现AOP的过程
首先我们要明白,Spring中实现AOP,就是生成一个代理,然后在使用的时候调用代理。
1.1 创建代理工厂
代码中首先创建一个代理工厂实例ProxyFactory proxyFactory = new ProxyFactory();
代理工厂的作用就是使用编程的方式创建AOP代理。ProxyFactory继承自AdvisedSupport,AdvicedSupport是AOP代理的配置管理器。然后是设置要代理的目标对 象proxyFactory.setTarget(new LoginServiceImpl());
,看下setTarget方法:
public void setTarget(Object target) {
//先根据给定的目标实现类,创建一个单例的TargetSource
//然后设置TargetSource
setTargetSource(new SingletonTargetSource(target));
}
1.2 添加通知
上面设置了要代理的目标类之后,接着是添加通知,也就是添加增强类,proxyFactory.addAdvice()
方法是添加增强类的方法。我们在例子中是这么使用的:
1 |
proxyFactory.addAdvice(new LogBeforeLogin());//前置增强 |
addAdvice方法的参数是一个Advice类型的类,也就是通知或者叫增强,可以去我们的增强类中查看,我们都继承了各种Advice,比如MethodBeforeAdvice
,AfterReturningAdvice
,MethodInterceptor。
1.3 获取代理
JDK动态代理方式获取代理
JDK动态代理方式获取代理,实现在JdkDynamicAopProxy中:
1 |
public Object getProxy() { |
1 |
public Object getProxy(ClassLoader cl) { |
关于JDK反射创建代理之类的,这里不做解析。
CGLIB方式获取代理
CGLIB获取方式,实现在Cglib2AopProxy中:
1 |
public Object getProxy() { |
使用代理
上面获取代理之后,就剩最后一步,使用,当我们调用业务方法的时候,实际上是调用代理中的方法,对于CGLIB生成的代理,调用的是DynamicAdvisedInterceptor的intercept方法;JDK动态代理生成的代理是调用invoke方法。
调用ProxyFactoryBean的getObject方法,也就是对bean增强的地方。下面我们着重来看一下是如何对bean进行增强的。首先我们进入到ProxyFactoryBean的getObject方法来看一下。
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
此处主要是先初始化了一下通知器链,然后就会根据是否单例做相应的动作,我们看一下初始化通知器链的进行。
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
} if (!ObjectUtils.isEmpty(this.interceptorNames)) {
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
} // Globals can't be last unless we specified a targetSource using the property...
if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
throw new AopConfigException("Target required after globals");
} // Materialize interceptor chain from bean names.
for (String name : this.interceptorNames) {
if (logger.isTraceEnabled()) {
logger.trace("Configuring advisor or advice '" + name + "'");
} if (name.endsWith(GLOBAL_SUFFIX)) {
if (!(this.beanFactory instanceof ListableBeanFactory)) {
throw new AopConfigException(
"Can only use global advisors or interceptors with a ListableBeanFactory");
}
addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
} else {
// If we get here, we need to add a named interceptor.
// We must check if it's a singleton or prototype.
Object advice;
if (this.singleton || this.beanFactory.isSingleton(name)) {
// Add the real Advisor/Advice to the chain.
advice = this.beanFactory.getBean(name);
}
else {
// It's a prototype Advice or Advisor: replace with a prototype.
// Avoid unnecessary creation of prototype bean just for advisor chain initialization.
advice = new PrototypePlaceholderAdvisor(name);
}
addAdvisorOnChainCreation(advice, name);
}
}
} this.advisorChainInitialized = true;
}
可以看到,其中针对我们配置的interpretorNames进行了循环,我们并非是配置的全局通知器,所以会进入else块,然后因为我们配置的testAdvisor默认是单例的,所以会从bean工厂中去获取这个实例,此时TestAdvisor已经实例化完成的,我们只是去取一下而已。然后就会进入addAdvisorOnChainCreation方法,就是把通知器加到了通知链当中。
值得注意的是在这个过程中,触发了一个这样的方法this.advisorAdapterRegistry.wrap(next)。这个方法就是用来包装通知器的,如果不是advisor而是advice,就会包装一下返回。
好了,接着刚才的过程,初始化通知器链完成以后,就会进入getSingletonInstance方法,这是用来获取单例实例的,而真正的加强也是在这里发生的,我们来看一下。
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
此时第一次获取,单例实例为null,所以会进入if块,首先刷新targetSource,因为我们的Target类没有实现targetSource接口,所以会由spring帮我们产生一个targetSource适配,这里是使用的适配器的模式,有兴趣可以进去看一下,我们此处不关注这个。接下来,会去判断代理接口,并且设置代理接口,但是我们的target未实现任何接口,所以此处interfaces仍然为空的,所以最后一步createAopProxy时,会帮我们创建cglib的proxy。最终由cglib生成代理返回。
2.spring aop通知(advice)分成五类
前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
异常返回通知[After throwing advice]:在连接点抛出异常后执行。
返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
总结一下,主要说几点:
1.在IOC容器初始化的过程中,并没有发生增强的动作,而是初始化了proxyFactoryBean。
2.如果配置中不指定,所有bean默认都是单例和非延迟加载的,也就是说所有的bean都将在第一次IOC容器初始化时全部实例化,所以上一章中所配置的三个bean都是在IOC容器初始化时进行的实例化。
3.springAOP代理有两种方式,一种是JDK提供的动态代理,一种是cglib字节码生成的技术,当要代理的类有实现的接口的时候,就会针对接口进行代理,否则就会采用cglib直接生成字节码产生子类。
参考---http://www.cnblogs.com/zuoxiaolong/p/spring7.html
http://cxis.me/2017/04/12/Spring%E4%B8%ADAOP%E6%BA%90%E7%A0%81%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90/
面试真题--------spring源码解析AOP的更多相关文章
- 面试真题--------spring源码解析IOC
spring是我经常使用的框架,可是你真的对spring理解吗? 还是只知道它得使用.如果你想知道它真实的面目请仔细向下看. 1.spring是如何知道哪些Bean需要实例化的? 容器启动过程中,首先 ...
- 专治不会看源码的毛病--spring源码解析AOP篇
昨天有个大牛说我啰嗦,眼光比较细碎,看不到重点.太他爷爷的有道理了!要说看人品,还是女孩子强一些.原来记得看到一个男孩子的抱怨,说怎么两人刚刚开始在一起,女孩子在心里就已经和他过完了一辈子.哥哥们,不 ...
- Spring源码解析-AOP简单分析
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等,不需要去修改业务相关的代码. 对于这部分内容,同样采用一个简单的例子和源码来说明. 接口 public ...
- Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean
Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...
- 面试必备:ArrayList源码解析(JDK8)
面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...
- spring 源码解析
1. [文件] spring源码.txt ~ 15B 下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB 下载( ...
- Spring源码解析——循环依赖的解决方案
一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...
- Spring源码解析系列汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...
- Spring 源码学习——Aop
Spring 源码学习--Aop 什么是 AOP 以下是百度百科的解释:AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程通过预编译的方式和运行期动态代理实 ...
随机推荐
- Ubuntu安装MyEclise16 过程差不多
选择好安装路径,和workpace路径,可能会因为工作空间放的位置不太对,导致myeclipse出现问题. 1.Ubuntu安装MyEclise10 不知道为什么网上会有那么多安装过程,还有配置目录和 ...
- IDEA集成git和使用步骤
2018年07月20日 11:26:29 完猛 阅读数:3246 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/miwanmeng/artic ...
- My sql 自增 虚拟列。
在MYSQL 是没有类似MSSQL 2008 / oracle 数据库开窗函数 over() ,rank(), DENSE_RANK() ,ROW_NUMBER() 又叫窗口函数 . 当我们需要在查询 ...
- FreeMarker的空值运算符和逻辑运算符
1.空值处理运算符 如果你在模板中使用了变量但是在代码中没有对变量赋值,那么运行生成时会抛出异常.但是有些时候,有的变量确实是null,怎么解决这个问题呢? 判断某变量是否存在:“??” 用法为:va ...
- 2.HTML+CSS制作一闪一闪亮晶晶的星星(stars)
效果地址:https://codepen.io/flyingliao/pen/NJxbdB?editors=1100 HTML code: <div class="stars" ...
- ssm学习的第一个demo---crm(1)
这是一个普通的CRM项目 (第一步规划好项目设计路线:导入jar包→配置sqlMapConfig.xml(空文件)→配置applicationContext.xml →配置springMVC.xml→ ...
- Kubernetes的ConfigMap说明
这篇博文,我们来说一说,关于在kubernetes的pod中自定义配置的问题. 我们知道,在几乎所有的应用开发中,都会涉及到配置文件的变更,比如说在web的程序中,需要连接数据库,缓存甚至是队列等等. ...
- Swoole 内存操作(Table)
使用: //实例化表格,参数 int : 最大行数 $table = new swoole_table(1024); //设置表格字段 参数 (字段名:string , 字段类型:int.float. ...
- react-native android打包
看了官网测试的是可以的,自己整理下,方便后面查看 先是生产安卓证书,安卓证书生成,点这里.这里掠过 生成安卓证书,记住2个密码 秘钥库口令 和 私钥密码 1.然后把你生成的安卓证书放到文件放到你工程中 ...
- groovy Date 格式化
刚开始使用Java,瞬间爱上:换了个厂接触到了groovy,开始有点嫌弃Java了... 看看时间的格式化 java玩法: new SimpleDateFormat("yyyy-MM-dd ...