AOP(Aspect Oriented Programming):面向切面编程。AOP是在我们原来写的代码的基础上,进行一定的包装,比如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者增强处理。面向对象的编程核心模块是类,然而在AOP中核心模块是切面。切面实现了多种类型和对象的模块化管理,比如事物的管理、权限控制。通过这边文章,我们可以了解到以下几点:
目录:
AOP的基本概念
AOP两种底层实现原理
一、AOP基本概念
什么是AOP:
AOP和Filter、Interceptor的异同
我们举个例子来说明AOP是来解决什么样的问题。我们都知道传统的OOP是自上而下的逻辑开发,见下图:
上图描述的是一个后台方法的调用过程,我们从浏览器发出的http请求根据url找到controller,然后controller会去调用对应的的service,service再去调用dao、然后将处理的结果返回给浏览器。在这个流程中,我们可以根据Filter、Interceptor、AOP其中的技术对请求进行拦截从而实现切面。我们以记录接口请求日志需求为例来说明三者的技术点和区别。最简单的方法就是我们在每一个controller方法中调用一个打印相关信息的函数,这样会存在一个问题,有多少接口,我们就会调用多少次日志打印函数。出了aop,这样的功能我们还可以通过Filter或者Interceptor实现请求拦截功能,记录日志信息。一个请求过来,先进行过滤器处理,看程序是否受理该请求 。 过滤器放过后 , 程序中的拦截器进行处理 ,处理完后进入 被 AOP动态代理重新编译过的主要业务类进行处理 。那么该有人问什么是Filter、Interceptor,那它们和我们将要说的AOP有什么区别?
Filter:使用的反射机制,和框架无关,过滤器拦截的是URL,可以控制最初的http请求,但是更细一点的类和方法控制不了。
Interceptor:主要使用的函数回调,拦截器拦截的也是URL,拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。
AOP: 使用的是动态代理,面向切面拦截的是类的元数据(包、类、方法名、参数等) 相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。
三者功能类似,但各有优势,从过滤器 ->拦截器 ->切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是AOP。又比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;针对日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。
AOP中的一些概念:
AOP是一种面向切面的编程思想。这些横切性问题,把它们抽象为一个切面,关注点在切面的编程。
AOP主要应用在日志记录,权限验证,事务管理中。我们首先来看一下Spring官方提供的一些有关AOP的基本概念:
1).通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理;通知类型,主要有以下几种:
Before :前置通知,在连接点方法前调用;对应Spring中@Before注解;
After :后置通知,在连接点方法后调用;对应Spring中的@After注解;
AfterReturning:返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常;对应Spring中的@AfterReturning注解;
AfterThrowing:异常通知,当连接点方法异常时调用;对应Spring中的@AfterThrowing注解;
Around:环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法;对应Spring中的@Around注解;
2).连接点(Join Point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用,可以说目标对象中的方法就是一个连接点;
3).切点(Pointcut): 就是连接点的集合;对应Spring中的@Pointcut注解;
4).切面(Aspect): 切面是通知和切点的结合;对应Spring中的注解@Aspect修饰的一个类;
5).目标对象(Target object):即被代理的对象;
6).代理对象(AOP proxy):包含了目标对象的代码和增强后的代码的那个对象;
具体概念可以参照下图:
我们利用上面这一张图来说一下目标对象和代理对象的关系,代理对象可以看作是目标对象的加强版,它是对目标对象中方法功能的一个扩充。代理对象的实现主要有两种,一种是基于jdk动态代理的,这要求目标对象必须是接口的实现;而另一种实现是基于cglib,即代理对象是继承自目标对象。
切点匹配表达式
切面是如何拦截到指定的类的元数据(包、类、方法名、参数等) 的呢,这是通过切点匹配表达式实现的。目前Spring支持的切点匹配表达式主要有以下几种:
execution:可以定义到的最小粒度是方法,修饰符,包名,类名,方法名,Spring AOP主要也是使用这个匹配表达式;
within:只能定义到类;例如@Pointcut(within(com.jnu.example.*))
this:当前生成的代理对象的类型匹配;
target:目标对象类型匹配;
args:只针对参数;
annotation:针对注解;
例如: execution (* com.sample.service..*. *(..))
整个表达式可以分为五个部分:
- 1、execution()::表达式主体;
- 2、第一个*号:表示返回类型, *号表示所有的类型;
- 3、包名:表示需要拦截的包名,包名后面的..,表明com.sample.service包、及其子包;
- 4、第二个号:表示类名,号表示所有的类;
- 5、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个点表示任何参数;
- 关于Spring AOP注解的使用,我这里就不介绍了,网上有大量的博客介绍如何使用。我们接下来就带大家来实现一个类似within切点匹配表达式的效果
关于切点表达式可以参考如下博客:https://www.cnblogs.com/xiao-lei/p/11707222.html
二、AOP两种实现方式详解
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
JDK动态代理:
针对目标对象的接口进行代理 ,动态生成接口的实现类 (必须有接口)
过程要点
1.必须对接口生成代理
2.采用Proxy对象,通过newProxyInstance方法为目标创建代理对象。
该方法接收三个参数 :
(1)目标对象类加载器
(2)目标对象实现的接口
(3)代理后的处理程序InvocationHandler
3.实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke
JDK动态代理产生的对象不再是原对象
Cglib动态代理:
Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理
该代理方式需要相应的jar包,但不需要导入。因为Spring core包已经包含cglib ,而且同时包含了cglib 依赖的asm的包(动态字节码的操作类库)
总结
spring在运行期,生成动态代理对象,不需要特殊的编译器
Spring AOP 优先对接口进行代理 (使用Jdk动态代理)如果目标对象没有实现任何接口,才会对类进行代理 (使用cglib动态代理)
需要注意的
1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案
2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的
3.spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果