AOP(Aspect Orient Programming)
什么是AOP
是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
是一个动态过程,为设计好的对象在动态编译或运行是做服务增益
AOP应用原理初步分析
AOP在启动时为目标类型创建子类或兄弟类型的对象(这个对象称之为动态代理对象)
其中:
为目标类型(XxxServiceImpl)创建其代理对象方式有两种:
-
第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
-
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.
说明: springboot工程默认的AOP代理为CGLIB代理,假如需要使用 JDK 动态代理可以在配置文件(applicatiion.properties)中进行如下配置:
spring:
aop:
proxy-target-class: false
AOP 相关术语概要分析
- 切面对象(Aspect):封装了扩展业务逻辑的对象,在spring中可以使用@Aspect描述.
- 切入点(Pointcut):定义了切入扩展业务逻辑的一些方法的集合(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点的定义.
- 连接点(JoinPoint):切入点方法集合中封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法.
- 通知(Advice):切面(Aspect)内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(例如@Around).
Spring中AOP快速入门
业务描述
在项目中定义一个日志切面,通过切面中的通知方法为目标业务对象做日志功能增强.
1. 添加AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. Spring 切面对象定义
在springboot工程中,切面对象需要使用@Aspect,关键代码如下:
package com.cy.pj.sys.service.aspect;
/**
* @Aspect 注解描述的类型为切面对象类型,此切面中可以定义多个切入点和通知方法.
*/
@Slf4j
@Aspect
@Component
public class SysLogAspect {
/**
* @Pointcut注解用于定义切入点
* bean("spring容器中bean的名字")这个表达式为切入点表达式定义的一种语法,
* 它描述的是某个bean或多个bean中所有方法的集合为切入点,这个形式的切入点
* 表达式的缺陷是不能精确到具体方法的.
*/
@Pointcut("bean(sysUserServiceImpl)")
public void doLog(){}//此方法只负责承载切入点的定义
/**
* @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
* 在当前业务中,此方法为日志通知方法
* @param joinPoint 连接点对象,此对象封装了要执行的切入点方法信息.
* 可以通过连接点对象调用目标方法,这里的ProceedingJoinPoint类型只能应用于@Around描述的方法参数中
* @return 目标方法的执行结果
* @throws Throwable
*/
@Around("doLog()")
public Object doAround(ProceedingJoinPoint jp)throws Throwable{
log.info("Start:{}",System.currentTimeMillis());
try {
Object result = jp.proceed();//执行目标方法(切点方法中的某个方法)
log.info("After:{}",System.currentTimeMillis());
return result;//目标业务方法的执行结果
}catch(Throwable e){
e.printStackTrace();
log.error("Exception:{}",System.currentTimeMillis());
throw e;
}
}
}
(1)注解:
① @Aspect
注解描述的类型为切面对象类型,此切面中可以定义多个切入点和通知方法
② @Pointcut
用于定义切入点
例: @Pointcut("bean(sysUserServiceImpl)")
缺点是不能精确到具体方法
切入点表达式,可分为两大类型:
- 粗粒度切入点表达式(不能精确到具体方法):
- bean(“bean的名字”) 重点
例:
bean(SysUserServiceImpl) //SysUserServiceImpl类中所有方法集合为切入点
bean(*ServiceImpl) //以ServiceImpl结尾所有方法集合为切入点
- within (“包名.类型”)
例:
within(com.pj.service.SysUserServiceImpl) //SysUserServiceImpl类中所有方法集合为切入点
within(com.pj.service.*) //com.pj.service包下所有类中的方法集合为切入点
within(com.pj.service..*) //com.pj.service包以及子包中所有类中方法的集合为切入点
- 细粒度切入点表达式
- execution(“返回值 类全名.方法名(参数列表)”)
例:
execution(int com.cy.pj.service.SysUserService.validById(Integer,Integer))
execution(* com.cy.pj.service..*.*(..))
- @annotation(“注解的类全名”) 重点
例:
@annotation(com.annotation.RequiredCache) //由RequiredCache注解描述的方法为缓存切入点方法
@annotation(com.annotation.RequiredLog) //由RequiredLog注解描述的方法为日志切入点方法
③ @Around 注解描述的方法,可以在切入点执行之前和之后进行业务拓展(优先级最高) 重点
例:
@Around("doTime()")
public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
try {
System.out.println("@Around.before");
Object result = joinPoint.proceed();
System.out.println("@Around.AfterReturning");
return result;
}catch(Exception e){
System.out.println("@Around.AfterThrowing");
e.printStackTrace();
throw e;
}finally {
System.out.println("@Around.after");
}
}
}
④ @Before
目标方法执行之前调用
⑤ @AfterReturning
目标方法正常结束时执行
⑥ @AfterThrowing
目标方法异常结束时执行
⑦ @After
目标方法结束时执行,正常结束和异常结束它都会执行
⑧ @Order
此注解描述切面优先级,数字越小优先级越高,默认优先级较低。(当项目中有多个切面时,并且对应着相同的切入点,假如切面中的通知方法的执行需要有严格顺序要求,此时我们需要设置切面的优先级。)
例:
@Order(2)
@Aspect
@Component
public class SysLogAspect{}
说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链
3. 启动项目进行访问测试,例如访问sysUserServiceImpl对象中的方法,检测其日志输出.
Spring 切面工作原理分析
当我们切面内部,切入点对应的目标业务方法执行时,底层会通过代理对象访问切面中的通知方法,进而通过通知方法为目标业务做功能增强.