【Spring进阶系列丨第九篇】基于XML的面向切面编程(AOP)详解

2024-04-14 21:04

本文主要是介绍【Spring进阶系列丨第九篇】基于XML的面向切面编程(AOP)详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、基于XML的AOP
    • 1.1、打印日志案例
      • 1.1.1、beans.xml中添加aop的约束
      • 1.1.2、定义Bean
    • 1.2、定义记录日志的类【切面】
    • 1.3、导入AOP的依赖
    • 1.4、主配置文件中配置AOP
    • 1.5、测试
    • 1.6、切入点表达式
      • 1.6.1、访问修饰符可以省略
      • 1.6.2、返回值可以使用通配符,表示任意返回值
      • 1.6.3、包名可以使用通配符表示任意包。有几级包,就几个*
      • 1.6.4、类名也可以用*
      • 1.6.5、方法也可以用*
      • 1.6.6、参数列表
      • 1.6.7、全通配符写法
      • 1.6.8、使用最多的写法
    • 1.7、通知类型的使用
      • 1.7.1、在日志类中新增通知方法
      • 1.7.2、配置AOP
      • 1.7.3、测试
    • 1.8、切入点表达式改进
      • 1.8.1、方式一
      • 1.8.2、方式二
    • 1.9、环绕通知
      • 1.9.1、在日志记录类中新增环绕通知
      • 1.9.2、AOP配置环绕通知
      • 1.9.3、测试1
      • 1.9.4、解决
  • 好书推荐

在这里插入图片描述

一、基于XML的AOP

1.1、打印日志案例

1.1.1、beans.xml中添加aop的约束

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

1.1.2、定义Bean

package cn.bdqn.domain;
public class User {}
package cn.bdqn.service;
public interface UserService {// 保存用户public void save(User user);// 根据id查询用户public User queryById(Integer id);// 查询全部用户public List<User> queryAll();
}
package cn.bdqn.service;
public class UserServiceImpl implements UserService{// 保存用户public void save(User user){}// 根据id查询用户public User queryById(Integer id){return new User();}// 查询全部用户public List<User> queryAll(){return new ArrayList<User>();}
}

1.2、定义记录日志的类【切面】

package cn.bdqn.advice;// 定义记录日志的类,这个类就封装了我们所有的公共的代码
public class Logger {//  该方法的作用是在切入点方法执行之前执行public void beforePrintLog(){System.out.println("开始打印日志啦");}
}

1.3、导入AOP的依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>

1.4、主配置文件中配置AOP

<beans><!--  1、注册UserServiceImpl这个Bean  --><bean id="userService" class="cn.bdqn.service.UserServiceImpl"/><!--  2、以下操作都是Spring基于XML的AOP配置步骤2.1 把通知/增强Bean也需要注册到Spring容器中2.2 使用<aop:config/>标签来去声明开始AOP的配置了2.3 使用<aop:aspect/>标签来去表示开始配置切面了可以想一下:既然要配置切面,那切面就是切入点和通知的结合,所以肯定需要配置切入点和通知这两部分id属性:是给切面提供一个唯一标识ref属性:是指定通知类bean的Id。2.4 在<aop:aspect/>标签的内部使用对应标签来配置通知的类型前置通知/后置通知/异常通知/最终通知需求:beforePrintLog方法在切入点方法执行之前之前:所以是前置通知前置通知:<aop:before/>method属性:用于指定Logger类中哪个方法是前置通知pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强3、切入点表达式的写法:关键字:execution(表达式)表达式:访问修饰符  方法返回值  包名1.包名2...类名.方法名(参数列表)需求:我现在就想对UserServiceImpl类中的queryAll方法进行拦截public java.util.List cn.bdqn.service.UserServiceImpl.queryAll()--><!--  2.1 把通知/增强Bean也需要注册到Spring容器中  --><bean id="logger" class="cn.bdqn.advice.Logger"/><!--  2.2 使用此标签来去声明开始AOP的配置了--><aop:config><!--配置切面 --><aop:aspect id="loggerAdvice" ref="logger"><!-- 配置通知的类型,并且建立增强方法和切入点方法的关联--><aop:before method="beforePrintLog" pointcut="execution(public java.util.List cn.bdqn.service.UserServiceImpl.queryAll())"/></aop:aspect></aop:config>
</beans>

1.5、测试

在这里插入图片描述

@Test
public void testUserServiceImpl() throws Exception{ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");UserService userService = (UserService) ac.getBean("userService");userService.queryAll();
}

1.6、切入点表达式

​ 问题:我们上面的案例经过测试发现确实在调用业务方法之前增加了日志功能,但是问题是仅仅能针对某一个业务方法进行增强,而我们的业务方法又有可能有很多,所以显然一个一个的去配置很麻烦,如何更加灵活的去配置呢?这个就需要使用到切入点表达式

