spring aop详解

article/2025/9/15 16:18:16

1.前言

spring aop是一个面向切面的编程,在自己第一遍学习的时候,感觉aop没有什么作用,但是真实接触下来,感觉spring aop还是很有用途的,感觉自己之前的想法太年轻了。

2.概念

Spring 提供了两种AOP 的实现:基于注解式配置和基于XML配置,我这里主要就是介绍一下,基于注解式配置。

2.1 AOP 即 Aspect Oriented Program 面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

  • 所谓的核心业务,工作中做的最多的就是增删改查,增删改查都叫核心业务。

  • 所谓的周边功能,比如性能统计,日志记录,事务管理等等

周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP

2.2 AOP 的目的

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

2.3 AOP 当中的概念:

  • 切面 (Aspect)可以理解成,就是一个特殊的类(包含的都是增强核心业务的代码),切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!

  • 连接点(join point)通俗理解就是整个系统的所有方法都可以称为连接点

  • 切入点(Pointcut) 就是被选中的连接点,可以通过execution来确定选中的连接点有哪些

  • 通知(Advice)在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能),就是切面这个类中的代码块

  • 织入(Weaving) 把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

2.4 Spring Aop中的通知类型:

  • 前置通知(Before Advice): 在目标方法被调用前调用通知功能;相关的类org.springframework.aop.MethodBeforeAdvice

  • 后置通知(After Advice): 在目标方法被调用之后调用通知功能;相关的类org.springframework.aop.AfterReturningAdvice

  • 返回通知(After-returning): 在目标方法成功执行之后调用通知功能;

  • 异常通知(After-throwing): 在目标方法抛出异常之后调用通知功能;相关的类org.springframework.aop.ThrowsAdvice

  • 环绕通知(Around): 把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptor

2.5 spring Aop实现的基础

spring aop实现是通过动态代理的方式实现的,动态代理避免了静态代理需要定义冗余的代理类,实现类,动态代理分为两种,第一种就是jdk 动态代理,第二种就是cglib 动态代理,aop 实现同时采用两种代理模式。

两种动态代理的区别:

jdk动态代理模式 :采用反射的方式,只能对实现接口的类生成代理,具有加载速度快,执行效率低的特点。

cglib动态代理模式:采用的asm,通过字节码形式实现,是针对类实现代理,具有加载速度慢,执行效率高的特点。

2.6 基于AspectJ实现基础上需要连接的几个内置注解

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选) 返回类型 方法名(参数) 异常模式(可选))

参数部分允许使用通配符:

* 匹配任意字符,但只能匹配一个元素

.. 匹配任意字符,可以匹配任意多个元素(零到若干个都可以),必须和*联合使用

"execution(public * com.qli.controller.TestController.*(..))"
@Pointcut(value = "@annotation(com.qli.config.RequestLog)")任何方法使用RequestLog注解都会触发该切面,进入切点

3.实现

具体的案例,在spring boot中实现spring aop,包含的内容有通过aop实现自定义注解,听起来就很高大上,其实懂了之后,感觉就那样,不过在新入门的程序员面前还是可以装起来的,还有就是多个切面的时候的执行顺序

3.1导入依赖

 <!--引入父依赖当前工程 继承 父类工程  spring-boot-starter-parent pom--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/></parent>
​<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties>
​
​<dependencies>
​<!-- web相关依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
​</dependency>
​<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>
​<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>

3.2创建切面

