【Spring 】了解Spring AOP

news2024/11/18 1:24:54

目录

一、什么是Spring AOP

二、AOP的使用场景

三、AOP组成

四、Spring AOP的实现

1、添加Spring AOP依赖

2、定义切面和切点

3、定义相关通知

五、 AOP的实现原理

1、什么是动态代理 

2、 JDK代理和CGLIB代理的区别


一、什么是Spring AOP

AOP(Aspect Oriented Programming),直译过来就是面向切面编程,AOP是一种编程思想,是面向对象编程(OOP)的一种补充。Spring AOP是AOP思想的一种实现,就像DI一样是IoC的一种实现。

AOP的主要作用就是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点,减少对业务代码的侵入。增强代码的可读性和可维护性简单来所,AOP的作用就是保证开发者在不修改业务代码的前提下,位系统中的业务组件添加某种通用功能。

就比如实现一个用户登录权限的校验功能,就比如我们使用的博客,在要进入博客编辑的页面时,需要对你是否登录进行校验,如果已经登录,那么就可以进入编辑页,如果没有那么就需要在登录页面登录之后在进入。像这样需要登录校验的页面,我们使用AOP思想,只需要在某一处配置以下,所有的需要判断用户登录的页面就可以实现用户登录验证了。这样每个页面就只关注具体的业务逻辑了。

二、AOP的使用场景

就像上面举的例子,当你的程序中实现的页面越来越多,那么你要 写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本我们对这种功能统一,并且使用地方较多的功能,就可以考虑使用AOP来统一处理。当然AOP可以使用的场景还有很多。

  • 统一日志记录
  • 统一方法执行时间统计
  • 统一的返回格式设置
  • 统一的异常处理
  • 事务的开启和提交等

如果没有使用AOP思想来写代码,用户发送的请求直接被业务代码控制层接收到,请求访问的页面来校验用户是否登录。使用了AOP思想的代码,用户发送方的请求被AOP这里的代码先进行登录校验,如果登录,将请求传给控制层,如果没有登录就会被拦截。

三、AOP组成

1️⃣切面(Aspect):表示当前AOP是针对那些事件做处理的,用来登录的还是记录日志的。切面就是通知和切点的结合,通知和切点共同定义了切面的全部内容,他是干什么的,什么时候在哪里执行。通常以类的形式表示。

2️⃣切点(Pointcut):表示定义具体规则。切点其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点作为切点,如果说通知定义了切面的动作后者执行时机的话,切点则定义了执行的地点。

3️⃣通知(Advice):AOP执行的具体方法。有的地方叫增强。

4️⃣连接点(Join point):就是有可能触发切点的所有点。应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,异常抛出时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

四、Spring AOP的实现

1、添加Spring AOP依赖

在创建好的Spring Boot项目的pom.xml中添加Spring AOP的依赖,我们可以从中央仓库中下载

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后点击刷新,触发下载。

2、定义切面和切点

这里使用注解@Aspect表示定义切面,即UserAserAspect类为切面,使用@Component注解表示让切面随着框架的启动而启动,这样切面中的切点定义的拦截规则才能生效。

package com.example.demo.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect//定义切面
@Component//让切面随着框架的启动而启动
public class UserAspect {
    //定义切点,@Pointcut注解的参数中定义了具体的拦截规则。参数中使用AspectJ表达式语法
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){}
}

上述代码中,pointcut方法为空方法,它不需要又方法体,此方法名就是起到一个"标识"的作用,标识下面的通知方法具体指的是那个切点。因为一个切面中有很多切点。

上述pointcut方法上添加的@Pointcut注解的参数中使用切点表达式定义了具体的拦截规则

execution(* com.example.demo.controller.UserController.*(..))

切点表达的意思是拦截UserContrller类中的所有方法其参数为任意参数并且返回值为任意类型的返回值。

  • execution表示的意思为执行,执行的是后面跟的()中的规则。
  • *表示的多个部分组成的,有修饰符和返回值类型。
  • com.example.demo.controller.UserController表示要拦截com.example.demo.controller包中的UserController类
  • 类后面跟的*表示UserController类中的所有方法。
  • ..表示的不定式传参

