【Spring】Spring中基于AOP的AspectJ框架总结

【AspectJ】Spring中基于AOP的AspectJ框架总结

注:本文仅供学习参考。

前言:对于AOP编程思想,很多框架都进行了实现。Spring也不例外,可以完成面向切面编程。AspectJ框架也实现了AOP的功能,且实现方式更为简洁,使用更加方便,而且支持注解开发,故Spring将AspectJ对于AOP的实现也入到了自己的框架中。在Spring中使用AOP开发时,通常使用AspectJ实现。

1、AspectJ简介

2、AspectJ通知类型

  • 以下为AspectJ中常用的通知:

    1)@Before 前置通知

    2)@AfterReturning 后置通知

    3)@Around 环绕通知

    4)@After 最终通知

    5)@Pointcut 定义切入点

3、AspectJ切入点表达式

(1)表达式格式如下:

  • execution(

    modifiers-pattern

    ret-type-pattern

    declaring-type-pattern

    name-pattern(param-pattern) throws-pattern)

  • 解释:

    • modifiers-pattern:访问权限类型 (可省略)
    • ret-type-pattern:返回值类型
    • declaring-type-pattern:包名类型 (可省略)
    • name-pattern(param-pattern):方法名(参数类型和参数个数)
    • throws-pattern:抛出异常类型 (可省略)
  • 简化格式如下:

    execution(访问权限 方法返回值 方法声明(参数) 异常类型)

    // 访问权限和异常类型可省略

(2)切入点表达式中的特殊字符

  • 表达式中可使用的特殊符号及含义:
    • “ * ”:0个或多个任意字符
    • “ … ”:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径
    • “ + ”:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

(3)案例(以下内容引自动力节点Spring课程讲义)

  • execution(public * *(…)) => 指定切入点为:任意公共方法。

  • execution(* set*(…)) => 指定切入点为:任何一个以“set”开始的方法。

  • execution(* com.xyz.service.impl.*.*(…)) => 指定切入点为:定义在 service 包里的任意类的任意方法。

  • execution(* com.xyz.service….(…)) * om.xyz.service.power2.aa..(…) => 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

  • execution(* * …service. * . * (…)) a.b.service..(…) a.b.c.d.service..(…) => 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。

  • execution(* * .service. * . *(…)) => 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点。

  • execution(* * . ISomeService.*(…)) => 指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点

  • execution(* * …ISomeService.*(…)) => 指定所有包下的 ISomeSerivce 接口中所有方法为切入点

  • execution(* com.xyz.service.IAccountService.*(…)) => 指定切入点为:IAccountService 接口中的任意方法。

  • execution(* com.xyz.service.IAccountService+.*(…)) => 指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。

  • execution(* joke(String,int))) => 指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。

  • execution(* joke(String,*))) => 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,Strings3)不是。

  • execution(* joke(String,…))) => 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3) 都是。

  • execution(* joke(Object)) => 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

  • execution(* joke(Object+))) => 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

4、AspectJ基于注解的AOP实现

(1)目录结构

在这里插入图片描述

(2)添加Maven依赖

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

(3)引入AOP约束

  • AspectJ对于AOP的实现有注解和配置文件两种方式,常用是注解方式。AspectJ的aop约束在applicationContext.xml文件(spring配置文件)中引入,详见applicationContext.xml文件。

(4)案例代码

SomeService.java
package com.Etui.s04;

public interface SomeService {
    
    

    String doSome(String userName, Integer userId);

    void doSome2();

}
SomeServiceImpl.java
package com.Etui.s04;

import com.Etui.s02.Student;
import org.springframework.stereotype.Service;

@Service
public class SomeServiceImpl implements SomeService {
    
    
    @Override
    public String doSome(String userName, Integer userId) {
    
    
        System.out.println("事务方法被调用…………………………");
//        int a = 1/0; // 出错时查看After通知是否执行
        return "hello";
    }

    @Override
    public void doSome2() {
    
    
        System.out.println("事务方法被调用…………………………");
    }

}
MyAspect.java
package com.Etui.s04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

// 最终通知
@Component
@Aspect
public class MyAspect {
    
    

   /** @Before
     * 前置通知中的切面方法的规范
     * 1)访问权限是public
     * 2)没有返回值void
     * 3)切面方法的名称自定义
     * 4)切面方法可以没有参数,如果有也是固定的类型JoinPoint
     * 5)使用@Before注解表明是前切功能
     * 6)@Before的参数:
     *   value:指定切入点表达式
     *    public String doSome(String name, int age)
     */
    // 前置通知
    @Before(value="muCut()")
    public void serviceBefore() {
    
    
        System.out.println("前置通知处理……………………………………");
    }

    
   /** @AfterReturning
     * 后置通知切面方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法自定义
     * 4)切面方法可以没有参数,如果有参数则是目标方法的返回值,也可以包含参数JoinPoint,它必须是第一个参数
     * 5)使用@AfterReturning注解
     * 6)参数value:指定切入点表达式
     *      returning:指定目标方法返回值的形参名称,此名称必须与切面方法的参数名称一致.
     */
    // 后置通知
    @AfterReturning(value = "muCut()", returning = "obj")
    public void serviceAfterReturning(Object obj){
    
    
        System.out.println("后置通知处理…………………………………………");
    }

    /** @Around
     * 环绕通知方法的规范
     * 1)访问权限是public
     * 2)切面方法有返回值,此返回值就是目标方法的返回值.
     * 3)切面方法的名称自定义
     * 4)切面方法有参数,参数就是目标方法.它是ProceedingJoinPoint的类型
     * 5)必须要回避异常Throwable
     * 6)使用@Around注解
     * 7)参数:value:指定切入点表达式
     *
     */
    // 环绕通知
    @Around(value = "muCut()")
    public void serviceAround(ProceedingJoinPoint pjp) throws Throwable {
    
    
        System.out.println("环绕通知中的前置事务处理…………………………………………");
        pjp.proceed(pjp.getArgs());
        System.out.println("环绕通知中的后置事务处理……………………………………………………");
    }

     /** @After
     * 最终方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法名称自定义
     * 4)方法可以没有参数,也可以有,则JoinPoint.
     * 5)使用@After注解
     * 6)参数:value:指定切入点表达式
     */
    // 最终通知
    @After(value = "muCut()")
    public void serviceAfter() {
    
    
        System.out.println("最终通知事务处理…………………………");
    }

     /** @Pointcut
     * 最终方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法名称自定义
     * 4)方法可以没有参数,也可以有,则JoinPoint.
     * 5)使用@After注解
     * 6)参数:value:指定切入点表达式
     */
    @Pointcut(value = "execution(* com.Etui.s04.*.*(..))")
    public void muCut(){
    
    }


}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.Etui.s04"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
MyTest.java
package com.Etui.s04;

import com.Etui.s04.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    
    

    @Test
    public void testAround(){
    
    
        ApplicationContext ac = new ClassPathXmlApplicationContext("s04/applicationContext.xml");
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        someService.doSome("刻晴", 001);
    }

}

5、相关注意事项

(1)@Before前置通知的JoinPoint参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。

(2)@AfterReturning后置通知的returning属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

(3)@Around环绕通知的ProceedingJoinPoint参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

Over!

猜你喜欢

转载自blog.csdn.net/m0_47015897/article/details/124442386