【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的约束
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 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 queryAll(){ return new ArrayList(); } }
1.2、定义记录日志的类【切面】
package cn.bdqn.advice; // 定义记录日志的类,这个类就封装了我们所有的公共的代码 public class Logger { // 该方法的作用是在切入点方法执行之前执行 public void beforePrintLog(){ System.out.println("开始打印日志啦"); } }
1.3、导入AOP的依赖
org.aspectj aspectjweaver 1.9.4
1.4、主配置文件中配置AOP
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
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、方式一
1.8.2、方式二
对于方式一,我们将aop:pointcut标签写在了aop:aspect里面,这样的话这切入点表达式只能被当前的切面使用,而如果其他切面想使用就使用不到了,所以我们可以把这个切入点表示再定义到外面。
1.9、环绕通知
1.9.1、在日志记录类中新增环绕通知
public class Logger { // 环绕通知 public void aroundPrintLog(){ System.out.println("环绕通知....aroundPrintLog....."); } }
1.9.2、AOP配置环绕通知
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开发工作,拥有近十年的Java开发经验,目前就职于一家互联网金融公司,担任互联网软件开发职位。
IT技术的狂热爱好者,热衷于Java互联网方向的软件技术开发与研究。熟练掌握Java基础、软件开发设计模式和数据库相关知识,对Spring、MyBatis等主流Java开源框架有深入研究。
购书链接:点此进入