Spring学习笔记-AOP(XML配置)

AOP简介

什么是OOP?

什么是AOP?

AOP的作用

AOP优势

AOP入门案例

AOP相关概念

(1)连接点 

(2)切入点

(3)通知

(4)切面

(5)目标对象 

(6)织入

(7)代理

AOP开发过程

AOP入门案例

项目骨架

详细代码 

代码分析 

AOP配置(XML)

AspectJ

AOP配置

(1)aop:config

(2)aop:aspect

(3)aop:pointcut

切入点

定义

切入点表达式的组成

切入点的三种配置方式

切入点配置经验

通知类型

(1)aop:before(前置通知)

(2)aop:after(后置通知)

(3)aop:after-returning(返回后通知)

(4)aop:after-throwing(抛出异常后通知)

(5)aop:around(环绕通知)

通知顺序

通知获取数据

(1)通知获取参数数据

(2)通知获取返回值数据

(3)通知获取异常数据


AOP简介

什么是OOP?

我们之前开发是使用三层架构的方式,每做一个模块都要开发他的表现层、业务层、数据层。

即OOP模式,我们关心的是一一个模块中的功能。

所以在开发中,就会有较多相似的代码。

什么是AOP?

例如下述代码,我们可以从中抽取出共性的代码,使用AOP,在运行的时候,再将该环境还给对象。

AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构

AOP弥补了OOP的不足,基于OOP基础之上进行横向开发

  • OOP规定程序开发以主体模型,一切围绕对象进行,完成某个任务先构建模型

  • AOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先构建可能遇到的所有共性功能(当所有功能都开发出来也就没有共性与非共性之分)

AOP的作用

  • 伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开发完毕,最终以功能组合来完成个别业务模块乃至整体业务系统的开发

  • 目标:将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”搭建

AOP优势

  • 提高代码的可重用性

  • 业务代码编码更简洁

  • 业务代码维护更高效

  • 业务功能扩展更便捷

AOP入门案例

AOP相关概念

(1)连接点 

不管是接口中的方法,还是实现类中的方法,只要是一个方法叫做连接点

(2)切入点

在后期会被抽取出共性功能方法,叫做切入点

切入点和连接点都是方法;但是连接点是指所有方法,而切入点只是指被抽取出共性功能方法。

(3)通知

被挖掉的共性功能叫做通知,最终以一个方法的形式呈现。

(4)切面

切入点通知间的关系叫做切面,即:共性功能与挖的位置的对应关系

(5)目标对象 

挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的。

(6)织入

目标对象是不能直接运行的,我们只有将其原始功能“放回去”才可以运行。这个放回去的过程叫做织入

(7)代理

目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现

小结 

  • Joinpoint(连接点):就是方法

  • Pointcut(切入点):就是挖掉共性功能的方法

  • Advice(通知):就是共性功能,最终以一个方法的形式呈现

  • Aspect(切面):就是共性功能与挖的位置的对应关系

  • Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的

  • Weaving(织入):就是将挖掉的功能回填的动态过程

  • Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现

  • Introduction(引入/引介) :就是对原始对象无中生有的添加成员变量或成员方法

AOP开发过程

  • 开发阶段(开发者完成)

    • 正常的制作程序

    • 非共性功能开发到对应的目标对象类中,并制作成切入点方法

    • 共性功能独立开发出来,制作成通知

    • 配置文件中,声明切入点

    • 在配置文件中,声明切入点通知间的关系(含通知类型),即切面

简单来说就是——做通知、做切入点、做切面!

  • 运行阶段(AOP完成)

    • Spring容器加载配置文件,监控所有配置的切入点方法的执行

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

AOP入门案例

项目骨架

详细代码 

UserServiceImpl实现类

public class UserServiceImpl implements UserService {

    public void save() {
        //被AOP抽取
        //System.out.println("共性功能");
        System.out.println("User Service running...");
    }
}

AOPAdvice 

