拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 动力节点Spring框架学习笔记-王鹤(二)AOP面向切面编程

动力节点Spring框架学习笔记-王鹤(二)AOP面向切面编程

白鹭 - 2022-01-23 2108 0 0

二、AOP面向切面编程

官方下载地址:动力节点官网

视频观看地址

https://www.bilibili.com/video/BV1nz4y1d7uy

2.1 概述

AOP(Aspect Orient Programming),面向切面编程是从动态角度考虑程序运行程序

  • AOP 底层,就是采用动态代理模式实作的,采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理,AOP就是动态代理的规范化, 把动态代理的实作步骤,方式都定义好了, 让开发人员用一种统一的方式,使用动态代理
  • Aspect: 切面,给你的目标类增加的功能,就是切面, 像上面用的日志,事务都是切面,切面的特点:一般都是非业务方法,独立使用的
  • Orient:面向, 对着
  • Programming:编程

2.2 相关术语

1. Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能,非业务功能,常见的切面功能有日志, 事务, 统计信息, 自变量检查, 权限验证,切面用于组织多个Advice,Advice放在切面中定义,实际就是对主业务逻辑的一种增强

2. JoinPoint:连接点 ,连接业务方法和切面的位置,就某类中的业务方法,程序执行程序中明确的点,如方法的呼叫,或者例外的抛出,在Spring AOP中,连接点总是方法的呼叫

3. Pointcut:切入点 ,指多个连接点方法的集合,多个方法,可以插入增强处理的连接点,简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点

4. Advice:AOP框架在特定的切入点执行的增强处理,处理有"around"、"before"和"after"等型别,能表示切面功能执行的时间,切入点定义切入的位置,通知定义切入的时间

5. Target:目标物件,目 标 对 象 指 将 要 被 增 强 的 对 象 , 即 包 含 主 业 务 逻 辑 的 类 的 对 象

2.3 AspectJ

2.3.1 概述

AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其主要包括两个部分:

  • 一个部分定义了如何表达、定义AOP编程中的语法规范;
  • 另一个部分是工具部分,包括编译、除错工具等

aspectJ框架实作aop的两种方式:

  1. 使用xml的组态档 : 配置全域事务
  2. 使用注解,我们在项目中要做aop功能,一般都使用注解,aspectj有5个注解
  • @Before
  • @AfterReturning
  • @Around
  • @AfterThrowing
  • @After

2.3.2 AspectJ的切入点表达式

表达式原型:

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 抛出例外型别
  • ?表示可选的部分

以上表达式一共4个部分

execution(访问权限 方法回传值 方法宣告(自变量) 例外型别)

符号意义
* 0至多个任意字符
. . 用在方法自变量中,表示任意多个自变量;用在包名后,表示当前包与子包路径
+ 用在类名后,表示当前类及其子类;用在界面后,表示当前界面及其实作类

相关实体:

  • execution(public * *(..)):任意公共方法
  • execution(* set*(..)):任何一个以“set”开始的方法
  • execution(* com.xyz.service.*.*(..)):定义在 service 包里的任意类的任意方法
  • execution(* com.xyz.service..*.*(..)):定义在 service 包或者子包里的任意类的任意方法,“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类
  • execution(* *..service.*.*(..)):指定所有包下的 serivce 子包下所有类(界面)中所有方法为切入点
  • execution(* com.xyz.service.IAccountService+.*(..)):IAccountService 若为界面,则为界面中的任意方法及其所有实作类中的任意方法;若为类,则为该类及其子类中的任意方法
  • execution(* joke(String,int))):所有的 joke(String,int)方法,且 joke()方法的第一个自变量是 String,第二个自变量是 int;如果方法中的自变量型别是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)

2.3.3 前置通知:@Before

1.方法的定义要求:

  • 公共方法 public
  • 方法没有回传值
  • 方法名称自定义
  • 方法可以有自变量,也可以没有自变量

2.@Before: 前置通知注解

属性:value ,是切入点表达式,表示切面的功能执行的位置

位置:在方法的上面

1.配置依赖

<!--spring依赖-->

    <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring-context</artifactId>

      <version>5.2.5.RELEASE</version>

    </dependency>

    <!--aspectj依赖-->

    <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring-aspects</artifactId>

      <version>5.2.5.RELEASE</version>

    </dependency>

2.创建业务界面与实作类物件

Service.interface

public interface Service {

    public void doSome();

}
点击并拖拽以移动
ServiceImpl.java

@org.springframework.stereotype.Service("myService")

public class ServiceImpl implements Service {

    @Override

    public void doSome() {

        System.out.println("这是我的业务方法!!!");

    }

}

