一、简介
面向切面编程,刨解开封装的对象内部,将影响多个类的公共行为封装到一个可用模块,即为切面。将系统服务与业务分离,减少重复代码,降低模块间的耦合度,方便后期维护和操作。
AOP运用的主要场景有:权限控制、错误处理、缓存、调试等
二、核心概念
- Aspect 切面:由pointcount和advice组成,包含连接点和横向逻辑的定义,通过 pointcut 和 advice 定位到特定的 joinpoint ,在 advice 中编写切面代码.
- advice 通知:将 aspect 添加到特定的 join point 的一段代码.指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕
- join point 连接点:程序运行中的一些节点,例如某方法的执行
- point cut 切点:Advice与point cut 关联, point cut 匹配 join point
- Target 目标对象:执行 advice 的目标对象,注:指的不是原来的类, 而是织入 advice 所产生的代理类, 默认使用标准的 JDK 动态代理
- introduction 引入:为一个类型添加额外的方法或字段
- AOP proxy 代理类:类被 AOP 织入 advice, 就会产生结果类, 它是融合了原类和增强逻辑的代理类
- Weaving 织入:将 aspect 和其他对象连接起来, 并创建 adviced object 的过程.
三、实现
1、定义 aspect 切面
@Aspect 标注 Bean 后,被Spring 框架 添加到 Spring AOP 中
@Aspect // 切面
@Component // Bean
public class SysLogAspect {
}
2、声明 pointcut 切点
// 定义切点为 被SysLog注解 的连接点
@Pointcut("@annotation(com.example.demo.study.SysLog)")
public void logPointCut() {
}
/** 匹配类型 **/
// 匹配指定包中的所有的方法
// execution(* com.example.service.*(..))
// 匹配当前包中的指定类的所有方法
// execution(* UserService.*(..))
// 匹配指定包中的所有 public 方法
// execution(public * com.example.service.*(..))
// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
// execution(public int com.example.service.*(..))
// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
// execution(public int com.example.service.*(String name, ..))
/** 匹配方法 **/
// 匹配指定包中的所有的方法, 但不包括子包
// within(com.example.service.*)
// 匹配指定包中的所有的方法, 包括子包
// within(com.example.service..*)
// 匹配当前包中的指定类中的方法
// within(UserService)
// 匹配一个接口的所有实现类中的实现的方法
// within(UserDao+)
/** 匹配Bean名字 **/
// 匹配以指定名字结尾的 Bean 中的所有方法
// bean(*Service)
/** 被注解标注的方法 **/
// @annotation(com.example.demo.study.SysLog)
/** 表达式组合 **/
// 匹配以 Service 或 ServiceImpl 结尾的 bean
// bean(*Service || *ServiceImpl)
// 匹配名字以 Service 结尾, 并且在包 com.example.service 中的 bean
// bean(*Service) && within(com.example.service.*)
3、声明 advice
// 定义切点前的 Advice
@Before(value = "logPointCut()")
public void beforeSyeLog() {
System.out.println("这是切点前");
// 逻辑
}
// 定义切点后的 Advice
@AfterReturning(pointcut = "logPointCut()", returning = "response")
public void saveSysLog(JoinPoint joinPoint, Object response) {
if (dataSource == null) {
log.error("未找到DataSource,无法记录操作日志。");
return;
}
// 利用反射获取
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取请求类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求方法名
String methodName = method.getName();
methodName = className + "." + methodName;
String operation = null;
// 获取注解信息
SysLog sysLog = method.getAnnotation(SysLog.class);
if (sysLog != null) {
operation = "[" + sysLog.module() + "]" + sysLog.operation();
}
// 参数信息
StringJoiner sjArgsInfo = new StringJoiner(",");
Parameter[] parameters = method.getParameters();
Object[] args = joinPoint.getArgs();
outer:
for (int i = 0; i < args.length; i++) {
try {
String paramName = parameters[i].getName();
if (sysLog != null) {
String[] ignoreParameters = sysLog.ignoreParameters();
if (ignoreParameters.length > 0) {
for (String ignore : ignoreParameters) {
if (paramName.equals(ignore)) {
continue outer;
}
}
}
}
String argInfo = null;
if (sysLog != null && "toString".equalsIgnoreCase(sysLog.serializationType())) {
argInfo = args[i].toString();
} else {
argInfo = mapper.writeValueAsString(args[i]);
}
sjArgsInfo.add(paramName + ":" + argInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
// return info
String returnInfo = null;
if (sysLog != null && !sysLog.ignoreResponse()) {
try {
if (sysLog != null && "toString".equalsIgnoreCase(sysLog.serializationType())) {
returnInfo = response.toString();
} else {
returnInfo = mapper.writeValueAsString(response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 定义切点周围的 Advice
@Around(value = "logPointCut()")
public Object aroundSyeLog(ProceedingJoinPoint pjp) {
System.out.println("这是切点前");
// 逻辑
return null;
}