草庐IT

【Spring】难理解的Aop编程 | 入门?

狮子也疯狂 2023-04-10 原文

作者:狮子也疯狂
专栏:《spring开发》
坚持做好每一步,幸运之神自然会驾凌在你的身上

目录

一. 🦁 前言

继上两篇文章,狮子总结了Spring中IOC的底层原理、基本使用以及注解使用。今天来细说一下Spring的另外一个重要原理——面向切面编程(Aspect Oriented Programming),即我们常说的AOP。它是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

二. 🦁 常见概念

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度并有利于未来的可扩展性和可维护性
SpringAOP基于动态代理的如果要代理的对象实现了某个接口,那么SpringAOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

2.1 常见术语

名称说明
连接点(Joinpoint)指能被拦截到的点,在Spring中只有方法能被拦截
切点(Pointcut)指要对哪些连接点进行拦截,即被增强的方法。
通知(Advice)指拦截后要做的事情,即切点被拦截后执行的方法
切面(Aspect)切点+通知称为切面
目标(Target)被代理的对象
代理(Proxy)代理对象
织入(Weaving)生成代理对象的过程

2.2 AOP入门

现在来简单了解一下AOP的使用,SpringAOP中已经集成了AspectJ框架(应该是Java生态中最为完整的AOP框架了),我们使用该框架来实现一个简易的AOP功能。

Ⅰ. 🐇 功能场景

持久层的每个方法结束后都可以打印一条日志

Ⅱ. 🐇 实现过程

  1. 创建maven项目,并且引入以下依赖:
<!-- spring -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.12</version>
</dependency>
<!-- AspectJ -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>
<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
  1. 编写连接点(该处省略持久层接口)
@Repository
public class UserDaoImpl implements UserDao{
    public void add(){
        System.out.println("用户新增");
        |
        |
   }
    public void delete(){
        System.out.println("用户删除");
   }
    public void update(){
        System.out.println("用户修改");
   }
}
  1. 编写通知类
public class MyAspectJAdvice {
    // 后置通知
    public void myAfterReturning() {
        System.out.println("打印日志...");
   }
}
  1. 配置切面
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"   
xmlns:context="http://www.springframework.org/schema/context"    
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
xsi:schemaLocation="http://www.springframework.org/schema/beans     
http://www.springframework.org/schema/beans/spring-beans.xsd    
http://www.springframework.org/schema/context   
http://www.springframework.org/schema/context/spring-context.xsd     
http://www.springframework.org/schema/aop       
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan basepackage="com.jackie">
</context:component-scan>

 <!-- 通知对象 -->
<bean id="myAspectJAdvice"
class="com.jackie.advice.MyAspectAdvice"></bean>
<!-- 配置AOP -->
<aop:config>
 <!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut"
expression="execution(*com.jackie.dao.UserDao.*(..))"/>
<!-- 配置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
 </aop:aspect>
 </aop:config>
</beans>
  1. 测试
public class UserDaoTest {
    @Test
    public void testAdd(){
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.add();
   }
    @Test
   public void testDelete(){
     	ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
     	UserDao userDao = (UserDao)ac.getBean("userDao");
     	userDao.delete();
   }
    @Test
    public void testUpdate(){
     	ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.update();
   }
}

通过调用就会发现,每个方法都会打印语句:打印日志...。这就是AOP的使用入门。

2.3 通知类型

我们通过上面的案例会发现,这里写了一个后置通知,那么有哪些类型的通知呢?我们来看看:

通知类型描述
前置通知在方法执行前添加功能
后置通知在方法正常执行后添加功能
异常通知在方法抛出异常后添加功能
最终通知无论方法是否抛出异常,都会执行该通知
环绕通知在方法执行前后添加功能

我们通过案例来看看这些通知类型的实现以及使用

Ⅰ. 🐇 编写通知方法

通过构造MyAspectAdvice通知类,在里面编写通知方法。

// 通知类
public class MyAspectAdvice {
  // 后置通知
  public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("切点方法名:" + joinPoint.getSignature().getName());
    System.out.println("目标对象:" + joinPoint.getTarget());
    System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
   }

  // 前置通知
  public void myBefore() {
    System.out.println("前置通知...");
   }

  // 异常通知
  public void myAfterThrowing(Exception ex) {
    System.out.println("异常通知...");
    System.err.println(ex.getMessage());
   }

  // 最终通知
  public void myAfter() {
    System.out.println("最终通知");
   }

  // 环绕通知
  public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前");
    Object obj = proceedingJoinPoint.proceed(); // 执行方法
    System.out.println("环绕后");
    return obj;
   }
}

Ⅱ. 🐇 编写切面

<!-- 配置AOP -->
<aop:config>
  <!-- 配置切面 -->
  <aop:aspect ref="myAspectJAdvice">
    <!-- 配置切点 -->
    <aop:pointcut id="myPointcut" expression="execution(* com.jackie.dao.UserDao.*(..))"/>
    <!-- 前置通知 -->
    <aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
    <!-- 后置通知 -->
    <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
    <!-- 异常通知 -->
    <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
    <!-- 最终通知 -->
    <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
    <!-- 环绕通知 -->
    <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
  </aop:aspect>
</aop:config>

Ⅲ. 🐇 测试

在这里就不阐述了,跟上面的案例是一样的。

三. 🦁 切点表达式

我们上面的几个案例中都用到了切点表达式,即:

使用AspectJ需要使用切点表达式配置切点的位置。

3.1 使用语法

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

遵循以下习惯:

  • 访问修饰符可以省略、返回值使用 * 代表任意类型、包名使用 * 表示任意包,多级包结构要写多个 * ,使用 *.. 表示任意包结构
  • 类名和方法名都可以用 * 实现通配。
  • 参数列表
    基本数据类型直接写类型
    引用类型写 包名.类名
    *表示匹配一个任意类型参数
    .. 表示匹配任意类型任意个数的参数
  • 全通配: * *..*.*(..)

四. 🦁 总结

今天总结了AOP的基本使用方法。通过使用AspectJ框架来实现AOP的基本用法以及介绍了该框架的几种通知类型。今天的分享到这里,希望您喜欢!

有关【Spring】难理解的Aop编程 | 入门?的更多相关文章

  1. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  4. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  5. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  6. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  7. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  10. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

随机推荐