- AOP概述
AOP即面向切面编程, 是对OOP的补充,OOP从纵向方向切入对象,比如接口,继承,抽象,多态等,但是它无法满足我们日常的一些重复代码的一个抽取,比如我们要实现一个日志功能,但是日志和业务是不相干的,如果在每个业务中添加日志输出,那我们不得不重新写很多重复的代码放进业务层代码中,这样很不友好还很费精力和时间,但是AOP很好解决了这些问题,它可以从横向切入对象,将多个对象中重复的代码抽取出来作为一个切面,用这个切面从三个层面(前置,环绕,后置)去代理这些业务层代码,然后就可以对这些重复代码去填充代理对象,这样就减少了代码的重复,也将这些和业务没有关系的代码从业务层中抽离出来,提高了业务层代码的可读性。 - 关于AOP几个关键点
- Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(被切入点):是指具体要实现前面所说的例如打印日志,数据库事务管理的被切入的点。也就是通过 AOP 将切面功能动态加入进去的程序位置。在 Spring AOP 里面这个指的都是某个方法
- Pointcut(切点):。用来指明如何通过规则匹配 Joint point。这个规则是一个表达式。在 Spring 中,默认使用的是 AspectJ 的 pointcut 表达式语言
- Advice(通知):指明在一个切入点的不同位置上采取的动作。例如对于一个数据库访问事务管理来说,在进入方法后要开启事务,在方法结束前要提交事务,在发生错误的时候要回滚事务。这属于三个不同的 Advice,要分别进行实现。Advice 通常和具体的 Pointcut 关联在一起。
- AOP proxy(AOP 代理):用来实现将 Advice 功能动态加入到 Pointcut 的方法。在 Spring 的 AOP 中采用动态代理和 CGLIB 代理的方式来实现。而 AspectJ 则采用了特定编译器侵入字节码的方式来实现。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
- @poIncut 切点
这个注解包含两部分,PointCut表达式和PointCut签名。
execution | 对于匹配方法执行连接点,这是您在使用Spring AOP时将使用的主要切入点设计器 |
within | 将匹配限制为特定类型中的连接点(当使用Spring AOP时,只需执行在匹配类型中声明的方法) |
this | 将匹配限制为连接点(使用Spring AOP时方法的执行),其中bean引用(Spring AOP代理)是给定类型的实例 |
target | 限制与连接点的匹配(使用Spring AOP时方法的执行),其中目标对象(代理的应用程序对象)是给定类型的实例 |
@target | 将匹配限制为连接点(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注释 |
@args | 限制连接点的匹配(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释 |
@within | 匹配具有给定注释的类型中的连接点的限制(使用Spring AOP时,使用给定注释在类型中声明的方法的执行) |
@annotation | 将匹配限制为连接点的主题(在Spring AOP中执行的方法)具有给定注释的连接点 |
- 通知类型
前置通知 | 方法执行之前执行响应AOP代理的方法 | @Before() |
环绕通知 | 方法执行过程中执行响应AOP代理的方法 | @Around() 注意环绕通知的参数是ProceedingJoinPoint JoinPoint的子类 |
后置通知 | 方法执行过程中执行响应AOP代理的方法(注意后置通知又分为成功后置通知即方法成功执行完成执行响应的AOP后置成功方法,和异常后置通知即方法在执行过程中出现异常执行AOP响应的异常通知方法) | @After() 可以细分为 @AfterThrowing和AfterReturning |
- demo
aspect类
package com.skindow.logAop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;
/**
* Created by Administrator on 2019/8/8.
* */
@Component
@Aspect
@Slf4j
public class LogAspect {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Pointcut("execution(public * com.skindow.controller..*.*(..))")//切入点描述 这个是controller包的切入点
public void webLog() {//签名,可以理解成这个切入点的一个名称
}
@Around("webLog()") //环绕通知,执行webLog方法
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
Date startDate = new Date();
//返回目标方法的签名
Signature signature = joinPoint.getSignature();
//返回目标方法所有的参数
Object[] args = joinPoint.getArgs();
log.info("signature.toString()" + signature.toString());
log.info("{} 调用时间:{}", signature, sdf.format(startDate));
long start = System.currentTimeMillis();
//用改变后的参数执行目标方法
Object object = joinPoint.proceed();
//目标执行完成并记录结束时间
long end = System.currentTimeMillis();
String time = this.formatExecuteTime(end - start);
log.info("{} 执行时间:{}", signature.toString(), time);
(new Thread(() -> {
log.info("{} 方法入参{}", signature.toString(),new ArrayList<>(Arrays.asList(args)).stream().map(a -> a.toString()).collect(Collectors.joining(",")));
})).start();
return object;
}
/**通过毫秒计算出时分秒并输出xxmxxsxxms的格式字符
* @param executeTime
* @return
*/
private String formatExecuteTime(long executeTime) {
long min = executeTime % 3600000L / 60000L;
long sec = executeTime % 60000L / 1000L;
long msec = executeTime % 10000L;
StringBuilder sb = new StringBuilder();
if(min > 0L) {
sb.append(min).append("m ");
}
if(sec > 0L) {
sb.append(sec).append("s ");
}
sb.append(msec).append("ms");
return sb.toString();
}
}
测试结果如下:
OK 成功了