//1.制作通知类,在一个类中定义一个方法用来完成共性功能
public class AOPAdvice {
    public void function() {
        System.out.println("共性功能");
    }
}

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        ">
    <!--3.开启AOP命名空间-->
    <bean id="userService" class="com.Harmony.service.impl.UserServiceImpl"/>

    <!--2.配置共性功能,成为Spring控制的资源-->
    <bean id="myAdvice" class="com.Harmony.aop.AOPAdvice"/>

    <!--4.配置AOP-->
    <aop:config>
        <!--5.配置切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <!--6.配置切面(切入点与通知的关系)-->
        <aop:aspect ref="myAdvice">
            <!--7.配置具体的切入点对应通知中的哪个操作方法-->
            <aop:before method="function" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

</beans>

UserApp程序运行入口

public class UserApp {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.save();
    }
}

代码分析 

我们重点关注XML文件中的内容即可。

在AOP中,需要我们手写的只有配置切入点配置通知、配置它们之间的关系切面。(3个东西)

<aop:config></aop:config>:表示aop的配置,是一对标签,里面写配置内容。

<aop:pointcut>:配置切入点,里面有两个属性 idexecution(表达式)        

<aop:aspect></aop:aspect>:配置切面,也是一对标签,里面有属性ref 表示哪一个通知对应的类。

<aop:before>:从原来的方法中的哪一个位置“挖来的”。before表示前面挖来的。

这只是大致描述,详细的请看下文!

AOP配置(XML)

AspectJ

  • Aspect(切面)用于描述切入点与通知间的关系,是AOP编程中的一个概念

  • AspectJ是基于java语言对Aspect的实现

AOP配置

(1)aop:config

作用:设置AOP(开启AOP)

归属<beans>标签

说明:一个<beans>中可以配置多个<aop:config>

<beans>
    <aop:config>...</aop:config>
    <aop:config>...</aop:config>
</beans>

(2)aop:aspect

作用:设置具体的AOP通知对应的切入点(切面)

归属<aop:config>标签

说明:一个<aop:config>标签中可以配置多个<aop:aspect>

<aop:aspect ref="myAdvice">
    <aop:before method="function" pointcut-ref="pt"/>
</aop:aspect>

ref属性:指定通知类的bean的id,即所有的通知都来自于idmyAdvice的bean中。

(3)aop:pointcut

作用:设置切入点       

归属<aop:config>标签、<aop:aspect>标签

说明:一个<aop:config>标签中可以配置多个<aop:pointcut>,且该标签可以配置在<aop:aspect>内。

<!--4.配置AOP-->
<aop:config>
    <!--5.配置切入点-->
    <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
    <!--6.配置切面(切入点与通知的关系)-->
    <aop:aspect ref="myAdvice">
        <!--7.配置具体的切入点对应通知中的哪个操作方法-->
        <aop:before method="function" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

id:配置切入点的名称为pt

expression:切入点的描述(下一节介绍)

pointcut-ref:表示使用上面名为 pt 的切入点

切入点

定义

  • 切入点描述的是某个方法

  • 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式

切入点表达式的组成

语法格式:

关键字(访问修饰符  返回值  包名.类名.方法名(参数)异常名)

关键字:描述表达式的匹配模式(参看关键字列表)

访问修饰符:方法的访问控制权限修饰符

类名:方法所在的类(此处可以配置接口名称)

异常:方法定义中指定抛出的异常

样例:

execution(public User com.Harmony.service.UserService.findById(int))

使用public修饰的返回值类型为User的在com.Harmony.service.UserService下的findById方法。

(1)关键字

常用的只有下述这两种

  • execution :匹配执行指定方法

  • args :匹配带有指定参数类型的方法

(2)通配符

3种类型,直接上图吧,见下方:

(3)逻辑表达式

  • && :连接两个切入点表达式,表示两个切入点表达式同时成立的匹配

  • || :连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配

  • ! :连接单个切入点表达式,表示该切入点表达式不成立的匹配

(4)范式

execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..)) 
execution(public void com..service.*.*(..))
execution(public void com.itheima.service.*.*(..))
execution(public void com.itheima.service.User*.*(..))
execution(public void com.itheima.service.*Service.*(..))
execution(public void com.itheima.service.UserService.*(..))
execution(public User com.itheima.service.UserService.find*(..))
execution(public User com.itheima.service.UserService.*Id(..))
execution(public User com.itheima.service.UserService.findById(..))
execution(public User com.itheima.service.UserService.findById(int))
execution(public User com.itheima.service.UserService.findById(int,int))
execution(public User com.itheima.service.UserService.findById(int,*))
execution(public User com.itheima.service.UserService.findById(*,int))
execution(public User com.itheima.service.UserService.findById())
execution(List com.itheima.service.*Service+.findAll(..))

切入点的三种配置方式

(1)公共切入点

<aop:config>
    <!--公共切入点-->
    <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
    <aop:aspect ref="myAdvice">
        <aop:before method="function" pointcut-ref="pt"/>
    </aop:aspect>
    <aop:aspect ref="myAdvice">
        <aop:before method="function" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

如果下述有多个<aop:aspect>,则它们都可以使用该切入点 pt         

(2) 局部切入点

<aop:config>
    <aop:aspect ref="myAdvice">
        <!--局部切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:before method="function" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

显然该切入点的作用域,无法影响其他的<aop:aspect>。

(3)直接配置切入点

该写法,使用率较低,可能导致代码复用性较差

<aop:config>
    <aop:aspect ref="myAdvice">
        <!--直接配置切入点-->
        <aop:before method="function" pointcut="execution(* *..*(..))"/>
    </aop:aspect>
</aop:config>

注意:这里就不用写pointcut-ref属性了,使用的是pointcut,值为原expression属性的属性值。

切入点配置经验

  • 企业开发命名规范严格遵循规范文档进行

  • 先为方法配置局部切入点

  • 再抽取中公共切入点

  • 最后抽取全局切入点

  • 代码走查过程中检测切入点是否存在越界性包含

  • 代码走查过程中检测切入点是否存在非包含性进驻

  • 设定AOP执行检测程序,在单元测试中监控通知被执行次数与预计次数是否匹配

  • 设定完毕的切入点如果发生调整务必进行回归测试

通知类型

AOP的通知类型共5种,前面4种方式

(1)aop:before(前置通知)

原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行

归属<aop:aspect>标签

作用:设置前置通知,用于数据校验

<aop:before method="before" pointcut-ref="pt"/>

