AOP面向切面编程

AOP面向切口编程:

AOP简介 :

AOP (Aspect Orient Programming)

  • Aspect : 切面 : 给你的目标类增加的功能 , 就是切面
    • 切面的特点 : 一般都是非业务逻辑 , 都是可以独立使用的
  • Orient : 面向 , 对着
  • Programming : 编程

意为 : 面向切面编程 , 可通过运行期动态代理实现程序功能的统一维护的一种技术 , AOP是Spring框架中一个重要内容 , 利用AOP可以对业务逻辑的各个部分进行隔离 , 从而使得业务逻辑各部分之间耦合度降低 , 提高程序的可重用性 , 同时提高了开发效率

AOP底层 , 就是采用的动态代理模式实现的 , 采用两种代理 ,JDK动态代理 和 CGLIB动态代理

面向切面编程 , 就是将交叉业务逻辑封装成切面 , 利用AOP容器的功能将切面织入到主业务逻辑中 , 所谓交叉业务逻辑是指 , 通用的 , 与主业务逻辑无关的代码 , 如安全检查 , 事务 , 日志 , 缓存等

如若不使用AOP , 则会出现代码的纠缠 , 级交叉业务逻辑与主业务逻辑混合在一起 , 这样 , 会使主业务逻辑变得混杂不清

AOP就是动态代理的规范化 , 把动态代理的实现步骤 , 方式都定义好了 , 让开发人员用一种统一的方式 , 使用动态代理


怎么理解面向切面编程 :

  • (1) 需要在分析项目功能时 , 找出切面

  • (2) 合理的安排切面的执行时间 , (在目标方法前 , 还是目标方法后 )

  • (3) 合理的安全切面执行的位置 , 在哪个类 , 哪个方法增加


术语 :

  • (1) Aspect : 切面 表示增强的功能 , 非业务逻辑功能

    • 常见的切面功能有 : 日志 , 事务 , 统计信息 , 参数检查 , 权限验证
  • (2) JoinPoint : 连接点 , 连接业务方法和切面的位置 , 就是某个类中的业务方法 (一个方法)

  • (3) Pointcut : 切入点 , 指多个连接点方法的集合 (多个方法)

  • (4) 目标对象 : 给哪个类的方法增加功能 , 这个类就是目标对象

  • (5) Advice : 通知 , 通知表示切面功能执行的时间

一个切面有三个关键的要素 :

  • (1) 切面的功能代码 , 切面干什么

  • (2) 切面的执行位置 , 使用Pointcut 表示切面执行的位置

  • (3) 切面的执行时间 , 使用Advice , 在目标方法之前 , 还是目标方法之后


AOP的实现 :

AOP是一个规范 , 是动态的一个规范化 , 一个标准

Spring框架监控切入点方法的执行 , 一旦监控到切入点方法被运行 , 使用代理机制 , 动态代理机制 , 动态创建目标对象的代理对象 , 根据通知类别 , 在代理对象的对应位置 , 将通知对应的功能织入 , 完成完整的代码的逻辑运行

AOP的技术实现框架 :

1.Spring : Spring 在内部实现了aop规范 , 能够做aop的工作

Spring主要是在事务处理时使用aop

​ 我们项目开发中很少使用Spring的aop实现 , 因为spring的aop比较笨重

2.aspectJ : 一个开源的专门做aop的框架 , Spring框架中已经集成了aspectJ框架 , 通过Spring 就能使用aspectJ的功能

  • 1.使用xml的配置文件 : 配置全局事务
  • 2.使用注解 , 我们在项目中要做AOP功能 , 一般都是用注解

AOP开发明确的事项:

  • 编写核心业务代码(目标类的目标方法)
  • 编写切面类 , 切面类中有通知(增强功能方法)
  • 在配置文件中 , 配置织入关系 , 即将那些通知与那些连接点进行结合

