Spring AOP介绍及源码分析
转自:http://www.uml.org.cn/j2ee/201301102.asp
软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程;前者是为了提高开发效率,而后者则使用了归纳法,把具有共性的东西进行归类并使之模块化,达到便于维护和扩展的目的;如果说面向对象编程可以对业务需求进行很好的分解使之模块化;那么面向切面编程AOP(Aspect-Oriented Programming)则可以对系统需求进行很好的模软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程;前者是为了提高开发效率,而后者则使用了归纳法,把具有共性的东西进行归类并使之模块化,达到便于维护和扩展的目的;如果说面向对象编程可以对业务需求进行很好的分解使之模块化;那么面向切面编程AOP(Aspect-Oriented Programming)则可以对系统需求进行很好的模块组织,简化系统需求和实现之间的对比关系,是对OOP思想的一种补充;块组织,简化系统需求和实现之间的对比关系,是对OOP思想的一种补充;
一、AOP介绍
举个例子来说明一下吧!现在系统中有很多的业务方法,如上传产品信息、修改产品信息、发布公司库等;现在需要对这些方法的执行做性能监控,看每个业务方法的执行时间;在不改变原业务代码的基础上,也许我们会这么做:
Offer接口:
Offer实现:
Offer代理:
我们要通过下面的方式来使用:
上面的例子的输出为:
上面的例子中,OfferProxy实现了IOffer,而所有的业务实现均委托给其成员offer;可以想像,这应该就是最简单的AOP的实现了;但这种方式会存在一个问题:如果有非常多的这种业务对象需要性能监控,我们就需要写同样多的XyzProxy来满足需求,这也是非常巨大的工作量。
上面说到了代理,我们先看看代理模式吧!
二、代理模式及实现
下面是代理模式的类图:
代理模式类图
代理模式中,存在一个称为ProxyObject的代理对象和RealObject的真实对象,它们都实现了相同的接口;在调用的地方持有ProxyObject的实例,当调用request()方法时,ProxyObject可以在执行RealObject.request()前后做一些特定的业务,甚至不调用RealObject.request()方法。
目前实现代理模式的方式有两种:基于JDK的动态代理和基于CGLIB字节码的代理。
2.1 JDK动态代理
JDK动态代理,顾名思义,是基于JDK的反射(reflect)机制;在JDK中,提供了InvocationHandler这个接口,下面是JDK里面的注释:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
简单翻译,意思是说:该接口由被代理对象的handler所实现;当调用代理对象的方法时,该方法调用将被编码,然后交给代理对象的invoke方法去执行;
是不是有一种豁然开朗的感觉呢?没错,答案就在你心中。
这样,上面的代码就可以改成下面的实现方式:
调用端:
通过这种方式,你不需要为针对每一个业务写一个代理对象,就可以很轻松地完成你的需求;但也许你已经注意到了,JDK的动态代理,在创建代理对象(上面红色代码部分)时,被代理的对象需要实现接口(即面向接口编程);
这就是JDK的动态代理,简单吧!下面看看CGLIB代理方式。
2.2 CGLIB代理
如果目标对象没有实现任何接口,那怎么办呢?不用担心,你可以用CGLIB来实现代理:
调用端:
使用CGLIB创建的代理对象,其实就是继承了要代理的目标类,然后对目标类中所有非final方法进行覆盖,但在覆盖方法时会添加一些拦截代码(上面CglibProxyFactory类中的intercept方法)。
下面看看Spring中是如何实现AOP的。
三、Spring AOP的实现
3.1 Spring AOP的几个概念
Spring AOP中的几个基本概念,每次学习AOP都被这几个概念折腾的很不爽,我们在这里再把这几个概念描述一遍,力争把这几个概念搞清,在每次review这块内容的时候可以很快上手。
1.切面(Aspect):切面就是一个关注点的模块化,如事务管理、日志管理、权限管理等;
2.连接点(Joinpoint):程序执行时的某个特定的点,在Spring中就是一个方法的执行;
3.通知(Advice):通知就是在切面的某个连接点上执行的操作,也就是事务管理、日志管理等;
4.切入点(Pointcut):切入点就是描述某一类选定的连接点,也就是指定某一类要织入通知的方法;
5.目标对象(Target):就是被AOP动态代理的目标对象;
用一张图来形象地表达AOP的概念及其关系如下:
3.2 Spring AOP中切入点、通知、切面的实现
理解了上面的几个概念后,我们分别来看看Spring AOP是如何实现这些概念的;
1.切入点(Pointcut):它定义了哪些连接点需要被织入横切逻辑;在Java中,连接点对应哪些类(接口)的方法。因此,我们都能猜到,所谓的切入点,就是定义了匹配哪些娄的哪些方法的一些规则,可以是静态的基于类(方法)名的值匹配,也可以是基于正则表达式的模式匹配。来看看Spring AOP Pointcut相关的类图:
在Pointcut接口的定义中,也许你已经想到了,ClassFilter是类过滤器,它定义了哪些类名需要拦截;典型的两个实现类为TypePatternClassFilter和TrueClassFilter(所有类均匹配);而MethodMatcher为方法匹配器,定义哪些方法需要拦截。
在上面的类图中:
- StaticMethodMatch与DynamicMethodMatch的区别是后者在运行时会依据方法的参数值进行匹配。
- NameMatchMethodPointCut根据指定的mappedNames来匹配方法。
- AbstractRegexpMethodPointCut根据正则表达式来匹配方法。
1.通知(Advice):通知定义了具体的横切逻辑。在Spring中,存在两种类型的Advice,即per-class和per-instance的Advice。
所谓per-class,即该类型的Advice只提供方法拦截,不会为目标对象保存任何状态或者添加新的特性,它也是我们最常见的Advice。下面是per-class的类图:
- BeforeAdvice:在连接点前执行的横切逻辑。
- AfterReturningAdvice:在连接点执行后,再执行横切逻辑。
- AfterAdvice:一般由程序自己实现,当抛出异常后,执行横切逻辑。
- AroundAdvice:Spring AOP中并没有提供这个接口,而是采用了AOP Alliance的MethodInteceptor接口;通过看AfterReturningAdvice的源码我们知道,它是不能更改连接点所在方法的返回值的(更改引用);但使用的MethodInteceptor,所有的事情,都不在话下。
在上面的类图中,还有两种类没有介绍,那就是***AdviceAdapter和***AdviceInteceptor,我们以AfterReturningAdviceInterceptor为例来说明:
该类实现了MethodInterceptor和AfterAdvice接口,同时构造函数中还有一个AfterReturningAdvice实例的参数;这个类存在的作用是为了什么呢?对,没错,Spring AOP把所有的Advice都适配成了MethodInterceptor,统一的好处是方便后面横切逻辑的执行(参看下一节),适配的工作即由***AdviceAdapter完成;
哈哈,Spring AOP的代码也不过如此嘛:所谓的AfterReturningAdvice,通过适配成MethodInterceptor后,其实就是在invoke方法中,先执行目标对象的方法,再执行的AfterReturningAdvice所定义的横切逻辑。你现在明白它为什么不能修改返回值的引用了吧?
对于per-instance的Advice,目前只有一种实现,就是Introduction,使用的场景比较少,有兴趣的同学可以自己研究一下,呵呵!
1.切面(Aspect):在Spring中,Advisor就是切面;但与通常的Aspect不同的是,Advisor通常只有一个Pointcut和一个Advice,而Aspect则可以包含多个Pointcut和多个Advice,因此Advisor是一种特殊的Aspect。但,这已经够用了!
接下来看下per-class Advisor的类图:
其实没有什么好看的,前面已经说过,Advisor包含一个Pointcut和一个Advisor;在AbstractGenericPointcutAdvisor中,持有一个Advice的引用;下面的几个实现,均是针对前面提到的几种不同的Pointcut的实现。
3.3 Spring AOP实现的基本线索
我们选择ProxyFactoryBean作为入口点和分析的开始。ProxyFactoryBean是在Spring IoC环境中,创建AOP应用的最底层方法,从中,可以看到一条实现AOP的基本线索。
所有的逻辑从以下的方法开始,我们主要针对单例的代理对象的生成:
下面我们深入到SpringAOP核心代码的内部,看看代理对象的生成机制,拦截器横切逻辑以及织入的实现。
3.4 代理对象的生成
对于getSingletonInstance()方法返回了什么,这就是代理对象如何产生的逻辑了,然我们须根溯源,看看传说中的proxy到底是如何一步一步的产生的。
ProxyFactoryBean是AdvisedSupport的子类,Spring使用AopProxy接口把AOP代理的实现与框架的其他部分分离开来。在AdvisedSupport中通过这样的方式来得到AopProxy,当然这里需要得到AopProxyFactory的帮助 ,从JDK或者cglib中得到想要的代理对象:
这个DefaultAopProxyFactory是Spring用来生成AopProxy的地方,它包含JDK和Cglib两种实现方式。让我接着往里面看:
可以看到其中的代理对象可以由JDK或者Cglib来生成,JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,我们进入JdkDynamicAopProxy实现中看看Proxy是怎样生成的:
用Proxy包装target之后,通过ProxyFactoryBean得到对其方法的调用就被Proxy拦截了, ProxyFactoryBean的getObject()方法得到的实际上是一个Proxy了,target对象已经被封装了。对 ProxyFactoryBean这个工厂bean而言,其生产出来的对象是封装了目标对象的代理对象。
3.5 拦截器的作用
前面分析了SpringAOP实现中得到Proxy对象的过程,接下来我们去探寻Spring AOP中拦截器链是怎样被调用的,也就是Proxy模式是怎样起作用的。
还记得在JdkDynamicAopProxy中生成Proxy对象的时候,有一句这样的代码吗?
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
这里我们的JdkDynamicAopProxy实现了InvocationHandler这个接口,this参数对应的是InvocationHandler对象,也就是说当 Proxy对象的函数被调用的时候,InvocationHandler的invoke方法会被作为回调函数调用:
上面所说的目标对象方法的调用,是通过AopUtils的方法调用,使用反射机制来对目标对象的方法进行的:
接下来,我们来看具体的ReflectiveMethodInvocation中proceed()方法的实现,也就是拦截器链的实现机制:
从上面的分析我们看到了Spring AOP拦截机制的基本实现,比如Spring怎样得到Proxy,怎样利用JAVA Proxy以及反射机制对用户定义的拦截器链进行处理。
3.6 织入的实现
在上面调用拦截器的时候,经过一系列的注册,适配的过程以后,拦截器在拦截的时候,会调用到预置好的一个通知适配器,设置通知拦截器,这是一系列Spring设计好为通知服务的类的一个,是最终完成通知拦截和实现的地方,例如对 MethodBeforeAdviceInterceptor的实现是这样的:
可以看到通知适配器将advice适配成Interceptor以后,会调用advice的before方法去执行横切逻辑。这样就成功的完成了before通知的织入。
Spring AOP介绍及源码分析的更多相关文章
- spring boot 2.0 源码分析(一)
在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...
- Spring JPA实现逻辑源码分析总结
1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...
- spring boot 2.0 源码分析(四)
在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...
- Spring中Bean命名源码分析
Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...
- Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)
Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...
- Spring Boot 自动配置 源码分析
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
- 设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)
1 智能生活项目需求 看一个具体的需求 1) 我们买了一套智能家电,有照明灯.风扇.冰箱.洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作. 2) 这些智能家电来自不同的厂家,我们不想针 ...
- 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)
1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...
- spring boot 2.0 源码分析(二)
在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...
随机推荐
- ZUFE2483 DO IT YOURSELF 2017-05-31 14:41 40人阅读 评论(0) 收藏
2483: DO IT YOURSELF 时间限制: 2 Sec 内存限制: 128 MB 提交: 8 解决: 3 [提交][状态][讨论版] 题目描述 有四个字符串S,T,tmp,ans,一开始 ...
- vc6中向vs2010迁移的几个问题
vc6版本支持的库编译:CJ60lib 1. 用vs2010打开CJ60库的源码的dsw,强制打开 (1)设置项目属性的语言 因为,如果代码字符的编码集不一样,则会出现函数冲定义,参数冲突等问题,这可 ...
- js-实现双色球功能
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- shell 命令之 crontab
crontab是shell命令中的定时任务: crontab -e 进入当前定时任务的vim页面 每行是一个独立的定时脚本,使用和vim的语法部署定时任务 如下图: 脚本执行周期设置 可以用下面的网页 ...
- Aggregate类型以及值初始化
引言 在C++中,POD是一个极其重要的概念.要理解POD类型,我们首先需要理解Aggregate类型.下文结合stackoverflow上的高票回答将对Aggregate类型做一个全面的解读. 对于 ...
- ContentControl as CC和ContentPresenter as CP的使用
1.CC为文本控件的父类,它继承为control,所以他是控件, 2.CP继承FrameworkElement,所以他是容器,相当于占位符 3.想让控件中能包含子控件就需要用CP,反之用CC就行.(不 ...
- Java内存模型与共享变量可见性
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:本文主要参考自<深入理解Java虚拟机(第二版)>和<深入理解Java内存模型> ...
- Linux 安装JavaEE环境之Tomcat安装笔记
1.先用xftp将tomcat的压缩包上传到 /opt/ 2.在/usr/local/下使用命令mkdir tomcat 创建tomcat目录 将apache-tomcat-7.0.70.tar.gz ...
- Android---------------Handler的学习
public LocalVPNService() { mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mBackgro ...
- Linux基础命令-cd
cd 作用:切换路径 切换至家目录 $ cd $ cd~ 在上一个目录和当前目录来回切换 $ cd - 切换至某用户的家目录 # cd ~ # pwd /root # cd ~quail #pwd / ...