SpringAOP的使用详解

2023-05-16

1.AOP切入点表达式

支持切点标识符

Spring AOP支持使用以下AspectJ切点标识符(PCD),用于切点表达式:

  • execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。 可以匹配到方法级别 ,细粒度
  • within: 只能匹配类这级,只能指定类, 类下面的某个具体的方法无法指定, 粗粒度
  • this: 匹配实现了某个接口:this(com.xyz.service.AccountService)
  • target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。
  • args: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。 AOP) where the arguments are instances of the given types.
  • @target: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
  • @args: 限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
  • @within: 限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
  • @annotation:限制匹配连接点(在Spring AOP中执行的方法具有给定的注解)。

 1.1、execution语法

细粒度的切入点表达式,可以以方法为单位定义切入点规则

语法:execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))

例子1:

<aop:pointcut expression="execution(void cn.tulingxueyuan.service.UserServiceImpl.addUser(java.lang.String))" id="pc1"/>

该切入点规则表示,切出指定包下指定类下指定名称指定参数指定返回值的方法。

例子2:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service.*.query())" id="pc1"/>

该切入点规则表示,切出指定包下所有的类中的query方法,要求无参,但返回值类型不限。

例子3:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service..*.query())" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,要求无参,但返回值类型不限。

例子4:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service..*.query(int,java.lang.String))" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,要求参数为int java.langString类型,但返回值类型不限。

例子5:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service..*.query(..))" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,参数数量及类型不限,返回值类型不限。

例子6:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service..*.*(..))" id="pc1"/>

该切入点规则表示,切出指定包及其子孙包下所有的类中的任意方法,参数数量及类型不限,返回值类型不限。这种写法等价于within表达式的功能。

例子7:

<aop:pointcut expression="execution(* cn.tulingxueyuan.service..*

   

访问修饰符:可不写 可以匹配任何一个访问修饰符

返回值:如果是jdk自带类型可以不用写完整限定名,如果是自定义类型需要写上完整限定名,如果被切入的方法返回值不一样可以使用* 代表所有的方法值都能匹配

包名:cn.* == cn.tulingxuyuean == cn.任意名字 但是只能匹配一级 比如 cn.tulingxueyuan.service就无法匹配

如果要cn.tulingxueyuan.service ==>cn.tulingxueyuan.service , cn.tulingxueyuan.* ==>cn.tulingxueyuan.service.impl就无法匹配

cn.tulingxueyuan..* ==>cn.tulingxueyuan.service.impl 可以匹配

类名: 可以写*,代表任何名字的类名。 也可以模糊匹配 *ServiceImpl==> UserServiceImpl ==>RoleServiceImpl

方法名:可以写*,代表任何方法。 也可以模糊匹配 *add==> useradd ==>roleadd

参数:如果是jdk自带类型可以不用写完整限定名,如果是自定义类型需要写上完整限定名。 如果需要匹配任意参数 可以写:..

1.2、within表达式

通过类名进行匹配 粗粒度的切入点表达式

within(包名.类名)

则这个类中的所有的连接点都会被表达式识别,成为切入点。

        <aop:pointcut expression="within(cn.tulingxueyuan.service.UserServiceImpl)"

在within表达式中可以使用*号匹配符,匹配指定包下所有的类,注意,只匹配当前包,不包括当前包的子孙包。

        <aop:pointcut expression="within(cn.tulingxueyuan.service.*)"

在within表达式中也可以用*号匹配符,匹配包

        <aop:pointcut expression="within(cn.tulingxueyuan.*.*)"

在within表达式中也可以用..*号匹配符,匹配指定包下及其子孙包下的所有的类

        <aop:pointcut expression="within(cn.tulingxueyuan..*)"

1.3、合并切点表达式

可以使用 &&, || 和 !等符号进行合并操作。也可以通过名字来指向切点表达式。

//&&:两个表达式同时
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
//||:任意满足一个表达式即可
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
//!:只要不是这个位置都可以进行切入
//&&:两个表达式同时
execution( public int cn.tulingxueyuan.inter.MyCalculator.*(..))