AOP:基于XML的AOP开发

  • (1)导入AOP相关坐标

    • <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.0.5.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.8.4</version>
      </dependency> 
      
  • (2)创建目标接口和目标类 (内部有切点)

  • (3)创建切面类 , (内部有增强方法)

  • (4)将目标类和切面类的对象创建权交给Spring

  • (5)在applicationContext.xml中配置织入关系

  • <!--目标对象-->
    <bean id="target" class="com.sichen.aop.Target"/>
    
    <!--切面对象-->
    <bean id="myAspect" class="com.sichen.aop.MyAspect"/>
    
    <!--配置织入 , 告诉spring框架 , 那些方法(切点) 需要进行那些增强(前置, 后置...)-->
    <aop:config>
    	<!--声明切面-->
        <aop:aspect ref="">
        	<!--切面 , 切点+通知-->
            <aop:before method="切面对象中的方法名称" pointcut="切入点表达式"></aop:before>
        </aop:aspect>
    </aop:config>
    

AspectJ框架:

学习aspectJ框架的使用:

(1) 切面的执行时间 : 这个执行时间在规范中叫做通知 Advice (通知 , 增强)

在AspectJ框架中使用注解来表示的 , 也可以使用XML文件中的标签

  • @Before
  • @AfterReturning
  • @Around
  • @AfterThrowing
  • @After

(2) 表示切面执行的位置 : 使用的是切入点表达式 ,

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

解释 :

  • modifiers-pattern : 访问权限类型
  • ret-type-patern : 返回值类型
  • declaring-type-pattern : 包名类名
  • name-pattern(param-pattern) 方法名(参数类型和参数个数)
  • throws-pattern : 抛出异常类型

? 表示可选部分

以上表达式共四个部分

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

切入点表达式要匹配的对象就是目标方法的方法名 , 所以 execution 表达式中明显就是方法的签名 , 注意 , 表达式中黑色文字表示可以省略部分 , 各部分间用空格分开 , 在其中可以使用以下符号

image-20220311131814036

举例 :

  • execution ( public(权限修饰符) ***** (返回值) *(…) (方法声明) )
    • 指定切入点为 : 任意公共方法
  • *execution( *** (返回值) set(…) (方法声明) )
    • 指定切入点为 : 任意一个以set开头的方法
  • execution ( *** (返回值) com.xyz.service. . * (…) (方法声明))*
    • 指定切入点为 : service包中的任意类 , 类中的任意方法 ,
    • 第一个*表示包下的所有类
    • 第二个*表示类下的所有方法
  • **execution( *** (返回值) com.xyz.service… . (…) (方法声明) )
    • 指定切入点为 : 定义在service包或者子包里的任意类的任意方法 ,
    • " … " 出现在类名中时 , 后面必须跟 " * " , 表示包 , 子包下所有的类1
  • execution( * (返回值) * …service.* . *(…) (方法声明))
    • 指定切入点为 : 所有包下的service子包下所有类(接口)中所有方法为切入点

aspectJ的使用 :

(1) 新建maven项目

(2) 加入依赖 :

  • spring依赖
  • aspectJ依赖
<!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.14</version>
    </dependency>
    <!--aspectJ依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.14</version>
    </dependency>

(3) 创建目标类 : 接口和实现类

要做的就是给类中的方法增加功能

(4) 创建切面类 : 普通类

  • 在类的上面要加入 @Aspect

  • /**
     * @Aspect : 是aspectj框架中的注解
     *  作用 : 表示当前类是切面类
     */
    
  • 在类中定义方法 , 方法就是切面要执行的功能代码

    • 在方法的上面要加入aspectJ中的通知注解 , 例如@before
    • 还需要指定切入点表达式 execution()
@Aspect
public class MyAspect {
    @Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
    public void myBefore(){
        System.out.println("前置通知 , 方法执行的时间..."+ new Date());
    }
}

(5) 创建spring的配置文件 , 声明对象 , 把对象交给容器同一管理

  • 声明对象可以使用注解或者是xml配置文件
    • 声明目标对象
    • 声明切面类对象
    • 声明aspectJ框架中的自动代理生成器标签
      • 自动代理生成器 : 用来完成代理对象的自动创建功能的
<!--把对象交给spring容器 , 有spring容器同一管理 , 管理对象-->
    <bean id="someService" class="com.sichen.DI01.impl.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.sichen.DI01.MyAspect"/>

    <!--
    自动代理生成器 : 使用aspectJ框架内部的功能 , 创建目标对象的代理对象
        创建代理对象是在内存中实现的 , 修改目标对象的内存中的结构 , 创建为代理对象
        aspectj-autoproxy
            他会把spring容器中所有的目标对象 , 一次性都生成代理对象
    -->
    <aop:aspectj-autoproxy/>

