第十一节_spring_AOP切面编程

AOP-面向切面编程思想

1、图解概述

image-20220328171546834

  • 简单来说就是在程序运行过程中执行的一种技术,通过预编译与运行期的动态代理实现程序功能的统一维护的技术
  • 动态代理:就是,不修改原码的情况下,对我的目标方法,进行一个增强,并且可以完成程序之间的松耦合

2、AOP的作用和优势

2.1、流程图解

image-20220328173332661

image-20220328173722527

image-20220328173924602

2.2、AOP的思想及底层的实现

  • 在Spring中,Spring底层通过动态代理的方式,实现类AOP的思想,通过动态代理,动态的生成代理对象,来完成对功能的一个增强

2.3、AOP动态代理技术

常见的动态代理技术有俩种

  • JDK代理:基于接口的动态代理技术(目标对象必须有接口,没有接口无法生成代理对象)

  • image-20220329082036669

  • cglib代理:基于父类的动态代理技术(第三方动态代理小工具,为目标对象找个子类,这个子类的功能比父类强大)

  • image-20220329082359704

3、jdk动态代理技术代码实现

3.1、分析

1、我们以target接口和他的实现类作为我们的目标对象,内部实现了一个save方法

image-20220329104543384

2、而现在我想要使用AOP的思想对我这个接口实现类的功能进行一个增强,我新建了一个类,内部包含两个增强的方法

image-20220329104630083

可以看出来,这三个方法,目前是没有任何联系的,如何将他们串起来?

3、映射,Proxy.newProxyInterface()

内部需要三个参数

  • 目标对象的类加载器–xxx.getClass().getClassLoader()//目标对象类加载器
  • 与目标对象相同的接口字节码对象数组–xxx.getClass().getInterfaces();
    • 为什么是数组,因为目标对象可能不是一个接口,java是单继承多实现的
  • 第三个参数是接口,new InvocationHandler
    • 内部包含一个invock方法,其实我们在调用代理对象的时候执行的就是这个invock方法

4、图解

image-20220329151922823

5、实操

在原有基础上创建一个类,老师举的那个匿名内部类看的不是很清晰,实际上,第三个参数实际上是一个接口,该接口有一个方法

image-20220329154040966

调用该接口的时候会执行这个invoke方法,而我们只是使用这个方法进行一个相对应的增强

invoke方法需要的参数,一就是我们创建好的那个Object对象proxy,代理对象,第二个参数就是我们需要被增强方法的那个参数的类,第三个参数是,第二个参数,要执行的方法,所携带的参数,所以他是一个数组类型的

我们使用一个类去实现InvocationHandler的接口,并对他的invoke方法进行一个重写,也就是我们所谓的增强

public class MyInvocationHandler implements InvocationHandler {
    // 目标对象
    private targetImpl target;

    public  MyInvocationHandler(targetImpl target){
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass().getName());
        Object invoke = method.invoke(target, args);
        System.out.println("后置增强......");
        return invoke;
    }
}

我发现一个问题,这个返回的invoke是一个空值,也就是说这个返回的invoke没多大的鸟用,我们只需要执行这个方法就行,方法的组装也是在这里完成的

main方法中的代码,增强后的代码进行一个查看

public static void main(String[] args) {
    // 目标方法对象
    targetImpl target = new targetImpl();
    // 增强目标的方法对象
    targetEach targetEach = new targetEach();
    com.waves.proxyJDK.target proxy = (com.waves.proxyJDK.target) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new  MyInvocationHandler(target)
    );
    // 使用代理对象调用我们的增强方法
    proxy.save();
}

image-20220329164031148

6、AOP的思想

就是在不改变原先代码的情况,解耦合,并且实现相对应的功能,可以看到,我在target的实现类中完全没有new 我要增强的对象,他们之间并无关系,但是可以相互组合,要学习这个思想,而不是会用就行

4、cglib的动态代理

这个是基于父类实现的,我这有个方法需要被增强,我这个代理对象,是被增强方法的儿子

4.1、cglib动态代理的步骤

  • 创建代理对象Enhancer对象

    • image-20220329172345201
  • 设置我们代理对象的父类

  • image-20220329172402012

  • 设置回调函数

    • // 设置回调函数
      enhancer.setCallback(new MethodInterceptor() {
          /**
           *
           * @param proxy 生成的代理对象
           * @param method 需要实现方法
           * @param args 参数集合
           * @param methodProxy 这个intercept的代理对象
           * @return 鸡巴用
           * @throws Throwable
           */
          @Override
          public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
              // 增强
              targetEach.befor();
              //执行目标
              Object invoke = method.invoke(target, args);
              //后续增强
              targetEach.afterAdvice();
              return invoke;
          }
      });
      
  • 开始创建我们的代理对象

  • image-20220329172446236

  • 实现方法

    • image-20220329172517291

5、AOP的正式学习

5.1、AOP的专业术语

1、Target(目标对象)

指代我们需要被代理的对象,由这个对象的代理对象来执行,增强我们这个对象当中需要被增强的方法

2、Proxy:代理

一个类被AOP代理,织入增强后,就会产生一个结果的代理类–Proxy

3、Joinpoint:连接点

其实这里的连接点指的就是我们的方法,这些方法被称之为–连接点,因为Spring只支持方法类型的连接点,因为需要对这些方法,拦截,并且增强他们的功能

4、Pointcut:切入点

切入点就是我们需要对哪些连接点进行拦截,并对其增强;切入点属于连接点的一部分,范围肯定没有连接点大

5、Advice:通知-增强

官方翻译为通知,实际上就是我们对连接点的一个增强

6、Aspect:切面

由切入点和增强的结合,就叫做切面

7、Weaving:织入

指的是把增强的功能应用到目标对象来创建新的代理对象的过程(切点和增强结合到一起的过程),这个过程叫做织入;Spring采用的是动态代理的织入,通过配置进行相应的配置

5.2、AOP开发需要注意的事项

1、需要编写的内容

  • 编写业务核心代码(目标类的目标方法)
  • 编写切面类,切面类中有增强的方法
  • 配置文件当中,配置织入关系,哪些切点和哪些通知需要进行相应的结合

2、AOP技术实现的内容

Spring框架监控切入点的方法的执行,而切点这个东西使我们通过配置文件进行配置的,哪些方法,是切点,当执行,调用这个方法时,说明切面类的某个方法被执行了,一执行,Spring就监控到了,监控到了以后,Spring就会启动代理机制,动态的创建你这个切点方法,所在的目标对象的deep对象?

切点就是一堆方法,集合,你在执行这些方法中的某一个方法时,只要一执行,就被Spring监控到了,你这个方法是不是在某个对象中,这个对象就是我们的目标对象,这个Spring就会为当前方法所在的目标对象动态的创建代理对象,是指的这个意思

创建了代理对象之后干嘛呢?他会根据你配置的,增强的类型(前置增强,后置增强,很多种),根据你配置的什么通知,在代理对象对应的位置,对你的目标方法,进行一个相应的增强方法的介入

3、AOP底层到底是使用哪种代理方式的?

jdk和cglib

在Spring中,框架会根据类是否实现了接口来决定采用哪种动态代理的方式

4、知识要点

  • aop:面向切面编程

  • aop底层实现:基于JDK和Cglib的动态代理

  • aop的重点概念:

    • Pointcut:切点:被增强的方法
    • Advice:增强:封装增强业务逻辑的方法
    • Aspect:切面:切点+通知
    • Weaving:织入:将切点和增强结合的这一过程
  • 开发明确事项:

    • 谁是切点:要被增强的方法,切点表达式配置
    • 谁是通知:切面类中的增强方法
    • 将切点和通知进行织入的配置

6、基于XML方式进行AOP的开发

6.1、快速入门

图解

image-20220329203415027

1、导入AOP的相关坐标

其实Spring内部包含了AOP的接口,在Spring-context中,但是光有这个还不行,因为实际上这个时候底层实现的代理是Aspect的方式进行的,所以还是要导入相关的坐标。

导入aspectjweaver,这个第三方的资源比spring更轻量级,并且官方文档也注明去使用aspectjweaver来进行AOP的相关操作

<!-- 导入aspectjweaver配置 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9</version>
</dependency>

2、创建目标接口和目标类(内部有切点)

image-20220329205208042

3、创建切面类

// 切面类
public class MyAspect {
    // 内含一些增强方法

    public void befor(){
        System.out.println("前置增强.....");
    }
    public void afterfor(){
        System.out.println("后置增强.....");
    }

}

4、将目标类和切面类的对象创建交给Spring

为了更好的控制这些对象,将控制权限交给Spring容器

<!-- 目标对象 -->
<bean id="target" class="com.waves.aop.target.impl.targetImpl"></bean>

<!-- 切面类 -->
<bean id="ascept" class="com.waves.aop.aspect.MyAspect"></bean>

5、在applicationContext.xml中配置织入关系

告诉Spring容器是那些切点,哪些目标方法,需要被增强,被增强的是哪一种?

  • 引入aop命名空间

  • image-20220329210224391

  • 告诉Spring,MyAspect是一个切面,通过配置告诉他

    • 因为现在Spring还不知道他是切面,目前而言他只是一个普通的bean,只是我们赋予了他这个切面的意义
  • <aop:config>
        <!-- 告诉Spring这是一个切面 -->
        <aop:aspect ref="aspect"></aop:aspect>
    </aop:config>
    
  • 配置切面—切点+通知(增强)

    • 在内部配置你需要增强的类型,然后指定是哪个方法需要被增强
  • <aop:config>
        <!-- 告诉Spring这是一个切面 -->
        <aop:aspect ref="aspect">
            <!-- 什么是切面?切点+通知 -->
            <!-- 前置增强,method:哪些方法是前置增强,增强的是哪些方法? -->
            <aop:before method="befor"
            pointcut="execution(public void com.waves.aop.target.impl.targetImpl.save())"/>
            <!-- 上面配置的是一个切点,内部是一个切点表达式 -->
        </aop:aspect>
    </aop:config>
    
  • 目光汇聚在内部,aop:before,是我的一个增强类型,内部装载的是我切面的一个方法-befor(),这个方法用来进行前置增强,后面配置一个切点表达式,也就是说,当我访问这个接口的实现类的save方法的时候,进行一个前置增强,增强的方法为befor()

6、测试代码

image-20220329213002509

总结,通过AOP这种方式我们实现了解耦合,想要就要不想要就拆掉

6.2、切点表达式的方式

 <aop:before method="befor"
        pointcut="execution(public void com.waves.aop.target.impl.targetImpl.save())"/>
1、aop:before:这是一种增强类型,在目标对象方法被增强前执行,配置好的增强方法
2、pointcut:切点表达式

其实上面看的出来,我们实际上就是对接口中的save方法进行一个增强,但是,只对save()方法进行增强显然不合适,将来可能有一对方法,来让其进行修饰

表达式的语法

image-20220329213719616

注意事项

  • 访问修饰符可以省略
  • 返回值的类型、包名、类名、方法名可以使用*号代表任意
  • 包名和类名之间的一个**.**代表当前包下的类,两个…代表当前包及其子包下的类
  • 参数列表**(save(…))**,可以使用两个点,代表任意个数、任意类型的参数

3、直接看老子的切点配置精华图

image-20220329214833803

6.3、通知的种类

除了前置通知还有哪些通知?

image-20220329215042039

1、环绕通知–增强

一般前后置方法执行的效果和环绕通知差求不多,所以采用环绕会稍微好点

  • 设计方法

  • // Proceeding  JoinPoint  正在执行   的切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕增强(前置增强).......");
            Object proceed = pjp.proceed();
            System.out.println("环绕增强(后置增强).......");
            return proceed;
        }
    
  • 参数详解

    • ProceedingJoinPoint–正在执行的切入点
    • 通过这个参数的proceed来执行我们目标对象的方法
    • 有可能这个方法会有返回值,如果所以我们要返回一下
  • 配置文件配置

  • 返回类型任意,com.waves.aop.target包及其子包(impl)的所有方法被增强,参数类型任意,个数任意

  • <aop:around method="around" pointcut="execution(*com.waves.aop.target..*.*(..))"/>
    

2、异常抛出增强

  • 方法设计

    • 抛出异常执行这个方法

    • // 异常抛出通知
      public void afterThrowing(){
          System.out.println("异常抛出异常.....");
      }
      
  • 配置设计

    • <aop:after-throwing method="afterThrowing"
      pointcut="execution(* com.waves.aop.target..*.*(..))"/>
      
  • 设计一个自杀式异常(save方法)

    • 打印完save running…之后抛出一个除零异常

    • @Override
      public void save() {
          System.out.println("save running......");
          int i = 1/0;
      }
      
  • 测试–效果展示

  • image-20220329221246130

3、最终增强

不管有没得异常,老子必须执行

  • 设计方法

    • 不管有没得异常,老子必须执行

    • // 最终异常
      public void after(){
          System.out.println("最终增强.......");
      }
      
  • 配置文件配置

    • <aop:after method="after" 
      pointcut="execution(* com.waves.aop.target..*.*(..))"/>
      
  • 测试–效果

    • 依旧是除0异常
    • image-20220329221456261

6.4、切点表达式的抽取

当多个切点表达式的作用域相同的时候,可以使用pointcut-ref属性来代替pointcut

image-20220329231125050

  • 我这三个增强的切点表达式是不是都一样

    • <aop:around method="around" 
      pointcut="execution(* com.waves.aop.target..*.*(..))"/>
      <!-- 异常抛出异常 -->
      <aop:after-throwing method="afterThrowing" 
      pointcut="execution(* com.waves.aop.target..*.*(..))"/>
      <aop:after method="after"
      pointcut="execution(* com.waves.aop.target..*.*(..))"/>
      

1、使用pointcut-ref代替

  • 规划我们的切点表达式抽

    • <!-- 规划我们的切点表达式抽取 -->
      <aop:pointcut id="mypointcut" 
      expression="execution(* com.waves.aop.target..*.*(..))" />
      <!-- 环绕增强 -->
      <aop:around  method="around" pointcut-ref="mypointcut"/>
      <!-- 异常抛出增强 -->
      <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut"/>
      <!-- 最终增强 -->
      <aop:after method="afterThrowing" pointcut-ref="mypointcut"/>
      

2、最终效果

  • image-20220329231802111

6.5、知识要点

image-20220329232045926

7、基于注解的AOP开发

7.1、图解

7.2、快速入门

1、创建目标接口和目标类(内部有切点)

2、创建切面类(内部有增强方法)

image-20220329233910747

3、将目标类和切面类的对象权交给Spring

image-20220329233945514

image-20220329233957003

4、在切面类中使用注解织入关系

image-20220329234032228

  • 配置织入的关系,使用注解之后,我们只需要配置切点表达式即可
    • 配置切点表达式的目的,让spring知道是哪些切点需要被什么通知进行一个什么样的增强
4.1、注解配置
1、环绕通知注解–@Around(“切点表达式”)

image-20220329234644366

2、异常抛出通知注解–@AfterThrowing

image-20220329234715561

3、最终通知注解–@After

image-20220329234734084

5、在配置文件中开启组件扫描和AOP自动代理

image-20220329235429414

6、测试

新建测试包,读取我们新配置的xml配置文件

image-20220329235520720

7.3、注解配置AOP开发

1、图解

image-20220329235646370

2、切点表达式的抽取

image-20220330000008754

3、实现–@Pointcut(),他必须要在一个空方法上面,因为需要一个宿主

  • 内部的参数放我们相同的切点表达式

    • image-20220330000224443
  • 调用抽取的切点表达式的方式

    • 直接调用切点表达式的那个方法体

      • image-20220330000308467
    • 对象.切点表达式的方法体–的方式

      • image-20220330000350010
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值