AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
在日常业务当中,通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,而非核心业务一般是通过AOP方式切入到系统之中。
分为JDK和CGLIB两种代理机制
1)假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口)–兄弟关系。
2)假如目标对象(被代理对象)没有实现接口,则底层采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)–父子关系。springboot2默认使用的是CGLIB代理模式
1)切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
2)通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
3)连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
4)切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。
简单来说:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安全检查过程看成是通知。总之,概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。
假如我们要对ServiceImpl的某个方法进行拓展,在不修改原有的代码情况下,实现业务拓展的功能。操作步骤如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了AOP的一些语法,有一个专门的字节码生成器来生成遵守java规范的class文件。
将此日志切面类作为核心业务增强(一个横切面对象)类,用于输出业务执行时长日志,其关键代码如下:
package com.cy.aspect;
@Aspect //该注解用于标识或者描述AOP中的切面类型
@Slf4j
@Component
public class Aspect {
/**
* @Pointcut 定义切面中的切入点
* 1.bean做为一种切入点表达式类型
* 2.serviceImpl为容器中的一个bean的名字(类的首字母小写)
* 当该实现类中的任意方法执行时,都由本切面的通知对象做功能增强
*/
@Pointcut("bean(serviceImpl)")
public void doPointCut() {} //此方法不需要写代码,只是为了让切入点生效
@Around("doPointCut()") //环绕通知,标明该方法使用的切入点为bean(ServiceImpl)
public Object around(ProceedingJoinPoint jp) //ProceedingJoinPoint jp 为一个连接点对象,封装了要执行的方法的信息
throws Throwable{
try {
log.info("start:"+System.currentTimeMillis());
Object result=jp.proceed();//调用下一个切面方法或目标方法,并接收返回目标方法的执行结果
log.info("after:"+System.currentTimeMillis());
return result;
}
catch(Throwable e) {
log.error(e.getMessage());
throw e;
}
}
}
代码说明:
@Aspect 注解用于标识或者描述AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
@Pointcut注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的方式进行描述),在本案例中切入点表达式用的是bean表达式,这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的名字。
@Around注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和之后要执行的一个动作),@Aournd注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut注解描述的方法的方法名)。
ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。只能用于@Around注解描述的方法参数。
执行ServiceImpl任意方法,均可看到,切面方法已经生效,在前后添加了业务执行时长的日志。
在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:
@Component
@Aspect
public class SysTimeAspect {
@Pointcut("bean(xxxServiceImpl)")
public void doTime(){}
@Before("doTime()")
public void doBefore(JoinPoint jp){
System.out.println("time doBefore()");
}
@After("doTime()")
public void doAfter(){
System.out.println("time doAfter()");
}
/**核心业务正常结束时执行* 说明:假如有after,先执行after,再执行returning*/
@AfterReturning("doTime()")
public void doAfterReturning(){
System.out.println("time doAfterReturning");
}
/**核心业务出现异常时执行说明:假如有after,先执行after,再执行Throwing*/
@AfterThrowing("doTime()")
public void doAfterThrowing(){
System.out.println("time doAfterThrowing");
}
@Around("doTime()")
public Object doAround(ProceedingJoinPoint jp)
throws Throwable{
System.out.println("doAround.before");
try{
Object obj=jp.proceed();
System.out.println("doAround.after");
}catch(Throwable e){
System.out.println(e.getMessage());
throw e;
}
return obj;
}
}
bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
@annotation表达式(重点)
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。