package com.qli.config;
​
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
​
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
​
@Aspect
@Component
@Order(2)
public class LogAspect {@Pointcut("execution(public * com.qli.controller.TestController.*(..))")public void webLog(){}
​@Before("webLog()")public void deBefore(JoinPoint joinPoint) throws Throwable {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容System.out.println("URL : " + request.getRequestURL().toString());System.out.println("HTTP_METHOD : " + request.getMethod());System.out.println("IP : " + request.getRemoteAddr());System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
​}
​@AfterReturning(returning = "ret", pointcut = "webLog()")public void doAfterReturning(Object ret) throws Throwable {// 处理完请求,返回内容System.out.println("方法的返回值 : " + ret);}
​//后置异常通知@AfterThrowing("webLog()")public void throwss(JoinPoint jp){System.out.println("方法异常时执行.....");}
​//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行@After("webLog()")public void after(JoinPoint jp){System.out.println("方法最后执行.....");}
​//环绕通知,环绕增强,相当于MethodInterceptor@Around("webLog()")public Object arround(ProceedingJoinPoint pjp) {System.out.println("方法环绕start.....");try {Object o =  pjp.proceed();System.out.println("方法环绕proceed,结果是 :" + o);return o;} catch (Throwable e) {e.printStackTrace();return null;}}
}

3.3 创建控制类

package com.qli.controller;
import com.qli.config.RequestLog;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
public class TestController {
​@RequestMapping("/requestLog")public String requestLog(){return "first controller";}
}

执行结果:

方法环绕start.....
URL : http://localhost:8088/requestLog
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.qli.controller.TestController.requestLog
ARGS : []
方法环绕proceed,结果是 :first controller
方法最后执行.....
方法的返回值 : first controller

上边是创建一个切面通过 @Pointcut("execution(public * com.qli.controller.TestController.*(..))")注解来限制切入点是controller包下TestController控制类下所有的方法都是切入点,那么congtroller包下其他控制类的方法就是连接点。

3.4创建自定义注解

3.4.1自定义注解之前需要了解的概念

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:

    public enum ElementType {/** 类,接口(包括注解类型)或枚举的声明 */TYPE,
    ​/** 属性的声明 */FIELD,
    ​/** 方法的声明 */METHOD,
    ​/** 方法形式参数声明 */PARAMETER,
    ​/** 构造方法的声明 */CONSTRUCTOR,
    ​/** 局部变量声明 */LOCAL_VARIABLE,
    ​/** 注解类型声明 */ANNOTATION_TYPE,
    ​/** 包的声明 */PACKAGE
    }
  • @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。

    public enum RetentionPolicy {/*** Annotations are to be discarded by the compiler.* (注解将被编译器忽略掉)*/SOURCE,
    ​/*** Annotations are to be recorded in the class file by the compiler* but need not be retained by the VM at run time.  This is the default* behavior.* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)*/CLASS,
    ​/*** Annotations are to be recorded in the class file by the compiler and* retained by the VM at run time, so they may be read reflectively.* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)* @see java.lang.reflect.AnnotatedElement*/RUNTIME
    }