2、通知方法的执行顺序

 

在之前的代码中大家一直对通知的执行顺序有疑问,其实执行的结果并没有错,大家需要注意:

1、正常执行:@Before--->@After--->@AfterReturning

2、异常执行:@Before--->@After--->@AfterThrowing

Spring在5.2.7之后就改变的advice 的执行顺序。 在github官网版本更新说明中有说明:如图

更新说明:https://github.com/spring-projects/spring-framewor...

#25186链接:https://github.com/spring-projects/spring-framewor...

 

3、获取方法的详细信息

在上面的案例中,我们并没有获取Method的详细信息,例如方法名、参数列表等信息,想要获取的话其实非常简单,只需要添加JoinPoint参数即可。

@Before("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

刚刚只是获取了方法的信息,但是如果需要获取结果,还需要添加另外一个方法参数,并且告诉spring使用哪个参数来进行结果接收

 @AfterReturning(value = "execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))",
                    returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行完成,结果是:"+result);

    }

也可以通过相同的方式来获取异常的信息

@AfterThrowing(value = "execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出现异常:"+exception);
    }

4、表达式的抽取

如果在实际使用过程中,多个方法的表达式是一致的话,那么可以考虑将切入点表达式抽取出来:

a、随便生命一个没有实现的返回void的空方法

b、给方法上标注@Potintcut注解

@Pointcut("execution( public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));
    }

5、环绕通知的使用

环绕通知,环绕通知围绕方法执行。可以在方法执行之前和执行之后执行,并且定义何时做什么,甚至是否真正得到执行。如果需要在方法执行之前和之后以线程安全的方式 (例如启动和停止计时器) 共享状态, 则通常会使用环绕通知。总是建议使用最适合要求的通知(即可以用前置通知解决的就不要用环绕通知了)。

使用@Around注解来定义环绕通知,第一个参数必须是ProceedingJoinPoint类型的。在通知中调用 ProceedingJoinPoint中的 proceed()方法来引用执行的方法。

// 环绕
    @Around("pointcut()")
    public  Object arround(ProceedingJoinPoint joinPoint){
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 所有的参数
        Object[] args = joinPoint.getArgs();

        Object returnValue="";
        try {
            System.out.println("环绕:前置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args));
            returnValue=joinPoint.proceed();
            System.out.println("环绕:后置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args));
        } catch (Throwable throwable) {
            System.out.println("环绕:异常通知:"+throwable);
        }
        finally {
            System.out.println("环绕:返回通知:"+returnValue);
        }

        return returnValue;
    }

总结:环绕通知的执行顺序是优于普通通知的,具体的执行顺序如下:

环绕前置-->普通前置-->目标方法执行-->环绕正常结束/出现异常-->环绕后置-->普通后置-->普通返回或者异常。

但是需要注意的是,如果出现了异常,那么环绕通知会处理或者捕获异常,普通异常通知是接收不到的,因此最好的方式是在环绕异常通知中向外抛出异常。

6、基于配置的AOP配置(xml)

<bean id="logUtil" class="cn.tulingxueyuan.util.LogUtil2"></bean>
<bean id="securityAspect" class="cn.tulingxueyuan.util.SecurityAspect"></bean>
<bean id="myCalculator" class="cn.tulingxueyuan.inter.MyCalculator"></bean>

<aop:config>
        <aop:pointcut id="globalPoint" expression="execution(public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))"/>
        <aop:aspect ref="logUtil">
            <aop:pointcut id="mypoint" expression="execution(public int cn.tulingxueyuan.inter.MyCalculator.*(int,int))"/>
            <aop:before method="start" pointcut-ref="mypoint"></aop:before>
            <aop:after method="end" pointcut-ref="mypoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="mypoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="mypoint"></aop:around>
        </aop:aspect> 
    </aop:config>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringAOP的使用详解 的相关文章

随机推荐