Spring AOP

基本概念

简介

AOP(Aspect-Oriented Programming) 面向切面编程,它是OOP(Object-Oriented Programming)的补充,在OOP中是以class为基础模块,而AOP的基础模块是aspect

AOP概念
  • Aspect:切面,就横切关注点的抽象。由pointcut(切入点)和advice(通知)组成
  • Join point:连接点,程序执行过程中某些特定的点
  • Advice:通知,是指在Join point(连接点)上执行的动作
  • Pointcut:切入点,用来匹配Join point(连接点)的,在Spring中所有的方法都可以认为是join point,而pointcut就是通过一定的规则来匹配join point,对匹配上的方法进行增强操作
  • Introduction:引入,为类型声明其他方法或字段。Spring AOP允许向任何建议的对象引入新的接口(以及相应的实现)。例如,您可以让bean实现IsModified接口,从而简化缓存。
  • Target object:目标对象,指pointcut(切入点)匹配上,需要进行通知的对象。需要注意的是,目标对象不是原对象,而是被织入advice后产生的代理对象
  • AOP proxy:AOP代理,类被AOP织入advice后会产生一个新类,这个类是结合了原逻辑与增加逻辑的代理类。AOP代理是JDK动态代理或者CGLIB代理
  • Weaving:织入,将切面(Aspect)与其他对象连接起来,形成新的代理对象的过程。
通知(Advice)类型:
  • 前置通知(Before advice):在连接点(join point)之前执行的通知,但这个通知不能阻止连接点执行之前的流程(除非抛出异常)
  • 后置通知(After returning advice):在连接点(join point)正常执行返回后执行的通知
  • 异常通知(After throwing advice):方法抛出异常退出时执行的通知
  • 最终通知(After (finally) advice):连接点(join point)执行完成退出时执行的通知,无论正常或异常执行都会执行的通知
  • 环绕通知(Around Advice):在连接点(join point)执行前和退出后都会执行的通知,这是最常用的通知

AOP的概念比较多,也比较复杂,理解起来也相对较麻烦。其实,我们只需要搞清楚几个角色,以及他们之间的关系就比较好理解了。我们知道,AOP是用来处理面向切面的编程,主要用于一些横切关注点的处理,这样描述还是比较抽象,不太好理解。我们知道AOP一个典型的使用场景就是日志的处理,按OOP的模式来处理日志,就是每个用到日志的类都要加日志的逻辑,而日志的处理逻辑都是相同的,这样会导致逻辑大量重复,而使用AOP就可以很好的解决这个问题,那么问题来了,AOP要如何解决这个问题?假如你来做这件事,会有哪些事需要做?

  • 第一步,我们要定义日志记录要处理哪些事?也就是日志处理的逻辑,这就是AOP里面通知(Advice)要做的事
  • 第二步,需要考虑哪些地方可以使用这些日志处理逻辑,也就是AOP中的连接点(join point),就是通知可以使用的范围(注意不是通知真正使用的范围)
  • 第三步,日志逻辑要使用到哪些地方,就是AOP中的切入点(point cut),这才是通知真正使用的点,它其实是在连接点(join point)的范围内限定一个使用范围,将通知使用到限定的点(方法)上
  • 最后,切面(Aspect)就是切入点(point cut)和通知(advice)共同组成的被称为横切关注点的抽象

实践

@AspectJ 支持

@AspectJ 是使用Java注解实现的AOP编码风格,Spring也支持@AspectJ的编码风格。
开启@AspectJ可以使用Java Configuration方式:

@Configuration
@EnableAspectJAutoProxy
public class Config{
}

或者XML方式:

<aop:aspectj-autoproxy/>
使用Spring AOP,需要做哪些事呢?

在这里插入图片描述

1.声明切面

@Component
@Aspect
public class MyAspect {
}

注意,当一个类被标注为Aspect后,此类就不能作为其他的Aspect的通知对象了,因为使用@Aspect会被排除在auto-proxying 机制之外

2.声明pointcut

  @Pointcut("execution(* com.proxy.aop.UserService.*(..))")
  public void testPointCut(){

  }

声明一个point需要做两件事

  • 声明一个方法
  • 标注切入点标示符(pointcut signature)

上面简单定义了一个pointcut,表示匹配com.proxy.aop.UserService类下所有的方法的执行
类似“execution(* com.proxy.aop.UserService.*(…))”这样的表达式被称为切入点表达式,用来描述通知需要执行的范围

切点标志符(designator)
切点表达式由标志符(designator)和操作参数组成,类似“execution( greetTo(…))”这样的表达式,execution是标志符, 而圆括号里的 greetTo(…) 就是操作参数

execution
匹配连接点的执行, 例如 “execution(* show(…))” 表示匹配所有目标类中的 show() 方法. 这个是最基本的 pointcut 标志符

within
匹配特定包下的所有连接点, 例如 within(com.aop.demo.*) 表示 com.aop.demo 包中的所有连接点, 即包中的所有类的所有方法. 而 within(com.aop.demo.*Mapper) 表示在 com.aop.demo 包中所有以 Mapper结尾的类的所有的连接点

this 与 target
this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of).

bean
匹配 bean 名字为指定值的 bean 下的所有方法

bean(*Mapper) // 匹配名字后缀为 Mapper的 bean 下的所有方法
bean(custService) // 匹配名字为 custService的 bean 下的所有方法

args
匹配参数满足要求的方法

// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}

@annotation
匹配由指定注解所标注的方法

@Pointcut("@annotation(com.aop.demo.checkConfig)") //匹配由注解 checkConfig所标注的方法
public void pointcut() {
}

常见的pointcut表达式

匹配方法签名

// 匹配指定包中的所有的方法
execution(* com.aop.demo.*(..))

// 匹配当前包中的指定类的所有方法
execution(* AppService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.aop.demo.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.aop.demo.*(..))

// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.aop.demo.*(String name, ..))

匹配类型签名

// 匹配指定包中的所有的方法, 但不包括子包
within(com.aop.demo.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.aop.demo..*)

// 匹配当前包中的指定类中的方法
within(UserService)


// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)

匹配Bean名称

// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)

组合匹配

// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)

3.声明advice

advice 是和一个 pointcut 表达式关联在一起的, 表示在匹配的 join point 的方法执行的前/后/周围 运行. pointcut 表达式可以是简单的一个 pointcut 名字的引用, 或者是完整的 pointcut 表达式.

  @Pointcut("execution(* com.proxy.aop.*(..))")
  public void testPointCut(){

  }

  @Before(value = "testPointCut()")
  public void doSomething() {

  }

也可以,pointcut和advice一起定义

  @Before("execution(* com.proxy.aop.*(..))")
  public void doSomething() {

  }

环绕通知
环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行
环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)

  @Around("execution(* com.proxy.aop.*(..))")
  public Object  doSomething(ProceedingJoinPoint pjp) throws Throwable {
    Object retVal = pjp.proceed();
    return retVal;
  }

其他类型的通知跟@Before的定义差不多

参考:
https://segmentfault.com/a/1190000007469968

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值