- 概念
Aspect Oriented Programming,面向切面编程,实际上它是一个规范、一种设计思路,总之是抽象的。
先上图
- 使用目的
从项目结构上来说
对业务逻辑的各个部分进行隔离,降低业务逻辑的耦合度
从开发的角度上说
减少重复编码,提高程序的可重用性,提高了开发的效率
总结简单的讲:就是那些与业务无关,却为业务模块所共同调用的逻辑封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- 使用场景
日志记录,性能统计,安全控制,事务处理,异常处理等
- 概念理解(仅指java中)
先上图
圆柱:原有的业务流程,从上至下执行
蓝色面:即切面,为指定的业务流程增加功能;交界处即切点,意味着将增加的功能插入到指定位置(单位是方法)
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点,即公共封装的业务逻辑,如日志记录
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器,在此不做深究
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
- spring aop实现方法
通常我们采用2种
基于注解
/** * 系统服务组件Aspect切面Bean*/ //声明这是一个组件 @Component //声明这是一个切面Bean @Aspect public class ServiceAspect { private final static Log log = LogFactory.getLog(ServiceAspect.class); //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点 @Pointcut("execution(* com.hyq.aop..*(..))") public void aspect(){ } /* * 配置前置通知,使用在方法aspect()上注册的切入点 * 同时接受JoinPoint切入点对象,可以没有该参数 */ @Before("aspect()") public void before(JoinPoint joinPoint){ System.out.println("执行before....."); } //配置后置通知,使用在方法aspect()上注册的切入点 @After("aspect()") public void after(JoinPoint joinPoint){ System.out.println("执行after....."); } //配置环绕通知,使用在方法aspect()上注册的切入点 @Around("aspect()") public void around(JoinPoint joinPoint){ long start = System.currentTimeMillis(); try { ((ProceedingJoinPoint) joinPoint).proceed(); long end = System.currentTimeMillis(); if(log.isInfoEnabled()){ log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!"); } } catch (Throwable e) { long end = System.currentTimeMillis(); if(log.isInfoEnabled()){ log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage()); } } } //配置后置返回通知,使用在方法aspect()上注册的切入点 @AfterReturning("aspect()") public void afterReturn(JoinPoint joinPoint){ if(log.isInfoEnabled()){ log.info("afterReturn " + joinPoint); } } //配置抛出异常后通知,使用在方法aspect()上注册的切入点 @AfterThrowing(pointcut="aspect()", throwing="ex") public void afterThrow(JoinPoint joinPoint, Exception ex){ if(log.isInfoEnabled()){ log.info("afterThrow " + joinPoint + "\t" + ex.getMessage()); } } }
基于配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--<context:component-scan base-package=""--> <!-- 定义目标对象 --> <bean name="productDao" class="com.xxx.spring.springAop.dao.daoimp.ProductDaoImpl" /> <!-- 定义切面 --> <bean name="myAspectXML" class="com.xxx.spring.springAop.AspectJ.MyAspectXML" /> <!-- 配置AOP 切面 --> <aop:config> <!-- 定义切点函数 --> <aop:pointcut id="pointcut" expression="execution(* com.xxx.spring.springAop.dao.ProductDao.add(..))" /> <!-- 定义其他切点函数 --> <aop:pointcut id="delPointcut" expression="execution(* com.xxx.spring.springAop.dao.ProductDao.delete(..))" /> <!-- 定义通知 order 定义优先级,值越小优先级越大--> <aop:aspect ref="myAspectXML" order="0"> <!-- 定义通知 method 指定通知方法名,必须与MyAspectXML中的相同 pointcut 指定切点函数 --> <aop:before method="before" pointcut-ref="pointcut" /> <!-- 后置通知 returning="returnVal" 定义返回值 必须与类中声明的名称一样--> <aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnVal" /> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="pointcut" /> <!--异常通知 throwing="throwable" 指定异常通知错误信息变量,必须与类中声明的名称一样--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/> <!-- method : 通知的方法(最终通知) pointcut-ref : 通知应用到的切点方法 --> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
重点介绍下pointcut(切入点) expression表达式解析及配置
方法参数匹配
args()
@args()
方法描述匹配
execution()
当前AOP代理对象类型匹配
this()
目标类匹配
target()
@target()
within()
@within()
标有此注解的方法匹配
@annotation()
其中execution 是用的最多的,其格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
ret-type-pattern,name-pattern, parameters-pattern是必须的.
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名, *代表所有
如:set*代表以set开头的所有方法.
parameters-pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数
(*,String)代表第一个参数为任何值,第二个为String类型.
更多配置细节可查看官方文档:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/aop.html#aop-introduction
- 原理分析
简单的讲,即使用jdk或cglib的动态代理功能,对目标类生成代理类,然后在代理类中调用或执行额外的新增功能。
先上图
在此推荐看https://blog.csdn.net/luanlouis/article/details/51095702
详细说明了spring aop实现的大致逻辑和机制
另外推荐https://blog.csdn.net/luanlouis/article/details/51155821
通过解析spring源码来深入理解spring aop的实现