注解中的参数 : (JoinPoint)

/**
     * 指定通知方法中的参数 : JoinPoint
     *      JoinPoint : 业务方法 , 要加入切面功能的业务方法 ,
     *      作用是 : 可以在通知方法中获取方法执行时的信息 , 例如方法名称 , 方法的实参
     *      如果你的切面功能中需要用到这个方法的信息 , 使用JoinPoint即可
     *      要求 : 这个JoinPoint参数的值是由框架赋予的 , 必须是第一个位置的参数
     */
    @Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的签名 (定义)=" + jp.getSignature());
        System.out.println("方法的名字="+ jp.getSignature().getName());
        //获取方法的实参
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println("方法的参数是 : " +arg);
        }
        System.out.println("前置通知 , 方法执行的时间..."+ new Date());
    }

aspectJ中的五种注解的详解:

@Before : 前置通知
  • @Before 前置通知注解
    方法的定义要求 :
         1.公共方法 public
         2.方法/*没有返回值*/
         3.方法名称自定义
         4.方法可以有参数 , 也可以没有参数
    如果有参数 , 参数(不是自定义的) , 有几个参数类型可以选择
    
  • 属性 : value

    • 切入点表达式 ,表示切面功能执行的位置
  • 位置 :

    • 在方法的上面
  • 特点 :

    •  1.在目标方法之前先执行的
      
    •  2.不会改变目标方法的执行结果
      
    •  3.不会影响目标方法的执行
      
  • 指定通知方法中的参数 : JoinPoint

    • 作用是 :
      • 可以在通知方法中获取方法执行时的信息 , 例如方法名称 , 方法的实参
      • 如果你的切面功能中需要用到这个方法的信息 , 使用JoinPoint即可
    • 要求 :
      • 这个JoinPoint参数的值是由框架赋予的 , 必须是第一个位置的参数
@Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
    public void myBefore(JoinPoint jp){
    //获取方法的完整定义
    System.out.println("方法的签名 (定义)=" + jp.getSignature());
    System.out.println("方法的名字="+ jp.getSignature().getName());
    //获取方法的实参
    Object[] args = jp.getArgs();
    for (Object arg : args) {
        System.out.println("方法的参数是 : " +arg);
    }
    System.out.println("前置通知 , 方法执行的时间..."+ new Date());
}

@AfterReturning : 后置通知
  • @AfterReturning : 后置通知
    方法的定义要求 :
    	1.公共方法 public
    	2.方法/*没有返回值*/
    	3.方法名称自定义
    	4.方法可以有参数 , 推荐使用Object ,参数是自定义的
    
  • 参数的作用:

    • 1.在目标方法执行之后执行的
    • 2.它能够获取目标方法的返回值
    • Object res = doSome();
    • 3.可以修改返回值
  • 属性 :

    • 1.value , 切入点表达式
    • 2.returning : 自定义的变量 , 表示目标方法的返回值的
  • 自定义的变量名必须和通知方法的形参名一样

@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
            returning = "res")
    public void myAfterReturning(Object res) {
        //Object res : 是目标方法执行之后的返回值 , 根据返回值做你的切面的功能处理
        System.out.println("后置通知 : 目标方法的返回值是 = " + res);
    }

@Around :环绕通知

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

环绕通知方法的定义格式
 1.公共方法 public
 2./*必须有一个返回值*/ , 推荐使用Object
 3.方法名称自定义
 4.方法/*有参数*/ , 固定的参数 ProceedingJoinPoint
@Around :环绕通知
属性 : value : 切入点表达式
特点 :
     1.它是/*功能最强*/的通知
     2.在目标方法的/*前后段都能增强功能*/
     3.它能/*控制目标方法是否被调用执行*/
     4./*修改*/原来的/*目标方法*//*执行结果*/ , /*影响最后的调用结果*/
环绕通知 , 等同于JDK中的invoke()方法
参数 :
     ProceedingJoinPoint :等同于动态代理中的Method
作用 : 执行目标方法的
     它继承的是JoinPoint , 所以可以直接使用JoinPoint中的方法
返回值 : 是目标方法的执行结果 , 可以被修改

