Spring AOP用法

参考:

spring aop中pointcut表达式完整版 - 知乎

我用一张图彻底了解 SpringAOP 切面表达式陈浩的博客-CSDN博客切面表达式

原来理解 AOP 可以这么简单! - 腾讯云开发者社区-腾讯云

AOP参数详解_码农小胖哥的技术博客_51CTO博客

Spring AOP(切面编程)

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。AOP的目的是实现关注点的分离;

==切点只能是spring bean的方法。==

术语、概念

通知(advice)

AOP框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理

连接点(join point)

连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用,异常的抛出。在Spring AOP中,连接点总是方法的调用。

切点(PointCut)

可以插入增强处理的连接点。

切面(Aspect)

切面是通知和切点的结合。

引入(Introduction)

引入允许我们向现有的类添加新的方法或者属性。

织入(Weaving)

将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入

9种切点表达式(@Pointcut)

说明

不重要的说明:

引用其他命名切入点,只有@ApectJ风格(注解)支持,Schema风格(XML配置)不支持,本文展示均为@ApectJ风格。

切点表达式简单解析

合并切入点表达式:

切入点表达式可以使用&&||!来合并.还可以通过名字来指向切入点表达式。【也可以用于类型和参数中】

类型匹配模式:

1:*:匹配任意类型的单个字符;比如模式 (*,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型

2:..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数,可以是零到多个。

3:+:匹配指定类型及子类型(包含当前类型);仅能作为后缀放在类型后边。

万能:

execution是最灵活最常用的切点表达式。所有的表达式都可以用execution表达式表示

指定类

within=>this=>target

相同点:都是指定至类的。

不同点:

within,路径可使用通配符,可指定接口,可指定类。

this,路径不可使用通配符!可指定接口,不可指定类!

target,路径不可使用通配符!不可指定接口!可指定类。

指定参数类型

args,匹配当前执行的方法传入的参数为指定类型的执行方法(是方法==传入==的参数类型,不是方法==声明==的参数类型)。

参数类型的路径不可使用通配符。

args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用

跟注解有关的

@annotation=>@target=>@within=>@args

@args匹配方法有持有某注解参数。依然是动态切入点不建议使用

@annotation匹配持有某注解的方法。

@within@target

相同点:都是匹配所有持有指定注解类型内的方法。

不同点:

@within,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

@target,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
(测试中,用@target会报错,大概意思是aop太宽泛不可以使用)

具体区别可见(02-05 AOP学习之@within和@target使用示例及对比分析 - 简书)

bean匹配bean名。

execution

匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指定者。

@注解只能使用全限定名,不能模糊使用..,例如:@java.lang.Deprecated * *(..)可以,@java..Deprecated * *(..)不行

// 匹配该包下的所有类的所有方法(不包含子包)
@Pointcut("execution(* com.example.demo.util.*.*(..))")
// 匹配该包及子包下所有类的所有方法
@Pointcut("execution(* com.example.demo.util..*.*(..))")
// 匹配该包下public修饰的所有参数为(Integer,Integer)的方法
@Pointcut("execution(public * com.example.demo.util.*.*(Integer,Integer))")
// 匹配该包及子包下所有类的所有a开头的方法
@Pointcut("execution(* com.example.demo.util..a*(..))")
// 匹配该包下IDataServer接口中的任何方法
@Pointcut("execution(* com.example.demo.service.IDataServer.*(..))")
// 匹配该包及子包下IDataServer接口中的任何方法(可用于接口,所有调用接口的方法都可以被织入)
@Pointcut("execution(* com.example.demo..IDataServer.*(..))")
// 与或非示例,可用于各种位置
@Pointcut("execution(void||Integer com.example.demo..IDataServer.*())")
// 组合条件示例:IDataServer里的所有void方法 并且 com及其子包下IDataServer的add方法
@Pointcut("execution(void com.example.demo.service.IDataServer.*(..)) && execution(* com..IDataServer.add(..))")
// 匹配该包及子包下IDataServer接口及子类型的的任何方法
@Pointcut("execution(* com.example.demo..DataImpl+.*(..))")// + 包含当前类型
@Pointcut("execution(* com.example.demo..IDataServer+.*(..))")

/*支持注解*/
// 任何持有AopAnno注解的方法
@Pointcut("execution(@com.example.demo.aop.AopAnno * *(..))")
// 任何持有AopAnno注解 和 Deprecated注解的方法(并且的关系)
@Pointcut("execution(@com.example.demo.aop.AopAnno @java.lang.Deprecated * *(..))")
// 任何持有AopAnno注解 或 Deprecated注解的方法(或者的关系)
@Pointcut("execution(@(com.example.demo.aop.AopAnno || java.lang.Deprecated) * *(..))")

// 匹配返回值是Integer的带有@Deprecated注解的所有方法
@Pointcut("execution(@java.lang.Deprecated Integer *(..))")
// 匹配所有返回值持有@Data的方法 (@lombok.Data *)是带有@Data的对象作为返回值
@Pointcut("execution((@lombok.Data *) *(..))")
// 匹配任何带有一个参数的方法,且该参数类型持有@Data的方法 (@lombok.Data *)是带有@Data的对象作为返回值
@Pointcut("execution(* *(@lombok.Data *))")
// 匹配任何参数带有两个参数的方法,且这个两个参数都被@Data标记了(这里展示了参数的两种写法,带括号和不带括号)
@Pointcut("execution(* *(@lombok.Data *,@lombok.Data (*)))")

// 演示示例:多参数多注解写法:这里展示了参数的两种写法,带括号和不带括号.这里展示了多个注解关系的写法,或者和并且
@Pointcut("execution(* *(@(lombok.Getter || lombok.Setter) *,@lombok.Getter @lombok.Setter (*),@(lombok.Getter&&lombok.Setter) (*)))")

/*支持泛型,泛型支持注解、+号*/
// 匹配任何参数带有List<DataModel>的方法
@Pointcut("execution(* *(java.util.List<com..DataModel>))")
// 匹配任何参数带有List<DataModel及子类型>的方法
@Pointcut("execution(* *(java.util.List<com..DataModel+>))")
// 匹配任何参数带有List<持有@Data注解的DataModel及子类型>的方法
@Pointcut("execution(* *(java.util.List<@lombok.Data com..DataModel+>))")

// throws 不管用,这依然会匹配throws RunTimeException异常的方法,反之一样。
@Pointcut("execution(* com.example.demo.util.DemoBean.*(..)) throws java.io.IOException")

within

限定匹配特定类型的连接点(在使用SpringAOP的时候,在匹配的类型中定义的方法的执行)。

/*针对的类和接口*/
// 匹配该包下所有类的所有方法的执行
@Pointcut("within(com.example.demo..*)")
// 匹配该包及子包下的IDataServer内所有方法的执行
@Pointcut("within(com.example.demo..IDataServer+)")
// 匹配所有持有@Deprecated的类所有方法
@Pointcut("within(@java.lang.Deprecated *)")

this

限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。

常用于命名绑定模式。对由代理对象的类型进行过滤筛选。

目标对象使用aop之后生成的代理对象必须是指定的类型才会被拦截,注意是目标对象被代理之后生成的代理对象和指定的类型匹配才会被拦截

// 支持controller和service(接口)
@Pointcut("this(com.example.demo.controller.TestController)")
@Pointcut("this(com.example.demo.service.IDataServer)")
// 通配符支持:
// 路径不支持通配符(..、*)
@Pointcut("this(com.example.demo.*.IDataServer)")// 错误X。不会生效
@Pointcut("this(com.example.demo..IDataServer)")// 错误X。不会生效
//  类名接口名支持通配符(*)不支持通配符(+)
@Pointcut("this(com.example.demo.service.IDataS*)")// 支持√
@Pointcut("this(com.example.demo.util.DemoBean+)")// 错误X。不会生效

target

限定匹配特定的连接点(使用SpringAOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。

// 支持controller,不支持接口
@Pointcut("target(com.example.demo.controller.TestController)")
@Pointcut("target(com.example.demo.service.IDataServer)")// 不支持接口
@Pointcut("target(com.example.demo.service.impl.DataImpl)")// 仅支持bean
// 通配符支持:
// 路径不支持通配符(..、*)
@Pointcut("target(com.example.demo.service.*.DataImpl)")// 错误X。不会生效
@Pointcut("target(com.example.demo.service..DataImpl)")// 错误X。不会生效
//  类名接口名支持通配符(*)不支持通配符(+)
@Pointcut("target(com.example.demo.service.impl.DataI*)")// 支持√
@Pointcut("target(com.example.demo.util.DemoBean+)")// 错误X。不会生效

args

限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。

// 匹配第一个参数是Serializable子类的参数,其余参数0到多个
@Pointcut("args(java.io.Serializable+,..)")

@within

限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。

该注解只能是注解到实现类上,注解到service层匹配不到哦

// 匹配持有该注解的类的所有方法
@Pointcut("@within(com.example.demo.aop.AopAnno)")
@Pointcut("@within(org.springframework.stereotype.Service)")
@Pointcut("@within(com.example.demo.*.AopAnno)") // 错误X,仅支持全限定名

@target

限定匹配特定的连接点(使用SpringAOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。

表述和@within一样,但是@within能运行起来,@target运行不起来,报错,所以尽量不考虑使用该表达式了。

@args

限定匹配特定的连接点(使用SpringAOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。

// 匹配方法有一个参数且参数持有@Data注解
@Pointcut("@args(lombok.Data)")
// 匹配方法有多个参数且第一个参数持有@Data注解
@Pointcut("@args(lombok.Data,..)")

@annotation

限定匹配特定的连接点(使用SpringAOP的时候方法的执行),其中连接点的主题有某种给定的注解

// 匹配持有@Deprecated注解的所有方法
@Pointcut("@annotation(java.lang.Deprecated)")

bean(拓展,第10种表达式)

根据beanNam来匹配。支持*通配符

// 匹配所有以Controller结尾的Bean的所有方法
bean(*Controller)

5种通知类型

@Before、@After、@AfterReturning、@AfterThrowing,可选择声明JoinPoint参数。

@Around需要声明ProceedingJoinPoint参数。

前置通知(Before)

在方法执行前通知

@Before("execOrder()")
public void doBefore(JoinPoint joinPoint) {
    Console.log("doBefore...");
}

方法执行后通知(@After)

在目标方法执行后无论是否发生异常,执行通知,不能访问目标方法的执行的结果。

@After("execOrder()")
public void doAfter(JoinPoint joinPoint) {
    Console.log("doAfter...");
}

环绕通知(@Around)

可以将要执行的方法(point.proceed())进行包裹执行,可以在前后添加需要执行的操作

/**
* 环绕
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("execOrder()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    Console.log("doAround-start...");
    Object proceed = proceedingJoinPoint.proceed();
    Console.log("doAround-end...");
    return proceed;
}

后置通知(@AfterReturning)

在方法正常执行完成进行通知,可以访问到方法的返回值的。

@AfterReturning(value = "execOrder()",returning = "a")
public void doAfterReturning(Object a) {
    Console.log("doAfterReturning...");
    System.out.println("返回值:"+a);
}

异常通知(@AfterThrowing)

在方法出现异常时进行通知,可以访问到异常对象,且可以指定在出现特定异常时在执行通知。

@AfterThrowing(pointcut="execOrder()",throwing="a")
public void doaction(Throwable a) {
    Console.log("doAfterThrowing...");
    System.out.println("目标方法中抛出的异常:"+a);
}

代码实现

1.maven中引入依赖:

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

2.编辑切面类(简单代码示例)

@Aspect
@Component
@Slf4j
public class AspectAop {

    /** 换行符 */
    private static final String LINE_SEPARATOR = System.lineSeparator();

    // 定义切点:
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void webLog(){}

    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        
    }

    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        
    }
    /**
     * 环绕
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕前 doing...
        Object result = proceedingJoinPoint.proceed();
        // 环绕后 doing...
        return result;
    }
}

执行顺序问题:

程序正常:

doAround-start...doBefore...RunningMethod......doAfterReturning...doAfter...doAround-end...

程序异常:

doAround-start...doBefore...RunningMethod......doAfterThrowing...doAfter...

总结:


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容