切点表达式由切点函数组成,其中execution()最常见的切点函数用来匹配方法,语法为:

execution(<修饰符><返回值类型><包.类.方法(参数)><异常>)

常见表达式示例

  • execution(* com.example.demo.User.*(..)):匹配User类中的所有方法。
  • execution(* com.example.demo.User+.*(..)):匹配该类的子类包括该类的所有方法
  • execution(* com.example.*.*(..)):匹配com.example包下的所有类的所有方法
  • execution(* com.example..*.*(..)):匹配com.example包下,子孙包下所有类的所有方法
  • execution(* addUser(String,int)):匹配addUser方法,其第一个参数类型是String,第二个参数类型是int。

创建UserController类,这个类中的方法哪一个要被执行(目标方法)哪一个就是连接点

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/getuser")
    public String getUser(){
        System.out.println("do getUser");
        return "get user";
    }
    @RequestMapping("/deluser")
    public String delUser(){
        System.out.println("do delUser");
        return "del user";
    }
}

3、定义相关通知

通知定义的是被拦截的方法具体要执行的业务。比如用户登录权限验证方法就是具体要执行的业务。

Spring AOP中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:

  • 前置通知使用@Before:通知方法会在目标方法(连接点)调用之前执行
  • 后置通知使用@After:通知方法会在目标方法(连接点)返回或者抛出异常后调用
  • 返回之后通知使用@AfterReturning:通知方法会在目标方法(连接点)返回后调用
  • 抛异常后通知使用@AfterThrowing:通知方法会在目标方法(连接点)抛出异常后调用
  • 环绕通知使用@Around:通知包裹了被通知的方法,在被通知的方法之前和调用之后执行自定义的行为。

1️⃣前置通知和后置通知的实现

package com.example.demo.common;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect//定义切面
@Component//让切面随着框架的启动而启动
public class UserAspect {
    //定义切点,@Pointcut注解的参数中定义了具体的拦截规则
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){}

    //定义前置通知
    @Before("pointcut()")//表示这个通知是针对pointcut方法的
    public void doBefore(){
        System.out.println("执行了前置通知");
    }

    //定义后置通知
    @After("pointcut()")
    public void doAfter(){
        System.out.println("执行了后置通知");
    }
}

当我们在前端页面中访问UserController类的方法时,后端程序的控制台上每次出现的结果是先执行前置通知,在执行目标方法(连接点),然后执行后置通知。

 

2️⃣环绕通知的具体实现 

环绕通知方法是具有Object类型的返回值,需要把方法执行结果返回给框架,框架拿到对象继续执行。

package com.example.demo.common;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect//定义切面
@Component//让切面随着框架的启动而启动
public class UserAspect {
    //定义切点,@Pointcut注解的参数中定义了具体的拦截规则
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){}

    //定义前置通知
    @Before("pointcut()")//表示这个通知是针对pointcut方法的
    public void doBefore(){
        System.out.println("执行了前置通知");
    }

    //定义后置通知
    @After("pointcut()")
    public void doAfter(){
        System.out.println("执行了后置通知");
    }

    //定义环绕通知
    @Around("pointcut()")
    //环绕通知方法的参数为要执行的连接点,也就是我们在前端访问的目标方法
    public Object doAround(ProceedingJoinPoint joinPoint){
        System.out.println("环绕通知之前");
        Object result = null;
        try {
            //执行目标方法,它的目标方法就是我们在前端访问的方法
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知之后");
        return result;
    }
}

从执行结果中可以看到环绕通知的执行范围,可以环绕执行通知是最先执行的,然后是执行前置通知,然后再执行目标方法,然后执行后置通知,最后所有的方法执行完成了,环绕通知方法才会执行完成。

 

五、 AOP的实现原理

Spring AOP是建立再动态代理的基础上的,Spring对AOP的支持局限于方法级别的拦截。