它相当于是把目标方法替换为了切面方法 , 这样就可以对该方法进行各种修改 , 
@Around(value = "execution( *  *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pj) throws Throwable {
        String name = "";
        Object[] args = pj.getArgs();
        if (args != null && args.length > 1) {
            Object arg = args[0];
            name = (String) arg;
        }
        //实现环绕通知 :
        System.out.println("环绕通知 : 在目标方法之前输出时间" + new Date());
        Object result = null;
        //1.目标方法的调用
        if ("思尘".equals(name)) {
            result = pj.proceed();
            //等同于Method中的invoke()方法 ; 
            //Object result = doFirst();
        }
        System.out.println("环绕通知 : 在目标方法之后 , 提交事务");
        //2.在目标方法的前后加入功能

        //修改目标方法的执行结果 , 影响方法最后的调用结果
        if (result != null) {
            result = "hello AspectJ AOP";
        }
        //返回目标方法的返回值
        return result;
    }

@AfterThrowing : 异常通知

目标方法抛出异常后执行 , 该注解的 throwing 属性用于指定所发生的异常类对象 , 当然 , 被注解为异常通知的方法 , 可以包含一个参数 Throwable , 参数名称为 throwing 指定的名称 , 表示发生的异常对象

异常通知方法的定义格式
	  1.公共方法 public
      2./*没有返回值*/
      3.方法名称自定义
      4.方法/*有一个参数 , Exception */
		   如果还有 , 就是JoinPoint
  @AfterThrowing : 异常通知
  属性 : 1.value 切入点表达式
        2.throwing 自定义的变量 , 表示目标方法抛出的异常对象
              /* 变量名必须和方法的参数名一样 */
  特点 :
      1./*在目标方法抛出异常时执行的*/
      2.可以做异常的监控程序 , 监控目标方法执行时是不是有异常
      如果有异常 , 可以发送邮件 , 短信进行通知

  /*
  	执行过程 : 
  		try {
  			目标方法的执行;
  		}catch(Exception e){
  			myAfterThrowing(e)
  		}
  */        
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))" , throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("方法在异常时执行"+ex.getMessage());
        //发送邮件或短信通知开发人员
    }

@After : 最终通知

无论目标方法是否抛出异常 , 该增强就会被执行 ,

最终通知的定义 : 
	1. 公共方法 public 
	2. /* 没有返回值 */
	3. 方法名称自定义
	4. 方法/* 没有固定参数 */, 可以有 JoinPoint 
@After : 最终通知
	属性 : value : 切入点表达式
    位置 : 在方法上边
特点 : 
	1.总是会执行
	2.在最后执行的 
	
 一般是做资源清除工作的   	

@Pointcut : 定义和管理切入点的 (代替重复的切入点表达式)

如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut

属性 : value 切入点表达式

位置 : 自定义的方法上面

特点 :

  • 当使用@Pointcut定义在一个方法上边 , 此时这个方法的名称就是切入点表达式的别名
  • 其他通知中 , value属性就可以使用这个方法名称(记得方法名后边要加括号) , 代替切入点表达式了

有接口和没接口使用的代理方式 :

有接口使用的是JDK的动态代理

没有接口使用的是cglib动态代理 (内部实现是 : 使用继承的方法生成目标类的子类 , 继承之后 重写方法)

在aspectJ框架中指定 , 即使有接口 也可以使用cglib的方式进行代理 :

在主配置文件中的自动代理生成器中设置一个属性
<aop:aspectj-autoproxy proxy-target-class="true"/>
告诉框架要使用cglib动态

源清除工作的


---

#### @Pointcut : 定义和管理切入点的 (代替重复的切入点表达式)

如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut

属性 : value 切入点表达式

位置 : 自定义的方法上面

特点 : 

- 当使用@Pointcut定义在一个方法上边 , 此时这个方法的名称就是切入点表达式的别名 
- 其他通知中 , value属性就可以使用这个方法名称(**记得方法名后边要加括号**) , 代替切入点表达式了

---

### 有接口和没接口使用的代理方式 : 

有接口使用的是JDK的动态代理

没有接口使用的是cglib动态代理 (内部实现是 : 使用继承的方法生成目标类的子类 , 继承之后 重写方法)

**在aspectJ框架中指定 , 即使有接口 也可以使用cglib的方式进行代理 :**

```xml
在主配置文件中的自动代理生成器中设置一个属性
<aop:aspectj-autoproxy proxy-target-class="true"/>
告诉框架要使用cglib动态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值