(2)aop:after(后置通知

原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知

归属<aop:aspect>标签

作用:设置后置通知,用于现场清理

<aop:after method="after" pointcut-ref="pt"/>

(3)aop:after-returning(返回后通知

原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行

归属<aop:aspect>标签

作用:设置返回后通知 ,用于返回值相关数据处理

<aop:after-returning method="afterReturning" pointcut-ref="pt"/>

(4)aop:after-throwing(抛出异常后通知

原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行

归属<aop:aspect>标签

作用:设置返回后通知 ,用于对原始方法中出现的异常信息进行处理

 <aop:after-returning method="afterThrowing" pointcut-ref="pt"/>

(5)aop:around(环绕通知

在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行         

归属<aop:aspect>标签

作用:设置环绕通知 ,十分强大,可以做任何事情

使用方式较上面有一点特殊,具体使用如下

完整的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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        ">
    <bean id="userService" class="com.Harmony.service.impl.UserServiceImpl"/>
    <bean id="myAdvice" class="com.Harmony.aop.AOPAdvice"/>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:around method="around" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
    
</beans>

AOPAdvice

public class AOPAdvice {
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");
        //对原始方法的调用
        pjp.proceed();
        System.out.println("around after");
    }
}

注意:

使用<aop:around>的时候,在原始方法的前后添加功能,在环绕通知中,要存在对原始方法的显式调用,即:要告诉AOP在该方法中的写的内容哪些在原始方法前,哪些在原始方法后。        

环绕通知方法相关说明:

public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object ret = pjp.proceed();
    return ret;
}

我们上述案例使用的返回值类型为void,不过对于该案例是没有影响的! 

  • 方法须设定Object类型的返回值,否则会拦截原始方法的返回(后续讲解)。如果原始方法返回值类型为void,通知方也可以设定返回值类型为void,最终返回null

  • 方法需在第一个参数位置设定ProceedingJoinPoint对象,通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行

  • 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常强制抛出Throwable对象,封装原始方法中可能出现的异常信息(可以使用捕获,也可以使用try-catch)

通知顺序

当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准。

配置的顺序是什么样的,运行后的顺序就是什么样的!

通知获取数据

  • (1)参数

  • (2)返回值

  • (3)异常

(1)通知获取参数数据

第一种情况:

设定通知方法第一个参数JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组

public void before(JoinPoint jp) throws Throwable {
    Object[] args = jp.getArgs();
    System.out.println(args[0]);
}

所有的通知均可以获取参数

但是,这种方法太麻烦了!要拿jp对象,还要有args,之后还不是一个值,还要通过数组的方式访问。

第二种情况:

设定切入点表达式为通知方法传递参数(锁定通知变量名

注意点:

  • 在XML中想要使用 &&,需要通过转义字符—— &amp;&amp;
  • 通知类中的变量名是a,b和XML中必须使用a,b(必须同名!)(强绑定!)

第三种情况:

设定切入点表达式为通知方法传递参数(改变通知变量名的定义顺序

注意:

  • arg-names属性:指定参数的位置顺序,如上述所示。

(2)通知获取返回值数据

能拿到返回值的只有——<aop:after-returning><aop:around>

第一种:返回值变量名

设定返回值变量名,适用于返回后通知(after-returning)

原始方法

public int save() {
	System.out.println("user service running...");
    return 100;
}

AOP配置

<aop:aspect ref="myAdvice">
    <aop:pointcut id="pt3" expression="execution(* *(..))  "/>
    <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/>
</aop:aspect>

通知类

public void afterReturning(Object ret) {
    System.out.println(ret);
}

<aop:after-returning>的方式获取返回值,先要在XML中配置returning属性,这里属性名为ret,然后将其作为参数传入通知类中。

第二种:

在通知类的方法中调用原始方法获取返回值,适用于环绕通知(around)

原始方法

public int save() {
    System.out.println("user service running...");
    return 100;
}

AOP配置

<aop:aspect ref="myAdvice">
    <aop:pointcut id="pt2" expression="execution(* *(..))  "/>
    <aop:around method="around" pointcut-ref="pt2" />
</aop:aspect>

通知类

public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object ret = pjp.proceed();
    return ret;
}

<aop:around>的方式获取返回值,不需要动XML了!在通知类中要用Object作为返回值,对proceed()方法获取的对象进行保存,并返回。

(3)通知获取异常数据

能拿到异常的只有——<aop:after-throwing><aop:around>

第一种:通知类的方法中调用原始方法捕获异常

适用于环绕通知(around)

原始方法

public void save() {
    System.out.println("user service running...");
    // 此处必报异常!
    int i = 1/0;
}

AOP配置

<aop:aspect ref="myAdvice">
    <aop:pointcut id="pt4" expression="execution(* *(..))  "/>
    <aop:around method="around" pointcut-ref="pt4" />
</aop:aspect>

通知类

public Object around(ProceedingJoinPoint pjp) {
    Object ret = null;
    try {
        ret = pjp.proceed();	
    } catch (Throwable throwable) {
         throwable.printStackTrace();
    }
    return ret;
}

使用try-catch获取异常

第二种:设定异常对象变量名

适用于返回后通知(after-throwing)

原始方法

public void save() {
    System.out.println("user service running...");
    int i = 1/0;
}

AOP配置

<aop:aspect ref="myAdvice">
	<aop:pointcut id="pt4" expression="execution(* *(..))  "/>
    <aop:after-throwing method="afterThrowing" pointcut-ref="pt4" throwing="t"/>
</aop:aspect>

通知类

public void afterThrowing(Throwable t){
    System.out.println(t.getMessage());
}

在XML中设置throwing属性,在通知类中将该属性传入,并使用getMessage方法获取。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

金鳞踏雨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值