浅浅浅浅的了解AOP

本文介绍了AOP(面向切面编程)的基础概念,包括切面、连接点、通知、切入点和目标对象。讲解了通知的五种类型,并详细阐述了切入点表达式execution、within、args及注解相关的匹配方式。最后通过简单的应用示例演示了如何在Spring Boot中使用AOP。

AOP相关的基础概念

AOP(面向切面编程),简单来说,就是将一些公共或者复用的代码抽离出来,在需要这些公共代码的地方,将这些代码“织入”进去。

1.切面(Aspect)

切入点和通知结合起来就是切面,类似Java里面的一个类声明。

2.连接点(Join Point)

程序执行过程中某个特定的点,通常是一个方法的执行或者异常处理的时候。

3.通知(Advice)

在切面的连接点上执行的动作,包括“before”、“after”、“around”等通知类型。

通知类型

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕通知(Around advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

4.切入点(Point Cut)

说明在哪些地方(方法)执行,一般通过通配、正则表达式来表示,通知和切入点表达式关联,可知道在满足切入点的连接点上运行。

5.目标对象(Target Object)

被一个或多个切面所通知的对象,也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

切入点表达式

1.execution

execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下:

  • modifiers-pattern:方法的可见性,public、protected等;
  • ret-type-pattern:方法返回的类型,void、String等;
  • declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
  • name-pattern:方法名类型,如buisinessService();
  • param-pattern:参数类型,如java.lang.String等;
  • throws-pattern:抛出的异常类型,如java.lang.Exception。

如下是一个使用execution表达式的例子:

execution(public * com.demo.Aspect.addLog(java.lang.String,..))

上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.demo.Aspect类中名称为addLog的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。上述示例中我们使用了…通配符,关于通配符的类型,主要有两种:

  • *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
    如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:
execution(* com.spring.service.BusinessObject.*())

下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:

execution(* com.spring.service.Business*.*())
  • … (两个点)通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
    如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:
execution(* com.spring.service..*.businessService ())

这里需要说明的是,包路径service…*.businessService()中的…应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的 *.businessService(),这里的 *表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。
如下示例是使用…表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由…进行匹配:

execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

2.within

within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。如下是within表达式的语法:

within(declaring-type-pattern)

within表达式路径和类名都可以使用通配符进行匹配,比如如下表达式将匹配com.spring.service包下的所有类,不包括子包中的类:

within(com.spring.service.*)

如下表达式表示匹配com.spring.service包及子包下的所有类:

within(com.spring.servic..*)

3.args

args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。如下是args表达式的语法:

args(param-pattern)

如下示例表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法:

args(java.lang.String)

也可以使用通配符,但这里通配符只能使用…,而不能使用*。如下是使用通配符的实例,该切点表达式将匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法:

args(java.lang.String,..,java.lang.Integer)

4.@within、@annotation、@args

@within@annotation分别表示匹配使用指定注解标注的类和标注的方法将会被匹配,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。

简单应用

首先导入jar包

//pom.xml
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

demo 1

使用@annotation

//注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoleAnnotation {
    String value() default "";
}
//切面类
@Aspect
@Component
public class AuthAspect {

    @Pointcut("@annotation(com.example.demo.annotation.RoleAnnotation)")
    public void pointcut(){

    }
    @Before(value = "pointcut()")
    public void auth() {
        System.out.println("==============权限校验");
    }
    @AfterReturning(value = "pointcut()")
    public void after() {
        System.out.println("=========after通知");
    }
}
//目标类
@RestController
public class LoginController {
    @GetMapping("/hello")
    @RoleAnnotation
    public void auth() {
        System.out.println("========登录");
    }
}

结果:
运行结果

demo 2

使用execution

//切面类
@Aspect
@Component
public class AuthAspect {
	//方法1 定义切入点
    @Pointcut("execution(public * com.example.demo.controller.LoginController.auth())")
    public  void logAspect(){}

    @Before("logAspect()")
    public void auth() {
        System.out.println("==============权限校验");
    }
    //方法2 直接在通知上定义切入点
    @AfterReturning("execution(public * com.example.demo.controller.LoginController.auth())")
    public void after() {
        System.out.println("=========after通知");
    }
}

demo 3

多个通知一起使用

//切面类
@Aspect
@Component
public class AuthAspect {

    @Pointcut("execution(public * com.example.demo.controller.LoginController.auth())")
    public  void logAspect(){}

    @Before("logAspect()")
    public void auth() {
        System.out.println("==============before通知");
    }
    /**
     * 后置返回通知
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行
     * 
     */
    @AfterReturning("execution(public * com.example.demo.controller.LoginController.auth())")
    public void afterReturning() {
        System.out.println("=========AfterReturning通知");
    }

    @Around(value = "logAspect()")
    public Object around(ProceedingJoinPoint proceed) throws Throwable {
        System.out.println("===========环绕通知1");
        Object result = proceed.proceed();
        System.out.println("===========环绕通知2");
        return result;
    }

    @After(value = "logAspect()")
    public void after() {
        System.out.println("============after通知");
    }
}
//目标类
@RestController
public class LoginController {
    @GetMapping("/hello")
    public void auth() {
        System.out.println("========登录");
    }
}

运行结果:
运行结果
简单的了解就到这了!

参考链接

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值