  • @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

3.5 实现

3.5.1 创建自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RequestLog {String desc() default "无信息";
}

3.5.2 创建自定义注解时候后将会触发的切面

package com.qli.config;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Component
@Aspect
@Order(1)
public class RequestAspect {@Pointcut(value = "@annotation(com.qli.config.RequestLog)")public void access() {}@Before("access()")public void deBefore(JoinPoint joinPoint) throws Throwable {System.out.println("second before");}@Around("@annotation(requestLog)")public Object around(ProceedingJoinPoint pjp, RequestLog requestLog) {//获取注解里的值System.out.println("second around:" + requestLog.desc());try {return pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();return null;}}
}

3.5.3 在控制层添加

@RequestLog(desc = "second")@RequestMapping("/second")public Object second(){return "second controller";}

执行结果

second around:second
second before

如果将两个切面的切入点都包含有second这个方法的时候,浏览器访问http://localhost:8088/second的执行结果如下

second around:second
second before
方法环绕start.....
URL : http://localhost:8088/second
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.qli.controller.TestController.second
ARGS : []
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller

执行顺序:

img

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。


http://chatgpt.dhexx.cn/article/pKSvTk2H.shtml

相关文章

详解Spring

Spring SSH框架中Struts2:是基于Web层&#xff0c;Hibernate&#xff1a;是基于持久化的&#xff0c;Spring:业务层&#xff0c;管理bean&#xff0c;它是一个容器&#xff0c;List,map&#xff0c; Set这里的内容&#xff0c;是适合已经学过了Spring的人供复习参考的..... Spr…

Spring详解简介

1、Spring的简介 Spring的英文翻译为春天&#xff0c;可以说是给Java程序员带来了春天&#xff0c;因为它极大的简化了开发。我得出一个公式&#xff1a;Spring 春天 Java程序员的春天 简化开发。最后的简化开发正是Spring框架带来的最大好处。 Spring是一个开放源代码的设计…

spring 详细讲解

有人说&#xff0c;“Java程序员都是Spring程序员”&#xff0c;老三不太赞成这个观点&#xff0c;但是这也可以看出Spring在Java世界里举足轻重的作用。 基础 1.Spring是什么&#xff1f;特性&#xff1f;有哪些模块&#xff1f; Spring Logo 一句话概括&#xff1a;Spring…

JAVA框架Spring 全面详解(学习总结)

Spring 1.Spring 全面详解 1.1简介 ◆ Spring&#xff1a;春天 给软件行业带来了春天 ◆ 2002&#xff0c;首次推出了Spring框架的雏形&#xff1a;interface21框架 ◆ Spring框架即是以interface21框架为基础经过重新设计&#xff0c;并不断丰富其内涵&#xff0c;于2004…

Spring的事务详解

事务简介 事务在逻辑上是一组操作&#xff0c;要么执行&#xff0c;要不都不执行。主要是针对数据库而言的&#xff0c;比如说 MySQL。 为了保证事务是正确可靠的&#xff0c;在数据库进行写入或者更新操作时&#xff0c;就必须得表现出 ACID 的 4 个重要特性&#xff1a; 原…

java系列之Spring详解

一、Spring简介 1.1 简介 关于spring的简介&#xff0c;可以查看百度百科&#xff0c;下面内容部分来自百度百科 Spring框架是针对软件开发过程中的复杂性而创建的。其使用javaBean来完成以前只可能由EJB完成的事情。 2002年&#xff0c;Rod Jahnson首次推出了Spring框架雏形…

Spring全面详解

—————版本Spring5.x————— ——编译器IntelliJ IDEA 2020.2.3 —— <-- 该文章有点老旧&#xff0c;停止了更新&#xff0c;请查看Spring5最新文章&#xff0c;目前已经书写完成 --> Spring5全面详解 它会持续更新&#xff0c;你所看到的不是最终版本。 如…

Spring-全面详解(学习总结)

Spring 1.简介 1.1.简介 简介 Spring : 春天 —>给软件行业带来了春天 2002年&#xff0c;Rod Jahnson首次推出了Spring框架雏形interface21框架。 2004年3月24日&#xff0c;Spring框架以interface21框架为基础&#xff0c;经过重新设计&#xff0c;发布了1.0正式版。 …

spring超全面详解

spring概述 Spring 是于2003年兴起的一款轻量级的,非侵入式的IOC和AOP的一站式的java开发框架 为简化企业级应用开发而生. 1.轻量级: 就是指spring核心功能的jar包不大 2.非侵入式: 我们的业务代码不需要继承或实现spring中任何的类或接口 3.IOC: 控制反转 就是把创建…

Spring全面详解(学习总结)

Spring FrameWork一、 前言二、IOC(控制反转)2.1 对于IOC的理解2.2如何使用IOC2.3配置文件的解读2.4IOC容器创建bean的两种方式2.5从IOC容器中取bean2.6bean的属性如果包含特殊字符 三、DI(依赖注入)四、Spring中的bean五、Spring中的继承六、Spring的依赖七、Spring读取外部资…

查看Linux的用户权限(转载)

&#xff08;转&#xff09;Linux查看用户及其权限管理 查看用户 请打开终端&#xff0c;输入命令&#xff1a; $ who am i或者 $ who mom likes输出的第一列表示打开当前伪终端的用户的用户名&#xff08;要查看当前登录用户的用户名&#xff0c;去掉空格直接使用 whoami …

linux查看登录用户

1&#xff0c;w w,显示目前登入系统的用户信息 -f  开启或关闭显示用户从何处登入系统。 -h  不显示各栏位的标题信息列。 -l  使用详细格式列表&#xff0c;此为预设值。 -s  使用简洁格式列表&#xff0c;不显示用户登入时间&#xff0c;终端机阶段作业和程序所耗费…

Linux下查看当前用户和所属用户组方法总结

1、查看当前用户 &#xff08;1&#xff09;whoami &#xff08;2&#xff09;id -un &#xff08;3&#xff09;who -H &#xff08;4&#xff09;who&#xff08;查看当前登陆的所有用户&#xff0c;和who -H功能差不多&#xff09; 2、查看当前用户所属的组 &#xff08…

linux如何查看所有的用户和组信息?

首先打开终端&#xff08;这里是Ubuntu系统&#xff09;&#xff0c;其他的打开命令界面即可 然后输入命令行cat /etc/passwd&#xff0c;直接按下回车键即可 然后这里就会显示很多的信息&#xff0c;所有的用户都在这里面了 然后就是查看所有的组&#xff0c;同样的方法…

linux 查看用户信息

目录 /etc/passwd id命令 whois命令 whoami命令 who命令 w命令 finger命令 vlock命令 /etc/passwd 有的用户信息在根目录 /etc/passwd 文件内&#xff0c;而passwd的所有权限是root用户及root组用户&#xff0c;所有想要查看所有用户&#xff0c;需要root用户登录系统…

linux查看所有用户命令

1、Linux里查看所有用户 (1)在终端里.其实只需要查看 /etc/passwd文件就行了. (2)看第三个参数:500以上的,就是后面建的用户了.其它则为系统的用户. 或者用cat /etc/passwd |cut -f 1 -d : 2、用户管理相关命令 useradd命令 useradd 选项 用户名 -d 目录,指定用户主目录,如…

Linux命令之查看登录用户列表users

概述 users 命令 用于显示当前登录系统的所有用户的用户列表。每个显示的用户名对应一个登录会话。如果一个用户有不止一个登录会话&#xff0c;那他的用户名将显示相同的次数。 注&#xff1a;该命令与 who、w 类似。不过该命令只会显示登录用户名&#xff0c;信息简略。 语法…

Linux 系统中如何查看当前所有登录的用户

导读今天我们简单介绍下在 Linux 系统中列出登录用户的几种方法。 在多用户的 Linux 系统中&#xff0c;有时候会有查询当前已登录到系统中用户的需求。比如因某种原因要​​注销某个用户​​​。 今天我们简单介绍下在 Linux 系统中列出登录用户的几种方法。 我们所介绍的这…

查看linux用户密码

需要root用户 用户名在/etc/passwd这个文件中&#xff1b; 密码在/etc/shadow中 cat /etc/passwd cat /etc/shadow root:$6$1WtyW6O0baQmTkDG$o.YXXTOZSb7hP4HitigzaW/mZS433aSFpancmyNKYxU/59FuPVlIeVNBUKQQVgzx3kszkQAxo6C2wjrRbv0VZ.::0:99999:7::: 格式解释 {用户名}…

linux查看用户名

【步骤一】cat /etc/passwd cat /etc/passwd查看所有的用户信息&#xff0c;详情如下图 【步骤二】cat /etc/passwd|grep 用户名 cat /etc/passwd|grep 用户名&#xff0c;用于查找某个用户&#xff0c;如下图 【步骤三】cat /etc/group cat /etc/group查看所有组信息&#x…