3.创建切面类:MyAspect.java

@Aspect

@Component("myAspect")

public class MyAspect {

    /**

    *指定通知方法中的自变量:JoinPoint

    *

    */

    @Before(value = "execution(void *..doSome(..))")

    public void before(){

        System.out.println("这是前置通知");

    }

}

4.配置applicationContext.xml档案

    <!--扫描档案-->

    <context:component-scan base-package="com.jjh.*"/>

    <!--宣告自动代理生成器-->

    <aop:aspectj-autoproxy/>

5.测验类呼叫

@org.junit.Test

    public void demo01(){

        String config = "applicationContext.xml";

        ApplicationContext app = new ClassPathXmlApplicationContext(config);

        Service proxy = (Service) app.getBean("myService");

        //输出当前类的信息:com.sun.proxy.$Proxy17

        //证明其使用的是JDK动态代理

        System.out.println(proxy.getClass().getName());

        proxy.doSome();

    }

2.3.4 JoinPoint

  • 指定通知方法中的自变量 : JoinPoint
  • JoinPoint:业务方法,要加入切面功能的业务方法
  • 作用:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参
  • 如果需要切面功能中方法的信息,就加入JoinPoint
  • JoinPoint自变量的值是由框架赋予, 必须是第一个位置的自变量
  • 不止前置通知的方法,可以包含一个 JoinPoint 型别自变量,所有的通知方法均可包含该自变量

MyAspect.java

@Aspect

@Component("myAspect")

public class MyAspect {

    @Before(value = "execution(void *..doSome(..))")

    public void before(JoinPoint joinPoint){

        //获取方法的定义

        System.out.println("方法的签名(定义):" + joinPoint.getSignature());

        System.out.println("方法的名称:" + joinPoint.getSignature().getName());

        //获取方法的实参

        Object[] args = joinPoint.getArgs();

        for (Object arg : args) {

            System.out.println("自变量:" + arg);

        }

        System.out.println("这是前置通知!!!");

    }

}

测验类

    @org.junit.Test

    public void demo01(){

        String config = "applicationContext.xml";

        ApplicationContext app = new ClassPathXmlApplicationContext(config);

        Service proxy = (Service) app.getBean("myService");

        proxy.doSome("张三",20);

    }

打印结果

2.3.5 后置通知:@AfterReturning

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

业务方法

@Component("myService2")

public class SomeServiceImpl implements SomeService {

    @Override

    public void doSome() {

        System.out.println("这是业务方法!!!");

    }

    @Override

    public String doOther(String str, int i) {

        return "业务方法doOther的回传值!!!";

    }

}

后置通知

1.该注解的 returning 属性就是用于指定接收方法回传值的变量名的

2.除了可以包含 JoinPoint 自变量外,还可以包含用于接识训传值的变量

3.该变量最好为Object 型别,因为目标方法的回传值可能是任何型别

4.方法的定义要求:

  • 公共方法 public
  • 方法没有回传值
  • 方法名称自定义
  • 方法有自变量的,推荐是Object ,自变量名自定义

5.@AfterReturning:后置通知

  • value:切入点表达式
  • returning:自定义的变量,表示目标方法的回传值的,自定义变量名必须和通知方法的形参名一样
  • 可以根据业务方法的回传值做出相应的操作
@AfterReturning(value = "https://www.cnblogs.com/laoduyyds/archive/2022/01/21/execution(String *..doOther(..))",returning = "res")

    public void myAfterReturning(JoinPoint joinPoint,Object res){

        //获取方法的签属(定义)

        System.out.println(joinPoint.getSignature());

        //获取方法的自变量

        Object[] args = joinPoint.getArgs();

        for (Object arg : args) {

            System.out.println("目标方法自变量:" + arg);

        }

        //目标方法的回传值

        System.out.println(res);

        //后置通知

        System.out.println("后置通知!!!");

    }

测验类

    @Test

    public void demo02(){

        String config = "applicationContext.xml";

        ApplicationContext app = new ClassPathXmlApplicationContext(config);

        SomeService proxy = (SomeService)app.getBean("myService2");

        proxy.doOther("Dick",20);

    }点击并拖拽以移动

2.3.6 环绕通知:@Around

在目标方法执行之前之后执行,被注解为环绕增强的方法要有回传值

被注解为环绕增强的方法要有回传值,Object 型别,并且方法可以包含一个ProceedingJoinPoint型别的自变量

界面ProceedingJoinPoint其中有一个proceed() 方法,用于执行目标方法

若目标方法有回传值,则该方法的回传值就是目标方法的回传值,最后,环绕增强方法将其回传值回传,该增强方法实际是拦截了目标方法的执行

  • 切面类

