Spring AOP面向切面编程
一、AOP面向切面编程基本概念
在软件系统中,有些行为对于绝大多数业务模块都是通用,例如日志管理、权限管理、事物管理等,在不同的业务模块中,我们很有可能会编写相同的代码来完成一个共同的功能点,这就造成了极大的代码冗余,和较高的维护成本,同时业务代码与特殊功能代码耦合交融到了一起,不利于系统的构建与开发。
AOP:Aspect Oriented Programming ,面向切面编程,就是将众多的业务模块进行横向切割,提炼抽取其中的相同功能代码进行单独编程,已达到业务代码与非业务功能代码的隔离,从而使得业务逻辑部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
AOP最重要的功能:高内聚,低耦合
二、Spring AOP的基本概念
AOP把软件系统分成两个部分:核心关注点和横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点,描述横切关注点的具体内容称之为切面。
描述切面的常用概念是:通知(Advice)、切点(Pointcut)、连接点(JoinPoint)、目标对象(Target)、织入(Weaving)、代理(Proxy)、切面(aspect)
- 通知:定义了切面执行的代码内容是什么,在什么时候执行。
- 切点:一个或多个连接点的集合,通常使用具体的类名和方法名来指定这些切点,或是使用正则表达式来定义匹配的类和方法来指定这些切点。
- 连接点:在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、修改一个字段时,切面代码可以利用连接点插入到应用的正常流程之中,并执行切面代码。
- 目标对象:被代理对象。
- 织入:将通知应用到切入点的过程。
- 代理:将通知织入到目标对象之后,形成代理对象。
- 切面:切面是通知和切点的结合,通知和切点共同定义了关于切面的全部内容,切面内容是什么,在什么时候执行,在什么地方执行。(切点+通知)。
Spring中实现AOP原理:
-
动态代理:被代理对象必须实现接口才能实现代理对象,如果没有接口将不能使用动态代理技术。
-
cglib代理:第三方的代理技术,可以对任何类生成代理,代理的原理是对目标对象进行继承代理,如果目标被对象final修饰,那么该类无法被cglib代理。
-
如果有接口优先使用动态代理。
Spring AOP中定义了五种类型的通知:
通知类型 | 描述 |
Before | 再切点方法执行之前,调用通知中指定的方法。 |
After | 再切点方法执行完成之后,调用通知中指定的方法,不管切点方法执行是否成功。 |
After-returning | 再切点方法成功执行之后,调用通知中指定的方法。 |
After-throwing | 再切点方法执行抛出异常后,调用通知中指定的方法。 |
Around | 通知方法包裹切点方法执行,改变切点方法中的执行逻辑 |
三、Spring AOP编程
1. 通过标签方式进行切面编程
<!--切面配置标签块-->
<aop:config>
<!--切点定义,使用正则表达式来匹配连接点集合,通过id来命令切点-->
<aop:pointcut expression="execution(* com.yingcai.service.*.*(..))" id="pointcut"/>
<!--通知配置标签块,ref属性定义通知执行Bean-->
<aop:aspect ref="adviceInfo">
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturnAdvice" pointcut-ref="pointcut"/>
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
环绕通知详解:
通过ProceedingJoinPoin类来获取操作连接点切入方法的相关信息
获取切入方法执行类名称:
Class executeClass = proceedingJoinPoint.getTarget().getClass();
String className = executeClass.getName();
获取切入方法名称:
String methodName = proceedingJoinPoint.getSignature().getName();
获取切入方法入参:
Object[] args = proceedingJoinPoint.getArgs();
执行切入方法内容:
proceedingJoinPoint.proceed(args);
注:环绕通知的方法格式必须与连接点方法格式相同,连接点方法有返参,环绕通知方法也必须有返参。
实例:
/**
* 在业务方法的前后做处理
*
* @param joinPoint 是具体业务方法被封装的对象
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.debug("enter=" + className + "." + method);
logger.debug("args=" + Arrays.toString(args));
try {
Result result = (Result) joinPoint.proceed(args);
logger.debug("result=" + result);
return result;
} catch (Throwable throwable) {
logger.error("occur exception:", throwable);
return new Result(null, "proceed failure");
}
}
业务方法:
@Service
public class CollegeService implements IBaseService<College>{
private Logger logger = LoggerFactory.getLogger(this.getClass());
public Result add(College college) {
// int num = 1/0;
return new Result(true,"增加成功");
}
}
输出日志:
2. 使用注解方式进行切面编程
切面注解的开启:
<aop:aspectj-autoproxy/>
-
@Aspect:标注于类上,将一个类定义为切面类。
-
@Pointcut:标注于一个普通方法上,定义切点内容。
-
@Before:标注于前置通知方法之上。
-
@After:标注于后置方法之上。
-
@AfterThrowing:标注于后置失败通知方法之上。
-
@AfterReturning:标注于后置成功通知方法之上。
-
@Around:标注于环绕通知方法之上。
实例:
/**
* copyright(c)2021 zbh.ALL rights Reserved
* <p>
* 描述:切面类
*
* @author zbh
* @version 1.0
* @date 2021/7/26
*/
@Component
@Aspect
public class LogAop {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.ychs.service.*.*(..))")
public void pc() {}
/**
* 在业务方法执行前打印日志
*/
// @Before(value = "execution(* com.ychs.service.*.*(..))")
@Before("pc()")
public void before() {
logger.debug("service method before...");
}
@After("pc()")
public void after() {
logger.debug("service method after...");
}
@AfterReturning("pc()")
public void afterReturn() {
logger.debug("service method afterReturn");
}
@AfterThrowing("pc()")
public void afterThrowing() {
logger.debug("service method afterThrowing");
}
/**
* 在业务方法的前后做处理
*
* @param joinPoint 是具体业务方法被封装的对象
* @return
*/
@Around("pc()")
public Object around(ProceedingJoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName();
String method = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.debug("enter=" + className + "." + method);
logger.debug("args=" + Arrays.toString(args));
try {
Result result = (Result) joinPoint.proceed(args);
logger.debug("result=" + result);
return result;
} catch (Throwable throwable) {
logger.error("occur exception:", throwable);
return new Result(null, "proceed failure");
}
}
}