Spring AOP使用两种混合的实现方式:JDK动态代理和CGLib动态代理。

  • JDK动态代理:如果目标对象实现了InvocationHandler接口,Spring将使用JDK动态代理来创建代理对象。
  • CGLib动态代理:如果目标对象没有实现InvocationHandler接口,Spring将使用CGLib代理,通过继承目标对象来创建代理对象。

1、什么是动态代理 

代理可以看作是对调用目标的一个包装,这样我们对目标代理的调用不是直接发生的,而是通过代理完成。

当想要给实现了某个接口的类中的方法,加一些额外的处理,比如加日志,加事务等。可以给这个类创建一个代理,也就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新方法,这个代理类并不是定义好的,而是动态生成的,具有解耦意义,灵活、扩展性强。

在Java中,动态代理通常使用Java.lang.reflect.Proxy类和Java.lang.reflect.InvocationHandler接口来实现。

2、 JDK代理和CGLIB代理的区别

  • 接口要求:JDK动态代理只能对实现了接口的类生成代理;而CGLIB代理可以没有实现接口的类,是通过继承被代理类,在运行时动态的生成代理对象。
  • 生成方式:JDK代理使用Java的反射机制来完成代理对象,而CGLIB代理使用CGLIB库生成代理对象,通过修改目标类的字节码来实现。所以该类不能被final修饰

如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/886901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习|Softmax 回归的数学理解及代码解析

机器学习&#xff5c;Softmax 回归的数学理解及代码解析 Softmax 回归是一种常用的多类别分类算法&#xff0c;适用于将输入向量映射到多个类别的概率分布。在本文中&#xff0c;我们将深入探讨 Softmax 回归的数学原理&#xff0c;并提供 Python 示例代码帮助读者更好地理解和…

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】 一、分布式系统遇到的问题1、服务挂掉的一些原因 二、解决方案三、Sentinel&#xff1a;分布式系统的流量防卫兵1、Sentinel是什么2、Sentinel和Hystrix对比3、Sentinel快速开发4、通过注解的方式来控流5、启动Sen…

【Android Framework系列】第11章 LayoutInflater源码分析

1 前言 本章节我们主要目目的是了解Activity的xml布局解析、对LayoutInfater源码进行分析。 我们知道Android界面上的每一个控件都是一个个View&#xff0c;但是Android也提供了通过xml文件来进行布局控制&#xff0c;那么xml布局文件如何转成最终的View的呢&#xff1f;转换利…

java面试基础 -- ArrayList 和 LinkedList有什么区别

目录 基本介绍 有什么不同?? ArrayList的扩容机制 ArrayLIst的基本使用 基本介绍 还记得我们的java集合框架吗, 我们来复习一下, 如图: 可以看出来 ArrayList和LinkedList 都是具体类, 他们都是接口List的实现类. 但是他们底层的逻辑是不同的, 相信学过这个的应该大概有…

idea下plantuml报错 dot executeable /opt/local/bin/dot

https://blog.csdn.net/weixin_40509040/article/details/121222419 注意路径中的版本问题&#xff0c;以及在完成这些操作后&#xff0c;重启软件&#xff0c;就可以正常了

SpringBoot集成Solr(一)保存数据到Solr

SpringBoot集成Solr&#xff08;一&#xff09;保存数据到Solr 添加依赖 <!--SpringBoot中封装过的Solr依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-solr</artifactId><ver…

【Linux】进程信号篇:信号的产生(signal、kill、raise、abort、alarm)、信号的保存(core dump)

文章目录 一、 signal 函数&#xff1a;用户自定义捕捉信号二、信号的产生1. 通过中断按键产生信号2. 调用系统函数向进程发信号2.1 kill 函数&#xff1a;给任意进程发送任意信号2.2 raise 函数&#xff1a;给调用进程发送任意信号2.3 abort 函数&#xff1a;给调用进程发送 6…

Rest学习环境搭建:服务提供者

创建一个普通的Maven项目 pom.xml父工程 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http…