1. 环绕通知方法的定义格式:

  • public
  • 必须有一个回传值,推荐使用Object
  • 方法名称自定义
  • 方法有自变量,固定的自变量 ProceedingJoinPoint

2. 特点

  • 在目标方法的前和后都能增强功能
  • 控制目标方法是否被呼叫执行
  • 修改原来的目标方法的执行结果,影响最后的呼叫结果
  • 它是功能最强的通知

3.环绕通知等同于jdk动态代理的InvocationHandler界面

4.自变量:ProceedingJoinPoint 就等同于 Method,用于执行目标方法

5.回传值: 就是目标方法的执行结果,可以被修改

6.环绕通知:经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务

@Around(value = "https://www.cnblogs.com/laoduyyds/archive/2022/01/21/execution(String *..do(..))")

    public Object myAround(ProceedingJoinPoint p) throws Throwable {

        Object result = null;

        //前置功能增强

        System.out.println("前置功能增强!!");

        //等同于method.invoke(); Object result = doFirst();

        result = p.proceed();

        //后置功能增强

        System.out.println("后置功能增强");

        return result;

    }

测验类

    @Test

    public void demo01(){

        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        SomeService proxy = (SomeService)app.getBean("Service3");

        String result = proxy.doFirst("Dick", 99);

        //输出回传结果

        System.out.println(result);

    }

2.3.7 例外通知:@AfterThrowing

在目标方法抛出例外后执行

该注解的 throwing 属性用于指定所发生的例外类物件

被注解为例外通知的方法可以包含一个自变量 Throwable,自变量名称为 throwing 指定的名称,表示发生的例外物件

  • 业务方法
 @Override

    public void doSecond() {

        System.out.println("执行业务方法doSecond()" + (10/0));

    }
  • 切面类

1. 例外通知方法的定义格式:

  • 访问权限public
  • 没有回传值
  • 方法名称自定义
  • 方法有个一个Exception, 也可以使用JoinPoint

2. @AfterThrowing:例外通知

  • 属性:

value 切入点表达式

throwinng 自定义的变量,表示目标方法抛出的例外物件,变量名必须和方法的自变量名一样

  • 特点:

在目标方法抛出例外时执行的

可以做例外的监控程序, 监控目标方法执行时是不是有例外,如果有例外,可以发送邮件,短信进行通知

    @AfterThrowing(value = "https://www.cnblogs.com/laoduyyds/archive/2022/01/21/execution(* *..SomeServiceImpl.doSecond(..))",

            throwing = "ex")

    public void myAfterThrowing(Exception ex) {

        System.out.println("例外通知:方法发生例外时,执行:"+ex.getMessage());

        //发送邮件,短信,通知开发人员

    }

2.3.8 最终通知:@After

无论目标方法是否抛出例外,该增强均会被执行

  • 业务方法
    @Override

    public void doThird() {

        System.out.println("执行业务方法doThird()");

    }

切面类

1. 最终通知的定义格式:

  • 访问权限public
  • 没有回传值
  • 方法名称自定义
  • 方法没有自变量,但是可以使用JoinPoint

2. @After :最终通知特点

  • 总是会执行
  • 在目标方法之后执行
       //等同以下执行方式

       try{

           SomeServiceImpl.doThird(..)

       }catch(Exception e){

     

       }finally{

           myAfter()

       }
点击并拖拽以移动
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")

    public  void  myAfter(){

        System.out.println("执行最终通知,总是会被执行的代码");

        //一般做资源清除作业的,

     }

2.3.9 @Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,撰写、维护均较为麻烦;AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式

将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点

代表的就是@Pointcut定义的切入点,这个使用@Pointcut注解的方法一般使用 private 的标识方法,即没有实际作用的方法

  • 切面类

1.@Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的,

2.特点:

当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名

其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了

    @After(value = "https://www.cnblogs.com/laoduyyds/archive/2022/01/21/mypt()")

    public  void  myAfter(){

        System.out.println("执行最终通知,总是会被执行的代码");

        //一般做资源清除作业的,

     }

    @Before(value = "mypt()")

    public  void  myBefore(){

        System.out.println("前置通知,在目标方法之前先执行的");

    }

    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )

    private void mypt(){

        //无需代码,

    }

2.4 代理方式更换

如果目标类有界面,默认使用jdk动态代理,如果目标类没有界面,则使用CGlib动态代理

如果想让具有界面的目标类使用CGlib的代理方式,需要以下组态档

<aop:aspectj-autoproxy proxy-target-class="true"/>


 

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *