Spring AOP

Spring AOP的设计原理及思想

AOP(Aspect Oriented Programing)面向切面编程:即扩展功能不通过修改源代码来实现。
AOP采用横向抽取机制,取代传统的纵向继承体系实现响应的功能。

举个例子,比如说,我们需要在一个原有的功能上增加一个打印日志的操作,通常传统继承的方式,我们会定义一个日志类,然后继承使用。
而AOP的横向抽取机制,底层使用动态代理方式来实现一个代理对象,它增加了新的功能。

java程序的执行流

程序运行的过程就是方法调用的过程。我们按照方法执行的顺序,将方法调用排成一串,这样就构成了Java程序流。
将上述的线程栈里的方法调用按照执行流排列,会有如下类似的图
在这里插入图片描述
基于时间序列,我们可以将方法调用排成一条线。而每个方法调用则可以看成Java执行流中的一个节点。这个节点在AOP的术语中,被称为Join Point,即连接点。一个Java程序的运行的过程,就是若干个连接点连接起来依次执行的过程。

通常面向对象的程序,代码都是按照时间序列纵向展开的,而他们都有一个共性:即都是以方法调用作为基本执行单位展开的。将方法调用当做一个连接点,那么由连接点串起来的程序执行流就是整个程序的执行过程。
AOP(Aspect Oriented Programming)则是从另外一个角度来考虑整个程序的,AOP将每一个方法调用,即连接点作为编程的入口,针对方法调用进行编程。从执行的逻辑上来看,相当于在之前纵向的按照时间轴执行的程序横向切入。相当于将之前的程序横向切割成若干的面,即Aspect.每个面被称为切面。
所以,我们可以把他理解为AOP本质上是针对方法调用的编程思路。
在这里插入图片描述
AOP是针对切面进行的编程的,那么,你需要选择哪些切面(即 连接点Joint Point)作为你的编程对象呢?
因为切面本质上是每一个方法调用,选择切面的过程实际上就是选择方法的过程。那么,被选择的切面(Aspect)在AOP术语里被称为切入点(Point Cut). 切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程。
在这里插入图片描述
既然AOP是针对方法调用(连接点)的编程, 现在又选取了你自己感兴趣的链接点—切入点(Point Cut)了,那么,AOP能对它做什么类型的编程呢?AOP能做什么呢?
了解这个之前,我们先要知道一个非常重要的问题:既然AOP是对方法调用进行的编程,那么,AOP如何捕获方法调用的呢?弄清楚这个问题,下面我们先来了解一下引入了代理模式的Java程序执行流是什么样子的。

使用代理模式的Java程序执行流

我们假设在我们的Java代码里,都为实例对象通过代理模式创建了代理对象,访问这些实例对象必须要通过代理,那么,加入了proxy对象的Java程序执行流会变得稍微复杂起来。
我们来看下加入了proxy对象后,Java程序执行流的示意图
在这里插入图片描述
由上图可以看出,只要想调用某一个实例对象的方法时,都会经过这个实例对象相对应的代理对象, 即执行的控制权先交给代理对象。
代理模式属于Java代码中经常用到的、也是比较重要的设计模式。代理模式可以为某些对象除了实现本身的功能外,提供一些额外的功能,大致作用如下图所示:
在这里插入图片描述
加入了代理模式的Java程序执行流,使得所有的方法调用都经过了代理对象。对于Spring AOP框架而言,它负责控制着整个容器内部的代理对象。当我们调用了某一个实例对象的任何一个非final的public方法时,整个Spring框架都会知晓
在这里插入图片描述
既然Spring代理层可以察觉到你所做的每一次对实例对象的方法调用,那么Spring就有机会在这个代理过程中插入自己的业务代码 。

Spring AOP的工作原理

AOP编程首先要选择它感兴趣的连接点----即切入点(Point cut),那么,AOP能对切入点做什么样的编程呢?我们先将代理模式下的某个连接点细化,你会看到如下这个示意图所表示的过程
在这里插入图片描述
为了降低我们对Spring的AOP的理解难度,我在这里将代理角色的职能进行了简化,方便大家理解。(注意:真实的Spring AOP的proxy角色扮演的只能比这复杂的多,这里只是简化,方便大家理解,请不要先入为主)
代理模式的代理角色最起码要考虑三个阶段:
1.在调用真正对象的方法之前,应该需要做什么?
2.在调用真正对象的方法过程中,如果抛出了异常,需要做什么?
3.在调用真正对象的方法后,返回了结果了,需要做什么?

Spring AOP 根据proxy提供的类型名和方法签名,确定了在其感兴趣的切入点内,则返回AfterReturingAdivce处理建议,proxy得到这个处理建议,然后执行建议
在这里插入图片描述
上述的示意图中已经明确表明了Spring AOP应该做什么样的工作:根据proxy提供的特定类的特定方法执行的特定时期阶段给出相应的处理建议。要完成该工作,Spring AOP应该实现:
1.确定自己对什么类的什么方法感兴趣?-----即确定 AOP的切入点(Point Cut),这个可以通过切入点(Point Cut)表达式来完成;
2. 对应的的类的方法的执行特定时期给出什么处理建议?------这个需要Spring AOP提供相应的建议 ,即我们常说的Advice。
在这里插入图片描述

AOP相关术语

连接点(JoinPoint):指的是那些被拦截到的点,在Spring这些点指的是方法,SPring中支持方法类型的连接点类可以被增强,这些方法称之为连接点

切入点(PointCut):指的是我们要对那些连接点进行拦截的定义,在类中很多的方法都可以被拦截,实际被拦截的方法称之为切入点

增强/通知(Advice):指的是拦截到连接点之后要做的事情就是通知。通知分为5中通知方式
前置通知:在真正实现方法之前执行
后置通知:在方法之后执行
异常通知:在方法执行出现异常时才执行
最终通知:在后置通知之后才执行
环绕通知:在方法之前和之后执行

切面(Aspect):是切入点和通知的结合,把通知应用到切入点的过程

引介(Introduction),引介是一种特殊的通知在不修改代码的前提下,引介可以在运行期为类动态的添加一些方法或属性

目标对象(Target):代理的目标对象,要增强的类

代理(Proxy):一个被AOP织入增强后,就铲射了一个结果代理类

织入(Weaving):是把通知应用到目标对象的过程,把advice应用到target的过程

AOP基于Aspectj配置的实现

在spring中AOP的使用是通过Aspectj第三方框架实现的,Aspectj是java语言实现的一个专门的面向切面编程的框架
Aspectj不是spring框架的一部分,和Spring一起实现AOP的操作

引入依赖

        <!--spring AOP相关jar-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.4</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

引入AOP相关约束

<?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:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

   http://www.springframework.org/schema/aop

   http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

实现类

public class Student {
    public void addStudent(){
        System.out.println("Student.addStudent");
    }
}

增强类

public class DIYLog {
    public void writeLog(){
        System.out.println("DIYLog.writeLog");
    }
}

通过表达式来配置切入点

execution函数

在实现通知的过程中,通过execution函数,可以定义切入点的方法切入
格式:execution(<访问修饰符>?<返回类型><方法名>(<参数>) <异常> )
回顾:方法格式:访问限定符 返回类型 方法名(参数) 异常
(1)execution(* com.tulun.bean.Book.show(…)) 表类里面的某一个方法
(2)execution(* com.tulun.bean.Book.(…)) 表类某个包里类所有方法
(3)execution(
.(…)) 表示所有
例:

  • 匹配所有类public方法 execution(public .(…))
  • 匹配指定包下所有类方法 execution(* com.tulun.bean.*(…)) (不包含子包)
  • execution(* com.tulun.bean…*(…)) (包含包、子包下所有类)
  • 匹配指定类所有方法 execution(* com.tulun.bean.Book.*(…))
  • 匹配实现特定接口所有类方法 execution(* com.tulun.bean.Book+.*(…))
  • 匹配所有com开头的方法 execution(* com*(…))

配置AOP

  <!--配置对象-->
    <bean id="student" class="com.tulun.bean.Student"/>
    <bean id="log" class="com.tulun.bean.DIYLog"/>

    <!--配置AOP操作-->
    <aop:config>
        <!--配置切入点:使用execution表达式-->
        <aop:pointcut id="pointcut1" expression="execution(* com.tulun.bean.Student.addStudent())"/>
        
        <!--配置切面:把通知应用到方法的过程-->
        <aop:aspect ref="log">
            <!--配置增强类型 method属性:指定曾倩类中的那个方法-->
            <aop:before method="writeLog" pointcut-ref="pointcut1"/>
        </aop:aspect>

    </aop:config>

使用

 //获取IOC容器,通过读取classpath路径下的spring的配置文件
        String path = "spring-aop.xml";
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext(path);
        //在IOC容器获取需要的对象实例
        Student student = (Student) applicationContext.getBean("student");
        student.addStudent();


    /**
     * 环绕通知,必须有ProceedingJoinPoint类型的参数,该参数调用proceed表示执行真正的实现
     */
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("方法之前执行");
        //执行真正的实现方法
        joinPoint.proceed();

        System.out.println("方法之后执行");

    }

            <!--前置通知:aop:before-->
            <aop:before method="writeLog1" pointcut-ref="pointcut1"/>
            
            <!--后置通知:aop:after-->
            <aop:after method="writeLog1" pointcut-ref="pointcut1"/>
            
            <!--最终通知:aop:after-returning-->
            <aop:after-returning method="writeLog2" pointcut-ref="pointcut1"/>

            <!--异常通知:aop:after-throwing-->
            <aop:after-throwing method="around" pointcut-ref="pointcut1"/>

            <!--环绕通知:aop:around-->
            <aop:around method="around" pointcut-ref="pointcut1"/>

AOP基于注解形式实现

开启AOP的注解操作

    <!--打开AOP注解-->
    <aop:aspectj-autoproxy/>

在增强类上添加注解

@Aspect
public class DIYLog {

    @Before(value = "execution(* com.tulun.bean.Student.addStudent(..))")
    @After(value = "execution(* com.tulun.bean.Student.addStudent(..))") //后置通知
    @AfterReturning //最终通知
    @AfterThrowing //异常通知
    public void writeLog1(){
        System.out.println("DIYLog.writeLog1");
    }

    /**
     * 环绕通知,必须有ProceedingJoinPoint类型的参数,该参数调用proceed表示执行真正的实现
     */
    @Around(value = "execution(* com.tulun.bean.Student.addStudent(..))") //环绕通知
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("方法之前执行");
        //执行真正的实现方法
        joinPoint.proceed();

        System.out.println("方法之后执行");
    }
}

注意:
AOP相关操作的注解一定是在增强类和方法上
在增强类上添加注解@Aspect
在增强类中响应方法上根据不同的增强类型添加上不同注解
@Around(value = “execution(* com.tulun.bean.Student.addStudent(…))”) //环绕通知
@Before(value = “execution(* com.tulun.bean.Student.addStudent(…))”)
@After(value = “execution(* com.tulun.bean.Student.addStudent(…))”) //后置通知
@AfterReturning //最终通知
@AfterThrowing //异常通知

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

降温vae+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值