​ 语法:execution(表达式)

访问修饰符  方法返回值  包名1.包名2...类名.方法名(参数列表)

1.6.1、访问修饰符可以省略

// 完整写法
public java.util.List cn.bdqn.service.UserServiceImpl.queryAll())// 标准写法
java.util.List cn.bdqn.service.UserServiceImpl.queryAll())

1.6.2、返回值可以使用通配符,表示任意返回值

* cn.bdqn.service.UserServiceImpl.queryAll())

1.6.3、包名可以使用通配符表示任意包。有几级包,就几个*

* *.*.*.UserServiceImpl.queryAll())

但是对于包来说,连续的写3个*,显然也是麻烦的,那么可以使用“…”表示当前包及其子包。

// 表示的是任意包下的只要有UserServiceImpl类都会对queryAll方法进行增强
* *..UserServiceImpl.queryAll())

1.6.4、类名也可以用*

* *..*.queryAll()

1.6.5、方法也可以用*

* *..*.*()

1.6.6、参数列表

写法1、可以直接写数据类型:基本类型直接写名称           int、double引用类型写包名.类名的方式   java.lang.String、java.util.List
写法2、可以使用通配符表示任意类型前提是必须要有参数。写法3、使用..可以使用..表示有无参数均可,如果有参数则表示的可以是任意类型	

1.6.7、全通配符写法

 * *..*.*(..)

1.6.8、使用最多的写法

​ 实际中的写法:切到业务层实现类下的所有方法。即:

* com.bdqn.service.impl.*.*(..)

1.7、通知类型的使用

1.7.1、在日志类中新增通知方法

// 定义记录日志的类,这个类就封装了我们所有的公共的代码
public class Logger {//  该方法的作用是在切入点方法执行之前执行public void beforePrintLog(){System.out.println("前置通知(beforePrintLog):开始打印日志啦");}//  该方法的作用是在切入点方法执行之后执行public void afterReturningPrintLog(){System.out.println("后置通知(afterReturningPrintLog):业务方法执行完了,日志打印");}//  该方法的作用是在切入点方法执行出错后执行public void afterThrowingPrintLog(){System.out.println("异常通知(afterThrowingPrintLog):业务方法出现异常了,日志打印");}//  该方法的作用是在切入点方法执行之后不管有没有错误,都最终要执行public void afterPrintLog(){System.out.println("最终通知(afterPrintLog):业务方法不管有没有异常了,日志打印");}
}

1.7.2、配置AOP

<beans><!--  2.1 把通知/增强Bean也需要注册到Spring容器中  --><bean id="logger" class="cn.bdqn.advice.Logger"/><!--  2.2 使用此标签来去声明开始AOP的配置了--><aop:config><!--配置切面 --><aop:aspect id="loggerAdvice" ref="logger"><!-- 配置前置通知:在切入点方法执行之前执行--><aop:before method="beforePrintLog" pointcut="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/><!-- 后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个--><aop:after-returning method="afterReturningPrintLog"  pointcut="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/><!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--><aop:after-throwing method="afterThrowingPrintLog"  pointcut="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/><!--配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--><aop:after method="afterPrintLog"pointcut="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/></aop:aspect></aop:config>
</beans>

1.7.3、测试

@Test
public void testUserServiceImpl() throws Exception{ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");UserService userService = (UserService) ac.getBean("userService");userService.queryAll();
}
/***前置通知(beforePrintLog):开始打印日志啦查询全部用户执行啦后置通知(afterReturningPrintLog):业务方法执行完了,日志打印最终通知(afterPrintLog):业务方法不管有没有异常了,日志打印
**/

1.8、切入点表达式改进

​ 通过11.7可以发现,我们在配置文件中配置了四种通知类型,其中的pointcut配置的是切入点表达式,发现是一模一样的,那么有没有一种改进写法呢?可以将表达式抽取出来,将来可以引用。

1.8.1、方式一

<beans><!--  1、注册UserServiceImpl这个Bean  --><bean id="userService" class="cn.bdqn.service.UserServiceImpl"/><!--  2、以下操作都是Spring基于XML的AOP配置步骤--><!--  2.1 把通知/增强Bean也需要注册到Spring容器中  --><bean id="logger" class="cn.bdqn.advice.Logger"/><!--  2.2 使用此标签来去声明开始AOP的配置了--><aop:config><!--配置切面 --><aop:aspect id="loggerAdvice" ref="logger"><!--配置切入点表达式id属性用于指定切入点表达式的唯一标识。expression属性用于指定表达式内容此标签写在aop:aspect标签内部只能当前切面使用。--><aop:pointcut id="loggerPt" expression="execution(* 																					cn.bdqn.service.UserServiceImpl.queryAll())"/><!-- 配置前置通知:在切入点方法执行之前执行--><aop:before method="beforePrintLog" pointcut-ref="loggerPt"/><!-- 后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个--><aop:after-returning method="afterReturningPrintLog" pointcut-ref="loggerPt"/><!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--><aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="loggerPt"/><!--配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--><aop:after method="afterPrintLog" pointcut-ref="loggerPt"/></aop:aspect></aop:config>
</beans>

