Aop- 面向切面编程-->通俗讲 对业务方法做一些增强(比如日志输出,事务控制,异常的处理等。。)
举个例子:
比如想看下接口的业务执行时间,可能第一会想到在最开始和最结束的地方统计时间,算出时间差
如果统计所有接口,那就很没必要,其实这个和业务本身是没关系的,应该抽离出去,这时aop就派上用场了
它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用
aop一些术语(其他地方搬过来的,方便理解)
1.通知(Advice)
就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
2.连接点(JoinPoint)
这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
3.切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4.切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入(introduction)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
6.目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7.代理(proxy)
怎么实现整套aop机制的,都是通过代理,这个一会给细说。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
关键就是:切点定义了哪些连接点会得到通知
一些aop相关介绍
AspectJ指示器
当我们查看这些Spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器
execution 表达式,还有其他不同的写法---->可以百度哦
1)通过方法签名定义切点
execution(public * * (..)) 匹配所有目标类的public方法,第一个 * 代表返回类型,第二个 * 代表方法名,而..代表任意入参的方法;
execution(* *To(..))l 匹配目标类所有以To为后缀的方法。第一个 * 代表返回类型,而 * To代表任意以To为后缀的方法;
2)通过类定义切点
execution( * com.qqqq.test. * (..))l第一个 * 代表返回任意类型,com.qqqq.test. * 第二个 * 代表Test接口中的所有方法;
3)通过类包定义切点
注解 作用
-
@Aspect 把当前类标识为一个切面
-
@Pointcut
Pointcut是织入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 -
@Around 环绕增强,目标方法执行前后分别执行一些代码
-
@AfterReturning 返回增强,目标方法正常执行完毕时执行
-
@Before 前置增强,目标方法执行之前执行
-
@AfterThrowing 异常抛出增强,目标方法发生异常的时候执行
-
@After 后置增强,不管是抛出异常或者正常退出都会执行
JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
常用API
方法名 功能
Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs(); 获取传入目标方法的参数对象
Object getTarget(); 获取被代理的对象
Object getThis(); 获取代理对象
ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
添加了以下两个方法。
Object proceed() throws Throwable;////执行目标方法
Object proceed(Object[] var1) throws Throwable;//传入的新的参数去执行目标方法
直接上代码操作
定义一个切面类
@Component
@Aspect
@Slf4j
public class ControllerAop {
}
定义一个方法,声明切入点表达式,其他的增强方法可以调用这个方法
@Pointcut("execution(* com.cheng.testone.service.*.*(..))")
public void PointcutDeclaration() {}
完整代码
@Component
@Aspect
@Slf4j
public class ControllerAop {
/**
*
*/
@Pointcut("execution(* com.cheng.testone.service.*.*(..))")
public void PointcutDeclaration() {}
//前置通知,方法执行之前执行
@Before("PointcutDeclaration()")
public void testBefore(JoinPoint joinPoint){
log.info("Before===========>测试方法执行前");
Object[] args = joinPoint.getArgs();
log.info("入参:"+Arrays.toString(args));
log.info("目标方法名为:" + joinPoint.getSignature().getName());
log.info("目标方法所属类的简单类名:" +joinPoint.getSignature().getDeclaringType().getSimpleName());
log.info("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
log.info("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
}
//后置通知,方法执行之后执行(不管是否发生异常)
@After("PointcutDeclaration()")
public void AfterMethod(JoinPoint jp) {
log.info("After==============>>方法执行后");
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("AfterMethod The method "+ methodName +" parameter is "+ Arrays.asList(args));
}
//返回通知,方法正常执行完毕之后执行
@AfterReturning(value="PointcutDeclaration()",returning="result")
public void AfterReturningMethod(JoinPoint jp,Object result) {
log.info("AfterReturning=============返回通知");
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("AfterReturningMethod The method "+ methodName +" parameter is "+Arrays.asList(args)+"return-> "+result);
}
//异常通知,在方法抛出异常之后执行
@AfterThrowing(value="PointcutDeclaration()",throwing="e")
public void AfterThrowingMethod(JoinPoint jp,Exception e) {
log.info("AfterThrowing==============>>异常通知");
String methodName = jp.getSignature().getName();
System.out.println("AfterThrowingMethod The method "+ methodName +"exception :"+e);
}
/**
* 环绕通知
* ProceedingJoinPoint对象是JoinPoint的子接口,该对象用在@Around的切面方法中,常用有以下两个方法
* @param pjp
* @return
*/
@Around("execution(public com.cheng.testone.model.entity.ResultBean *(..))")
public Object handlerResultBeanControllerMethod(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
ResultBean<?> result = null;
try {
log.info("【环绕增强中的--->前置增强】");
StringJoiner argsBuf = new StringJoiner(",");
Object[] args = pjp.getArgs();
Arrays.stream(args).filter(Objects::nonNull).forEach(arg -> argsBuf.add(arg.toString()));
log.info(pjp.getSignature() + "request param :" + argsBuf.toString());
//执行目标方法
result = (ResultBean)pjp.proceed();
log.info("【环绕增强中的--->返回增强】");
log.info(pjp.getSignature() + "use time:" + (System.currentTimeMillis() - startTime) + "ms");
} catch (Throwable e) {
//异常通知
result = handlerResultBeanException(pjp, e);
log.info("【环绕增强中的--->异常增强】");
}
log.info("【环绕增强中的--->后置增强】");
return result;
}
private ResultBean<?> handlerResultBeanException(ProceedingJoinPoint pjp, Throwable e) {
ResultBean<?> result = new ResultBean();
if (e instanceof ServiceException) {
log.error(pjp.getSignature() + " error {}",e.getMessage());
result.setMsg(((ServiceException) e).getMsg());
result.setCode(((ServiceException)e).getCode());
} else {
log.error(pjp.getSignature() + " error ", e);
result.setMsg("系统异常");
result.setCode(300);
}
return result;
}
}
网友评论