科大讯飞发布星火认知大模型2.0版——体验实测

8月15日&#xff0c;科大讯飞举行讯飞星火认知大模型V2.0升级发布会&#xff0c;对外展示其升级后的大模型代码能力和多模态能力&#xff0c;同时发布并升级搭载讯飞星火认知大模型V2.0能力的多项应用和产品。自5月6日首发以来&#xff0c;星火认知大模型经历V1.5版本的迭代&am…

面试之快速学习STL-deuqe和list

1. deque deque 容器用数组&#xff08;数组名假设为 map&#xff09;存储着各个连续空间的首地址。也就是说&#xff0c;map 数组中存储的都是指针如果 map 数组满了怎么办&#xff1f;很简单&#xff0c;再申请一块更大的连续空间供 map 数组使用&#xff0c;将原有数据&…

利用Figlet工具创建酷炫Linux Centos8服务器-登录欢迎界面-SHELL自动化编译安装代码

因为我们需要生成需要的特定字符,所以需要在当前服务器中安装Figlet,默认没有安装包的,其实如果我们也只要在一台环境中安装,然后需要什么字符只要复制到需要的服务器中,并不需要所有都安装。同样的,我们也可以利用此生成的字符用到脚本运行的开始起头部分,用ECHO分行标…

go_并发编程(1)

go并发编程 一、 并发介绍1&#xff0c;进程和线程2&#xff0c;并发和并行3&#xff0c;协程和线程4&#xff0c;goroutine 二、 Goroutine1&#xff0c;使用goroutine1&#xff09;启动单个goroutine2&#xff09;启动多个goroutine 2&#xff0c;goroutine与线程3&#xff0…

log4j:WARN No appenders could be found for logger问题

本文将idea场景下的使用。 IDEA中&#xff0c;将配置文件命名为log4j.properties&#xff08;该命名才会被自动加载&#xff09;&#xff0c; 并放到某个目录下&#xff08;通常放到resources目录&#xff09;&#xff0c;并在resources上右键&#xff0c;找到Mark Directory a…

(二)结构型模式:8、代理模式(Proxy Pattern)(C++示例)

目录 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 2、代理模式的UML图学习 3、代理模式的应用场景 4、代理模式的优缺点 5、C实现代理模式的实例 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 代理模式&#xff08;Proxy&#xff09;&#xff0c;为…

【LangChain】P1 LangChain 应用程序的核心构建模块 LLMChain 以及其三大部分

LangChain 的核心构建模块 LLMChain LangChain 应用程序的核心构建模块语言模型 - LLMs提示模板 - Prompt templates输出解析器 - Output Parsers LLMChain 组合 LangChain 应用程序的核心构建模块 LangChain 应用程序的核心构建模块 LLMChain 由三部分组成&#xff1a; 语言…

【Git】(四)子模块

1、增加子模块 进入准备添加子模块所在的目录&#xff0c;例如library。 git submodule add -b 1.0.0.0 gitgitee.com:sunriver2000/SubModule.git参数-b用于指定子模块分支。 2、更新子模块 git submodule update --progress --init --recursive --force --remote -- "…

小程序前台Boot后台校园卡资金管理系统java web学校进销存食堂挂失jsp源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 小程序前台Boot后台校园卡资金管理系统 系统有2权限&…

fastadmin 下拉多级分类

要实现下图效果 一、先创建数据表 二、在目标的controll中引入use fast\Tree; public function _initialize() {parent::_initialize();$this->model new \app\admin\model\zxdc\Categorys;$tree Tree::instance();$tree->init(collection($this->model->order(…

Springboot写单元测试

导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintag…

探索高级UI、源码解析与性能优化,了解开源框架及Flutter,助力Java和Kotlin筑基,揭秘NDK的魅力!

课程链接&#xff1a; 链接: https://pan.baidu.com/s/13cR0Ip6lzgFoz0rcmgYGZA?pwdy7hp 提取码: y7hp 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍&#xff1a; &#x1f4da;【01】Java筑基&#xff1a;全方位指…