1.8.2、方式二

​ 对于方式一,我们将aop:pointcut标签写在了aop:aspect里面,这样的话这切入点表达式只能被当前的切面使用,而如果其他切面想使用就使用不到了,所以我们可以把这个切入点表示再定义到外面。

<beans><bean id="userService" class="cn.bdqn.service.UserServiceImpl"/><!--  2、以下操作都是Spring基于XML的AOP配置步骤--><!--  2.1 把通知/增强Bean也需要注册到Spring容器中  --><bean id="logger" class="cn.bdqn.advice.Logger"/><!--  2.2 使用此标签来去声明开始AOP的配置了--><aop:config><!--配置切入点表达式id属性用于指定切入点表达式的唯一标识。expression属性用于指定表达式内容此标签写在aop:aspect标签外面,那么所有的切面都可以使用。--><aop:pointcut id="loggerPt" expression="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/><!--配置切面 --><aop:aspect id="loggerAdvice" ref="logger"><!-- 配置前置通知:在切入点方法执行之前执行--><aop:before method="beforePrintLog" pointcut-ref="loggerPt"/><!-- 后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个--><aop:after-returning method="afterReturningPrintLog" pointcut-ref="loggerPt"/><!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--><aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="loggerPt"/><!--配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--><aop:after method="afterPrintLog" pointcut-ref="loggerPt"/></aop:aspect></aop:config>
</beans>

1.9、环绕通知

1.9.1、在日志记录类中新增环绕通知

public class Logger {// 环绕通知public void aroundPrintLog(){System.out.println("环绕通知....aroundPrintLog.....");}
}

1.9.2、AOP配置环绕通知

<beans><!--  1、注册UserServiceImpl这个Bean  --><bean id="userService" class="cn.bdqn.service.UserServiceImpl"/><!--  2、以下操作都是Spring基于XML的AOP配置步骤--><!--  2.1 把通知/增强Bean也需要注册到Spring容器中  --><bean id="logger" class="cn.bdqn.advice.Logger"/><!--  2.2 使用此标签来去声明开始AOP的配置了--><aop:config><!--    配置切入点表达式    --><aop:pointcut id="loggerPt" expression="execution(* cn.bdqn.service.UserServiceImpl.queryAll())"/><!--配置切面 --><aop:aspect id="loggerAdvice" ref="logger"><!-- 环绕通知--><aop:around method="aroundPrintLog" pointcut-ref="loggerPt"/></aop:aspect></aop:config>
</beans>

1.9.3、测试1

@Test
public void testUserServiceImpl() throws Exception{ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");UserService userService = (UserService) ac.getBean("userService");userService.queryAll();
}
/**环绕通知....aroundPrintLog.....发现:仅仅打印了环绕通知的代码。当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
*/

1.9.4、解决

​ Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。

public class Logger {// 环绕通知public Object aroundPrintLog(ProceedingJoinPoint pjp){Object result = null;try{Object[] args = pjp.getArgs();System.out.println(pjp.getSignature().getName());System.out.println("前置");result = pjp.proceed(args);System.out.println("后置");return result;}catch (Throwable t){System.out.println("异常");throw new RuntimeException(t);}finally {System.out.println("最终");}}
}
/**环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/

好书推荐

在这里插入图片描述

《深入浅出Spring Boot 3.x》

> 对于Java开发人员来说,Spring是必须学习的框架。

作者简介

杨开振——长期从事Java开发工作,拥有近十年的Java开发经验,目前就职于一家互联网金融公司,担任互联网软件开发职位。
IT技术的狂热爱好者,热衷于Java互联网方向的软件技术开发与研究。熟练掌握Java基础、软件开发设计模式和数据库相关知识,对Spring、MyBatis等主流Java开源框架有深入研究。

购书链接:点此进入

在这里插入图片描述

这篇关于【Spring进阶系列丨第九篇】基于XML的面向切面编程(AOP)详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/904012

相关文章

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

详解C#如何提取PDF文档中的图片

《详解C#如何提取PDF文档中的图片》提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使用,下面我们就来看看如何使用C#通过代码从PDF文档中提取图片吧... 当 PDF 文件中包含有价值的图片,如艺术画作、设计素材、报告图表等,提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

C#数据结构之字符串(string)详解

《C#数据结构之字符串(string)详解》:本文主要介绍C#数据结构之字符串(string),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录转义字符序列字符串的创建字符串的声明null字符串与空字符串重复单字符字符串的构造字符串的属性和常用方法属性常用方法总结摘