【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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听