AOP(Aspect-oriented Programming-面向切面编程)是一种编程模式,是对OOP(Object-oriented Programming-面向对象编程)一种有益补充。在OOP中,万事万物都是独立的对象,对象相互耦合关系是基于业务进行的;但在实际应用系统中,存在对大量对象的公共管理要求(如功能安全检查、日志记录、方法执行时间统计等),这些行为同业本身无关,因此是对象本身难于处理的,这就是AOP诞生和发展的原因。
基本概念
AOP要达到的目标就是在某些方法执行前或后,需要完成一种或几种通用的行为逻辑。示意图如下:
图中红色边框的图标组成了完整的AOP(只示意了在方法前,也可以在方法后):
日志记录:就是AOP要达成的目标-当执行对应方法时进行日志记录。对应AOP中的切面(Aspect)。
红线:代表切面(日志记录)切入位置。对应AOP概念的连接点(Joinpoint)
蓝色方块:代表切面(日志记录)真正起作用的条件。对应AOP概念的切入点(Pointcut)
红色箭头:代表发起执行的通知。对应AOP概念的通知(Advice)
术语
Aspect(切面):代指需要处理的共性业务,一个切面就是一个共性业务,如功能安全性验证就是一个切面,日志记录时另一个切面。实现时,通常处理该业务的类就代表一个切面。(中文中“切面”比“方面”更准确和形象,意思就是切入到方法前后干点事情)
Joinpoint(连接点):程序执行过程中需要切面起作用的位置,通常是对象的方法。
Advice(通知):一个切面在一个特定的连接点上采取的行动。可以更直白理解为在程序执行到连接点时,按照通知类型执行切面的对应方法。通知有五中类型(见后文)。
Pointcut(切入点):定义匹配连接点的表达式。Advice与切入点表达式相关联,并在切入点匹配的任何连接点运行(例如,执行具有特定名称的方法)。切入点表达式匹配的连接点概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。
Introduction(引言):代表类型声明其他方法或字段。Spring AOP允许您向任何通知的对象引入新的接口(以及相应的实现)。例如,您可以使用引言来使bean实现IsModified接口,以简化缓存。
Target Object(目标对象):由一个或多个切面通知的对象,即应用中同切面通知相关的业务逻辑对象。
Proxy Object(代理对象):是使用切面逻辑对业务逻辑进行包裹之后生成的对象。
AOP proxy(AOP代理):AOP框架创建的一个对象,用于实现切面的契约(如通知方法执行等)。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。
Weaving(织入):将切面与应用程序类型或对象链接以创建通知对象。通常是框架在编译时、加载时或运行时完成。Spring AOP是在运行时执行织入。
注:目标对象和代理对象的区别:
目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。
通知类型
Before advice:在连接点执行之前运行的通知,不能阻止执行流继续进行到连接点(除非抛出异常)。
After returning advice:在连接点正常执行完成后运行的通知(例如,如果一个方法返回而没有抛出异常)。
After throwing advice:如果方法通过抛出异常退出,则要运行的通知。
After(finally)advice:无论连接点以何种方式退出都要运行的通知(正常或异常返回)。
Around advice:围绕连接点(如方法调用)的通知。Around advice在方法执行前后都要运行对应的通知。它还负责选择是继续到连接点,还是通过返回自己的返回值或抛出异常来缩短通知方法的执行。
通知执行顺序
同一个Joinpoint如果出现多个通知,执行顺序: Around 的前advice–>Before advice–>(AfterThrowing advice)–>AfterReturn advice–>After advice–>Around的后 advice。
注意:如果发生AfterThrowing advice,后续的After Advice会执行,其它Advice则不再执行
AOP应用场景
- 日志记录:记录方法的入参、出参以及方法的执行时间等信息;
- 安全检查:在方法执行前对用户进行身份验证,判断其是否具备访问方法的权限;
- 性能监控:记录方法的调用频度、执行时间,方便分析程序性能瓶颈;
- 事务管理:在方法执行前开启事务,在方法执行后根据方法执行结果提交或回滚事务;
- 缓存:在方法执行前判断缓存中是否存在方法的结果,在方法执行后将结果存入缓存中,方便下次调用时使用
- 返回参数格式统一:可对前后端参数进行统一
- 异常处理:对异常可进行统一管理。
AOP实现机制
AOP是通过代理机制来实现的。
什么是代理
”代理“的本质是A要完成一些事,B可以代理A去完成,两者做的效果就是结果达成是一样的,但过程可能不一样。比如我要注册一家公司,肯定需获得营业执照,如果自己办理就容易跌坑,效率很低;我可以委托中介(代理)帮我完成,他们办证的过程和自己去办的过程肯定有不同,但最终目的都拿到了营业执照。从技术上来说,达成结果相同意味着直接执行方法本身即可,过程不同就意味着在方法执行之前或之后可以做其它事情。代理机制示意图如下:
图中可以看出代理前和代理后的区别就是方法被代理器做了封装(框架自动进行的):
1、从编码人员的角度看,两者没有任何区别,都是直接调用方法。
2、从机器角度看(编译后执行),调用实际由代理器来完成的,代理器还做了自己的事情(否则就没必要加代理器)。
3、从结果看,1和2获得的结果是一样的。
在Java中,引入动态代理的原因如下:
1、动态代理主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),如添加调用日志,做事务控制等。典型的应用就是AOP。
2、应用解耦:通过动态代理,应用之间的关系变得松散,从而更好的实现解耦合。例如,如果两个系统通过RMI通信,则每个系统都必须使用相同的消息结构和规范。动态代理允许不同的消息结构和规范之间进行映射,从而完成两个系统之间的通信。
3、优化性能:在调用远程服务或者本地数据库时,需要建立连接或者事务。使用动态代理可以避免在每个请求上进行开销巨大的连接和事务管理操作。例如,ORM框架就是利用了动态代理,将Java对象映射到数据库表中。
技术机制
代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。
实现模式
代理常见的实现模式有两种:静态代理和动态代理。
静态代理,是编译时增强,框架会在编译阶段生成代理类,在程序运行前代理类的.class文件就已经存在了。常见的实现:JDK静态代理,AspectJ 。
动态代理,是运行时增强,它不修改代理类的字节码,而是在程序运行时,运用反射机制,在内存中临时为方法生成一个代理对象,这个代理对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。常见的实现:JDK、CGLIB、Javassist(Hibernate中的使用动态代理)
CGLIB
GLIB主要就是为了增强动态代理功能提出的开源项目。技术上就是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。
CGLIB主要用于AOP、 测试、数据访问等框架。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。
CGLIB作为一个开源项目,其代码托管在github:https://github.com/cglib/cglib。
JDK代理和CGLIB区别
1、JDK代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类,但被代理类不能是final类或有final方法。
2、JDK代理使用Java原生的反射API进行操作,生成类比较高效;CGLIB使用ASM框架直接对字节码进行操作,类执行过程比较高效。
3、JDK代理需要自己写代理类,代理类需要实现与目标对象相同的接口。CGLIB不需要自己编写代理类,代理类是动态生成的。
Spring AOP代理
SpringAOP默认使用标准JDK动态代理作为AOP代理。这使得任何接口(或一组接口)都可以被代理。Spring AOP也可以使用CGLIB代理,如果业务对象未实现接口,则使用CGLIB。
Spring AOP功能和目标
1、Spring AOP是用纯Java实现的。Spring AOP不需要控制类加载器层次结构,因此适合在servlet容器或应用程序服务器中使用。
2、Spring AOP目前只支持方法执行连接点(建议在Spring Bean上执行方法)。虽然可以添加对字段拦截的支持,但没有实现字段拦截。如果有需要,考虑使用AspectJ。
3、Spring AOP的AOP方法不同于大多数其他AOP框架。其目的不是提供最完整的AOP实现(尽管Spring AOP非常强大)。相反,其目的是在AOP实现和SpringIoC之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。因此,Spring Framework的AOP功能通常与Spring IoC容器一起使用。特点是通过使用普通的bean定义语法来配置的,这是与其他AOP实现的一个关键区别。您无法使用SpringAOP轻松或高效地完成某些事情,例如通知非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最好的选择。
4、Spring AOP目的不是与AspectJ竞争以提供全面的AOP解决方案。它们是互补的,而不是竞争的。Spring AOP和IoC可以与AspectJ无缝集成,以在一致的基于Spring的应用程序架构中实现AOP的所有使用。
补充说明:
1、Spring框架的核心原则之一是非侵入性。就是不强迫您在业务或域模型中引入特定于框架的类和接口的想法。然而,在某些地方,Spring Framework确实为您提供了将特定于Spring Framework的依赖项引入代码库的选项。提供这些选项的理由是,在某些情况下,以这种方式阅读或编码某些特定的功能可能会更容易。无论如何,Spring Framework总是为您提供选择:您可以自由地做出明智的决定,决定哪个选项最适合您的特定用例或场景。因此,您可以选择用哪种AOP框架:AspectJ、SpringAOP,或者两者兼而有之。您还可以选择@AspectJ注释样式方法或SpringXML配置样式方法。
2、应用如何选择用Spring AOP还是AspectJ?选择原则:使用最简单的可行方法。SpringAOP比使用完整的AspectJ更简单,因为不需要在开发和构建过程中引入AspectJcompiler/weaver。如果您只需要在Spring管理的bean上执行操作,那么SpringAOP是正确的选择。如果您需要通知不是由Spring容器管理的对象(通常是域对象),则需要使用AspectJ。如果希望建议连接点而不是简单方法执行(例如,字段get或设置连接点等),则还需要使用AspectJ。