当前位置: 首页 > news >正文

全网最强剖析Spring AOP底层原理

相信各位读者对于Spring AOP的理解都是一知半解,只懂使用,却不懂原理。网上关于Spring AOP的讲解层出不穷,但是易于理解,让人真正掌握原理的文章屈指可数。笔者针对这一痛点需求,决定写一篇关于Spring AOP原理的优质博客。这篇文章深入浅出,层次递进,循序渐进讲解Spring AOP的底层原理。全文通俗易懂,图文并茂,原理和源码相结合,让你做到知其然知其所以然。通篇阅读预计半小时,相信你读完后,对Spring AOP会有全新的理解,有质的飞跃,面试时也能得心应手,回答游刃有余。

欢迎关注+点赞+收藏~

静态代理

Service层中包含了哪些代码?

Service层中 = 核心功能(几十行、上百代码)+ 额外功能(附加功能)

  • 核心功能:业务运算、Dao调用等
  • 额外功能:不属于业务、可有可无、代码量很小。比如事务、日志、性能等

举个现实生活中的例子:对于房东这个实体来说,他出租房屋主要包括广告看房和签合同收钱。但是他觉得自己一个人经常发广告看房太累了,只想签合同收钱。也就是对他来说发广告看房是额外功能,签合同收钱才是核心功能。

如果没有中介(代理人):
在这里插入图片描述

如果有中介(代理人):
在这里插入图片描述

这其实就是现实生活中,代理模式的影子。

那么什么是代理模式呢?

概念:通过代理类,为原始类(目标类)增加额外的功能。

使用代理设计模式的好处:利于原始类(目标类)的维护。

  • 目标类(原始类):指的是业务类(核心功能 --> 业务运算 DAO调⽤)
  • 目标方法(原始方法):目标类(原始类)中的方法
  • 额外功能(附加功能):日志、事务、性能

代理模式的核心:

代理类 = 原始类(目标类)+ 额外功能 + 原始类(目标类)实现相同的接口

也就是说在实现代理模式时,我们需要重点关注三点:原始类额外功能接口。其中原始类中执行的肯定是核心功能。额外功能其实是定义在代理类中的,在代理类中可以添加一些额外功能,并且在代理类中会引入原始对象(即原始类的对象),当代理类执行完额外功能后,就会通过原始对象去调用原始类中的核心功能。接口就是定义了一组行为规范,原始类和代理类都必须实现相同的接口。

下面是静态代理模式的一个简单代码示例:
在这里插入图片描述

  • 接口:UserSerivce。在这个接口中,我们会定义两个方法,即register方法和login方法。
  • 原始类:UserServiceImpl。这个原始类会实现UserSerivce接口中定义的方法,然后在这些方法中实现核心功能。
  • 额外功能:比如图片中的输出语句。UserServiceIProxy是静态代理类,它和原始类UserServiceImpl都要实现相同的UserSerivce接口中定义的方法。我们可以看到在UserServiceIProxy静态代理类中引入了原始类UserServiceImpl的原始对象userService。当在静态代理类中做完额外功能后,就会通过这个原始对象userService去调用原始类UserServiceImpl中的核心功能。

从上面我们可以知道,静态代理最大的特点:有一个原始类,就必须要有一个对应的静态代理类(比如原始类UserServiceImpl和对应的代理类UserServiceProxy;原始类OrderServiceImpl和对应的代理类OrderServiceProxy)。这些代理类都要由程序员手动写出来。为每⼀个原始类,手工编写⼀个代理类 (.java .class)。

可以总结出静态代理的缺点:

  • 静态类文件数量过多,不利于项目管理;
  • 额外功能维护性差,静态代理类中额外功能修改复杂;

由此引出下文的动态代理。

Spring动态代理入门

Spring动态代理的开发步骤:

  • 创建原始对象(目标对象)
  • 定义额外功能(附加功能)
  • 定义切点
  • 组装切面
  1. 创建原始对象(目标对象)
    在这里插入图片描述

  2. 定义额外功能(附加功能)

Spring提供了MethodBeforeAdvice接口,这个接口中有一个before方法需要我们去实现。我们把额外功能书写在接口的实现中,它会在原始方法执行之前运行额外功能。
在这里插入图片描述

  1. 定义切入点

切入点的定义:额外功能加入的位置。

目的:由程序员根据自己的需要,决定将额外功能加入给哪个原始方法。(比如register方法、login方法)

如果将所有方法都作为切入点,都加入额外的功能。那么可以写成如下:
在这里插入图片描述

  1. 组装切面(将第二步定义好的额外功能与第三步定义的切入点进行整合,组装成切面)
    在这里插入图片描述

完美图解:
在这里插入图片描述

那么我们该如何获得Spring工厂创建的动态代理对象,并进行调用呢?

正常来说,通过ctx.getBean("userService")得到的应该是UserServiceImpl这个类的对象。但在动态代理中,其实不是这样的。请注意,它实际上得到的是代理类的对象。

获取代理对象后,怎么确定这个代理对象的类型呢?

在静态代理中,我们知道代理类和原始类都要实现相同接口。在动态代理中其实也是一样的!我们可以通过原始类看它实现什么接口,那么这个代理对象的类型就是该接口。比如这里原始类UserServiceImpl实现UserService接口,那么这个代理对象的类型就是UserService
在这里插入图片描述

我们来实践出真知:
在这里插入图片描述

可以发现这和我们的结论确实是一致的!

Spring创建的动态代理类在哪里呢?

在上述的动态代理开发中,我们并没有编写任何像静态代理中的UserServiceImplProxy代理类,根本就没有出现代理类的影子。那么Spring创建的动态代理类在哪里呢?

原理:Spring框架在运行时,通过动态字节码技术,在JVM中创建动态代理类,运行在JVM内部,等程序结束后,会和JVM⼀起消失

动态字节码技术:它并不需要.java.class,而是直接通过第三方的动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。

动态代理的特点:动态代理不需要定义任何代理类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理中类文件数量过多,影响项目管理的问题
在这里插入图片描述

动态代理编程简化代理的开发

在额外功能不改变的前提下,创建其他原始类的代理对象时,只需要指定原始对象即可。

比如我们要创建OrderServiceImpl这个原始类时,只需要在配置文件中指定原始对象即可:
在这里插入图片描述

动态代理额外功能的维护性大大增强

比如我们想要修改之前创建的额外功能before类,但其实我们并不需要在这个类中直接修改。而是可以新建一个额外功能before1类,在这个新类中完成添加额外功能操作。
在这里插入图片描述

Spring动态代理详解

从上面我们知道Spring动态代理开发四步骤:
在这里插入图片描述

下面我们将主要详细分析第二步额外功能和第三步切入点。
在这里插入图片描述

额外功能详解

分析before方法中各个参数的具体含义:
在这里插入图片描述

(1)Method method

它表示我们需要把这个额外功能添加到哪个原始方法中。如我们想要给register原始方法、login原始方法加入这个额外功能,那么这里method就是register或者login。

(2)Object[] agrs

它表示额外功能所增加给的那个原始方法中所含有的形参。

比如参数method是login原始方法,那么这里args对应的就是login方法中的参数。如下图,login方法中的参数是String name和String password。因此这里args数组对应的就是这两个参数。
在这里插入图片描述

比如参数method是register原始方法,那么这里args对应的就是register方法中的参数。如下图,register方法中的参数是User user。因此这里args数组对应的就是这个参数。
在这里插入图片描述

由此可知,args和method是息息相关的。

(3)Object target

它表示原始对象。比如原始类UserServiceImpl它的原始对象就是userService,原始类OrderServiceImpl它的原始对象就是orderService。

实践出真知:
在这里插入图片描述

MethodInterceptor(方法拦截器)

MethodBeforeAdvice和MethodInterceptor的区别:

  • MethodBeforeAdvice:额外功能只能运行在原始方法前。
  • MethodInterceptor:额外功能可以运行在原始方法之前或者原始方法之后或者原始方法前后。

我们编写一个类Around实现MethodInterceptor接口,这个接口中有一个invoke方法需要我们实现。这里方法中有一个形参MethodInvocation invocation。invoke方法的作用:额外功能书写在invoke中。
在这里插入图片描述

我们知道在MethodInterceptor中,额外功能可以运行在原始方法之前或者原始方法之后或者原始方法前后。那怎么确定这个额外功能到底要运行在原始方法之前、之后、前后呢?这个额外功能的运行时机我们怎么在invoke方法中体现出来呢?

在实现invoke方法过程中,我们必须要确定:原始方法怎么运行?

答:一旦我们确定原始方法怎么运行了,那么在原始方法之前写的额外功能就是运行在原始方法之前,在原始方法之后写的额外功能就是运行在原始方法之后,前后都写了就是运行在原始方法的前后。

我们需要先来掌握invoke这个方法的参数。

  • MethodInvocation invocation:它表示额外功能所要增加给的那个原始方法。如果我们所开发的这个额外功能是添加给login,那么invocation就代表login方法。

它有点类似于MethodBeforeAdvice中的Method method,不过它是对method的高级封装。

好了,现在我们已经知道invocation就代表原始方法。但是我们想要知道的是原始方法是怎么运行呢?
在这里插入图片描述

从源码中可以看出,其实invocation.proceed()就表示原始方法的运行。

  • 如果我们把额外功能添加给login原始方法,那么invocation.proceed()就代表login这个原始方法的运行;
  • 如果我们把额外功能添加给register原始方法,那么invocation.proceed()就代表register这个原始方法的运行。

我们再来看一下invoke方法的返回值:

返回值Object其实代表的是原始方法的返回值。也就是说,如果invocation代表login方法,那么Object其实就是login方法的返回值。

(1)额外功能运行在原始方法执行之前的代码示例
在这里插入图片描述

(2)额外功能运行在原始方法执行之后的代码示例
在这里插入图片描述

(3)额外功能运行在原始方法执行前后的代码示例
在这里插入图片描述

什么样的额外功能需要运行在原始方法执行的前后呢?

事务!如下图,原始方法执行之前开启事务,原始方法之前之后提交事务。
在这里插入图片描述

MethodInterceptor接口中invoke里面的额外功能也可以运行在原始方法抛出异常的时候:
在这里插入图片描述

总结MethodInterceptor中额外功能所运行的四个时机:

  • 额外功能运行在原始方法执行之前
  • 额外功能运行在原始方法执行之后
  • 额外功能运行在原始方法执行前后
  • 额外功能运行在原始方法抛出异常时

MethodInterceptor影响原始方法的返回值

如果原始方法的返回值直接作为invoke方法的返回值返回时,那么MethodInterceptor不会影响原始方法的返回值。
在这里插入图片描述

那么如果我们想要让MethodInterceptor影响原始方法的返回值呢?此时,我们不要在invoke中把原始方法的返回值作为invoke方法的返回值直接返回了。
在这里插入图片描述

切入点详解

在这里插入图片描述

切入点表达式分为三种:

  • 方法切入点表达式
  • 类切入点表达式
  • 包切入点表达式

(1)方法切入点表达式
在这里插入图片描述

定义login方法作为切入点:* login(..)
在这里插入图片描述

定义login方法且login方法有两个字符串类型的参数作为切入点:* login(String, String)
在这里插入图片描述

这里我们使用的参数类型是String,它是java.lang包中的类型。

注意:如果此时的参数类型是非java.lang包中的类型,那么必须要写全限定名。比如* register(com.baizhiedu.proxy.User)

注意:..可以和具体的参数类型连用。比如* login(String, ..)可以代表login(String)login(String,String)login(String,com.baizhiedu .proxy.User)
在这里插入图片描述

上面所讲解的这种切入点表达式并不精准!

如下图所示,如果使用* login(..),那么下图中的所有login方法都将可以匹配。
在这里插入图片描述

那么如何才能做到精准呢?

精准方法切入点限定:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(2)类切入点表达式
在这里插入图片描述

指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
在这里插入图片描述

使用* *.UserServiceImpl.*(..),只能处理一层包。比如com是第一层包,然后UserServiceImpl类是在com这个包下,那么就是可以的。假设UserServiceImpl类是在com.baozhiedu.proxy包下,此时这是三层包,那么就不能正确切入。
在这里插入图片描述

那如果我非要把UserServiceImpl类是在com.baozhiedu.proxy包下呢?那么就应该写成:* *..UserServiceImpl.*(..),增加..其实就表示多级包。
在这里插入图片描述

(3)包切入点表达式
在这里插入图片描述

在这里插入图片描述

注意上面的这种写法* com.baizhiedu.proxy.*.*(..)必须要求UserServiceImpl类和OrderServiceImpl类都在proxy包中,而不能存在于proxy的子包中。

那么该如何处理上面的这个情况呢?可以写成* com.baizhiedu.proxy..*.*(..)
在这里插入图片描述

以上三种切入点表达式中,最具实战价值的是包切入点表达式

切入点函数

切入点函数的作用:用于执行切入点表达式。

切入点函数有:execution、args、within、@annotation。
在这里插入图片描述

(2)args

args代码示例:
在这里插入图片描述

(3)within
在这里插入图片描述

within代码示例:
在这里插入图片描述

在这里插入图片描述

(4)@annotation
在这里插入图片描述

@annotation的代码示例:
在这里插入图片描述

切入点函数的逻辑运算
在这里插入图片描述

and与操作的代码示例:
在这里插入图片描述

注意:and操作不适用于同种类型的运算,比如execution() and execution()是不行的。代码示例如下:
在这里插入图片描述

or或操作符的代码示例:
在这里插入图片描述

Spring AOP详解

  1. AOP(Aspect Oriented Programing):⾯向切⾯编程 = Spring动态代理开发 以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建。切面 = 切入点 + 额外功能
  2. OOP (Object Oritened Programing) ⾯向对象编程 Java 以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建。
  3. POP (Producer Oriented Programing) ⾯向过程(⽅法、函数)编程 比如C语言。以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建。

AOP:本质就是Spring动态代理开发,通过代理类为原始类增加额外功能

好处:利于原始类的维护。

注意:AOP编程不可能取代OOP,OOP编程有意补充。

AOP编程的开发步骤

  1. 原始对象
  2. 额外功能(MethodInterceptor)
  3. 切入点
  4. 组装切面(额外功能+切入点)
  • 切面 = 切入点 + 额外功能

从几何角度来看,面 = 点 + 相同的性质
在这里插入图片描述

有两个核心问题:

  • AOP如何创建动态代理类(动态字节码技术)
  • Spring工厂如何加工创建代理对象(通过原始对象的id值,获得的是代理对象)

JDK动态代理

Proxy.newProxyInstance方法参数详解:
在这里插入图片描述

可以看到,newProxyInstance方法中含有三个参数:

  • ClassLoader loader它表示代理类的类加载器。传递给newProxyInstance的类加载器应该是已经被加载到内存中的类或接口的类加载器。这个类加载器将用来定义生成的代理类。
  • Class<?>[] interfaces它表示代理类所需要实现的接口列表。生成的代理类将实现这些接口中的每一个,使得代理实例可以被安全地转型为这些接口类型的任何一个。这些接口定义了代理实例可以调用哪些方法。
  • InvocationHandler h这是一个处理接口方法调用的调用处理器。当代理实例的方法被调用时,方法调用将被转发到这个调用处理器。这个处理器的invoke方法负责决定如何处理代理实例上的方法调用。这包括调用实际对象的方法、返回一个值或抛出一个异常等。这是实现动态代理功能的核心,允许开发者在运行时动态改变方法的行为。

在Java中,InvocationHandler 接口定义了一个非常重要的方法 invoke,它是实现动态代理的关键。当一个代理实例的方法被调用时,该方法就会被触发。InvocationHandler 的 invoke 方法有三个参数,每个参数都有其特定的用途和意义:
在这里插入图片描述

  • Object proxy这是代理类的实例本身,即调用方法的代理对象。通常不直接使用这个对象来调用方法,因为它可能会导致无限递归调用代理方法自身。这个参数主要用于反射相关的信息,比如可以通过它获取代理类的信息,但在方法内部调用它的其他方法时需要小心处理。
  • Method method额外功能所要增加给的那个原始方法。这是正被调用的方法的反射对象。这个 Method 对象包含了关于正在被调用的方法的所有元数据,如方法名、返回类型、参数类型等。通过这个对象,你可以访问到任何关于这个方法的信息,甚至可以通过反射来调用它。
  • Object[] args它表示原始方法中的形参。这是一个包含了传递给方法的所有参数的数组。这些是调用方法时实际使用的参数值。如果被调用的方法没有参数,则 args 将是一个长度为0的数组。通过这个数组,你可以修改、替换或调整传入方法的参数值,或者在调用原方法之前对它们进行处理。
    在这里插入图片描述

在这里插入图片描述

下面的这幅图中classloader借用的是类TestJDKProxy的classloader:
在这里插入图片描述

下面的这幅图中classloader借用的是UserService的classloader:
在这里插入图片描述

CGlib动态代理

CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法⼀致,同时在代理类中提供新的实现(额外功能+原始方法)
在这里插入图片描述

如何通过cglib方法创建动态代理对象呢?

回顾一下JDK动态代理的过程,Proxy.newProxyInstance(classloader, interfaces, invocationhandler)。这个方法中需要用到三个参数。其实cglib也是同样的。只不过cglib中没有接口,因此不需要interfaces。但是cglib中是子类继承父类,因此需要保证继承关系。cglib需要三个参数:

  • Enhancer.setClassLoader():等效于JDK动态代理中的classloader
  • Enhancer.setSuperClass():等效于JDK动态代理中的interfaces
  • Enhancer.setCallback():等效于JDK动态代理中的invocationhandler。不过使用的是cglib中的MethodInterceptor而不是Spring提供的MethodInterceptor。

代码示例:
在这里插入图片描述

JDK动态代理和CGlib动态代理的总结:

  1. JDK动态代理Proxy.newProxyInstance()通过接⼝创建代理的实现类
  2. Cglib动态代理 Enhancer 通过继承⽗类创建的代理类

现在我们来思考一下"为什么Spring通过原始对象的id值,获得的是代理对象"呢?

要弄明白这个细节,需要把BeanPostProcessorJDK动态代理/CGlib动态代理结合起来分析。

下面这张图是创建代理过程中Spring通过BeanPostProcessor完成的对原始类UserServiceImpl的加工过程。
在这里插入图片描述

首先我们通过bean创建了原始类UserServiceImpl的原始对象,这里设置id是userService,因此userService就是UserServiceImpl原始类的对象。

Spring工厂第一步还是创建出了userService这个原始对象,它调用构造方法把原始对象创建好了。按照BeanPostProcessor加工时机来说,此时应该要进入postProcessBeforeInitialization对它进行加工。但是我们知道不一定都要进行postProcessBeforeInitialization和postProcessAfterInitialization这两次加工。因为我们很少做初始化操作。所以postProcessBeforeInitialization初始化之前的加工和postProcessAfterInitialization初始化后的加工实际上它们大概所起的作用是一样的。那么我们就可以不做postProcessBeforeInitialization这个加工处理了,在postProcessBeforeInitialization中直接return bean交还给Spring即可。总的来说就是创建完成userService这个原始对象后直接交给postProcessAfterInitialization这个方法来处理。

把创建好的原始对象userService传递给postProcessAfterInitialization方法的第一个参数Object bean。在postProcessAfterInitialization方法中的加工,要注意此时不像我们之前做的只是对属性进行简单的改变,我们要让userService这个原始对象通过我们的加工,最后把这个代理对象给创建出来。所以实际上从整个加工代码来讲,我们就用相应代理创建的底层代码来完成了。可以看到postProcessAfterInitialization方法中使用了JDK动态代理来创建出userServiceProxy代理对象。即通过Proxy.newProxyInstance()把userService原始对象加工成最终我们需要的userServiceProxy代理对象

这里关注一下Proxy.newProxyInstance()中的三个参数从何而来呢?

  • 对于classloader,我们随便借一个就行。
  • 对于interfaces,它是原始对象所实现的接口,这个原始就是userService,它传参给了bean,因此我们可以通过bean.getClass().getInterfaces()得到原始对象所实现的接口。
  • 对于invocationHandler,我们实现这个接口即可。

最后我们把加工后得到的代理对象userServiceProxy作为返回值交给Spring工厂。因此后续通过ctx.getBean("userService")得到的就是代理对象userServiceProxy而不是原始类UserServiceImpl它所对应的原始对象了。

代码示例:
在这里插入图片描述

Spring AOP编程

在这里插入图片描述

加入@Around()注解就等同于我们编写了一个类MyArround去实现了MethodInterceptor接口。然后我们声明的arround()方法(这个方法可以随便命名)就等同于里面的invoke方法。arround()方法的返回值就等同于invoke方法的返回值。arround()方法中的joinpoint就等同于invoke方法的invocation。

代码示例:
在这里插入图片描述

  1. 切入点复用

在切面类中定义一个方法,上面有@Pointcut注解。通过这种方式,定义切点表达式,后续更加有利于切入点复用。

如下图,出现了代码冗余:
在这里插入图片描述

解决切入点复用,如下图:
在这里插入图片描述

  1. 基于注解的AOP编程中动态代理的创建方式
    在这里插入图片描述

回顾AOP底层实现中的两种代理:

  • JDK 通过实现接口 做新的实现类方式 创建代理对象
  • Cglib通过继承父类 做新的子类 创建代理对象

那么基于注解的AOP编程中动态代理采用的是JDK还是Cglib呢?
在这里插入图片描述

从上图中可以看到,默认情况下AOP编程(包括传统的AOP开发和基于注解的AOP开发)底层采用的是JDK动态代理创建方式。

那么如果我们想要采用Cglib呢?

如下图,针对基于注解的AOP编程,我们可以在aop:aspectj-autoproxy中将proxy-target-class设置为true,那么就可以将默认的JDK动态代理修改为Cglib动态代理了。
在这里插入图片描述

如下图,针对传统的AOP开发,我们可以在aop:config标签中将proxy-target-class设置为true。
在这里插入图片描述

Spring AOP开发中的坑点

坑点:在同一个业务中,进行业务方法间的相互调用,只有最外层的方法,才是加入额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,就要ApplicationContextAware获得工厂,进而获得代理对象。

先来看下图,给UserServiceImpl类中的所有方法(这里是register方法和login方法)都添加额外功能:
在这里插入图片描述

如下图,我们在register方法中调用了login方法,然后把测试类中的userService.login方法注释掉。从输出结果中可以看出,只给register方法添加了额外功能,并没有给login方法添加额外功能。这显然不符合我们的预期,按理来说应该是都要给register方法和login方法都添加上额外功能呀。为什么login方法没有执行额外功能呢?
在这里插入图片描述

我们重新来审视下面这个图,当测试类TestAspectProxt调用register方法时,我们是通过代理对象调的register方法(我们之前反复强调过ctx.getBean(“userService”)得到的是代理对象而并不是原始类UserServiceImpl它的原始对象)。然后我们在register方法中使用this调用了login方法,那么来思考一下这个login方法是属于哪个对象的?

从下图中显然看出login方法是属于this,而这里的this是指的类UserServiceImpl。我们知道UserServiceImpl是原始类,通过this.login它本质上是通过原始对象调的login方法。它并不是通过代理对象调的login。既然不是通过代理对象调的,那么显然你就不能拥有代理对象中添加的额外功能。因此这里只是普通的通过this调用本类UserServiceImpl中的login方法,没有涉及任何额外功能,那么输出结果肯定只有login方法中的输出内容,不会有代理对象中的额外功能。
在这里插入图片描述

明白了错因,如果想要解决问题,那么就要在register方法中拿到代理对象,进而通过代理对象调用login方法。最简单的办法就是如下图,把测试类中的这两行代码也写到register方法中:
在这里插入图片描述

但是我们知道Spring工厂是重量级资源,创建多个工厂ctx势必会占用大内存,一个应用中应该只能创建一个工厂。因此上述方法不推荐。

其实我们只需要实现ApplicationContextAware这个接口。由于在测试类中已经创建出了一个Spring工厂ctx,我们在UserServiceImpl类中定义一个ctx,然后实现ApplicationContextAware接口中的setApplicationContext方法,把测试类中创建好的那个Spring工厂直接赋值给UserServiceImpl类中的ctx。这样整体上就只有一个Spring工厂。

解决方法的代码示例:
在这里插入图片描述

Spring AOP总结

在这里插入图片描述


相关文章:

全网最强剖析Spring AOP底层原理

相信各位读者对于Spring AOP的理解都是一知半解&#xff0c;只懂使用&#xff0c;却不懂原理。网上关于Spring AOP的讲解层出不穷&#xff0c;但是易于理解&#xff0c;让人真正掌握原理的文章屈指可数。笔者针对这一痛点需求&#xff0c;决定写一篇关于Spring AOP原理的优质博…...

Vscode中的行尾序列CRLF/LF不兼容问题

最近开发的的时候&#xff0c;打开项目文件经常会出现爆红错误提示信息&#xff0c;显示如下图&#xff1a; 这东西太烦人了&#xff0c;毕竟谁都不希望在遍地都是爆红的代码里写东西&#xff0c;就像能解决这个问题&#xff0c;根据提示可以知道这是vscode中使用的prettier插件…...

常见加密方式:MD5、DES/AES、RSA、Base64

16/32位的数据&#xff0c;最有可能就是使用md5加密的 使用对称加密的时候&#xff0c;双方使用相同的私钥 私钥&#xff1a;单独请求/隐藏在前端的隐藏标签当中 二、RSA非对称密钥加密 公钥加密&#xff0c;私钥解密 私钥是通过公钥计算生成的 加密解密算法都在js源文件当…...

如何在 C++/Qt/CMake 项目中构建 Rust 代码

问题描述 我有一个使用 CMake 构建的现有 C/Qt 项目&#xff0c;我想开始添加 Rust 代码&#xff0c;并能够从主 C 代码库中调用这些 Rust 代码。应该如何组织项目结构&#xff1f; 现有项目结构 ./CMakeLists.txt ./subproject-foo/CMakeLists.txt ./subproject-foo/src/..…...

封装了一个优雅的iOS转场动画

效果图 代码 // // LBTransition.m // LBWaterFallLayout_Example // // Created by mac on 2024/6/16. // Copyright © 2024 liuboliu. All rights reserved. //#import "LBTransition.h"interface LBPushAnimation:NSObject<UIViewControllerAnimated…...

数据中心技术:大数据时代的机遇与挑战

在大数据时代&#xff0c;数据中心网络对于存储和处理大量信息至关重要。随着云计算的出现&#xff0c;数据中心已成为现代技术的支柱&#xff0c;支持社交媒体、金融服务等众多行业。然而&#xff0c;生成和处理的大量数据带来了一些挑战&#xff0c;需要创新的解决方案。在这…...

29、架构-技术方法论之向微服务迈进

治理&#xff1a;理解系统复杂性 微服务架构的引入增加了系统的复杂性&#xff0c;这种复杂性不仅体现在技术层面&#xff0c;还包括组织、管理和运维等各个方面。本节将详细探讨微服务架构的复杂性来源&#xff0c;并介绍一些应对复杂性的治理策略。 1. 什么是治理 治理是指…...

点云处理实操 1. 求解点云法向

目录 一、点云法向的定义 二、如何计算计算法向量 三、实操 四、代码 main.cpp CMakeList.txt 一、点云法向的定义 点云法向量是指点云中某个点的局部表面法向量(Normal Vector)。法向量在三维空间中用来描述表面在该点处的方向属性,它是表面几何特征的重要描述工具。…...

XSS+CSRF组合拳

目录 简介 如何进行实战 进入后台创建一个新用户进行接口分析 构造注入代码 寻找XSS漏洞并注入 小结 简介 &#xff08;案例中将使用cms靶场来进行演示&#xff09; 在实战中CSRF利用条件十分苛刻&#xff0c;因为我们需要让受害者点击我们的恶意请求不是一件容易的事情…...

PasteSpiderFile文件同步管理端使用说明(V24.6.21.1)

PasteSpider作为一款适合开发人员的部署管理工具&#xff0c;特意针对开发人员的日常情况做了一个PasteSpiderFile客户端&#xff0c;用于windows上的开发人员迅速的更新发布自己的最新代码到服务器上&#xff01; 虽然PasteSpider也支持svn/git的源码拉取&#xff0c;自动编译…...

NLP中两种不同的中文分词形式,jieba和spaCy

1. jieba分词 import jiebatext在中国古代文化中&#xff0c;书法和绘画是艺术的重要表现形式。古人常说&#xff0c;‘文字如其人’&#xff0c;通过墨迹可以窥见作者的性情和气质。而画家则以笔墨搏击&#xff0c;表现出山川河流、花鸟虫鱼的灵动。这些艺术形式不仅仅是技艺…...

【数据库】四、数据库编程(SQL编程)

四、数据库编程 另一个大纲&#xff1a; 5.1存储过程 5.1.1存储过程基本概念 5.1.2创建存储过程 5.1.3存储过程体 5.1.4调用存储过程 5.1.5删除 5.2存储函数 5.2.1创建存储函数 5.2.2调用存储函数 5.2.3删除存储函数 目录 文章目录 四、数据库编程1.SQL编程基础1.1常量1.2变…...

17.RedHat认证-Ansible自动化运维(下)

17.RedHat认证-Ansible自动化运维(下) 这个章节讲ansible的变量&#xff0c;包括变量的定义、变量的规则、变量范围、变量优先级、变量练习等。 以及对于tasks的控制&#xff0c;主要有loop循环作业、条件判断等 变量 介绍 Ansible支持变量功能&#xff0c;能将value存储到…...

React Suspense的原理

React Suspense组件的作用是当组件未完成加载时&#xff0c;显示 fallback 组件。那么 Suspense 是如何实现的呢&#xff1f;React 的渲染是通过 Fiber 进行的&#xff0c;Suspense 的更新机制也是要围绕 Fiber 架构进行的。Suspense 是由两部分组成&#xff0c;实际 UI 子组件…...

React的生命周期函数详解

import React,{Component} from "react";import SonApp from ./sonAppclass App extends Component{state{hobby:爱吃很多好吃的}// 是否要更新数据&#xff0c;这里返回true才会更新数据shouldComponentUpdate(nextProps,nextState){console.log("app.js第一步…...

DoubleSummaryStatistics 及其相关类之-简介

1. DoubleSummaryStatistics 使用简介 在Java 8中&#xff0c;DoubleSummaryStatistics 类被引入作为 java.util 包的一部分。它是一个用于收集统计数据&#xff08;如计数、最小值、最大值、和、平均值等&#xff09;的类&#xff0c;特别适用于处理 double 类型的数据。 Do…...

java线程间的通信 - join 和 ThreadLocal

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…...

差分GPS原理

双差RTK&#xff08;Real-Time Kinematic&#xff09;算法是基于差分全球卫星导航系统&#xff08;GNSS&#xff09;技术的一种高精度定位方法。它利用至少两个接收机&#xff08;一个为基站&#xff0c;其他为移动站&#xff09;接收自同一组卫星的信号来实现精确测量。双差处…...

【栈与队列】前k个高频元素

题目&#xff1a;给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 分析&#xff1a;首先我们需要计算数组中元素出现的频率&#xff0c;前几篇文章讲解了哈希表的应用&#xff0c;所以这里我们很容易想到用…...

B端产品竞品分析-总结版

B端竞品分析的难点 分析维度-业务逻辑复杂 B端产品与C端产品业务模型不同&#xff0c;B端产品主要以业务为导向&#xff0c;因此其业务流程与业务逻辑梳理起来也会较C端产品复杂的多&#xff0c;对于个人能力也有一定的要求&#xff0c;需要我们具备相关领域或行业专业知识。…...

刷代码随想录有感(116):动态规划——单词拆分

题干&#xff1a; 代码&#xff1a; class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string>set(wordDict.begin(), wordDict.end());vector<bool>dp(s.size() 1, false);dp[0] true;for(int j 0; j &…...

CSS-0_1 CSS和层叠(样式优先级、内联样式、选择器 用户代理样式)

CSS 的本质就是声明规则 ——《深入解析CSS》 文章目录 CSS层叠和优先级用户代理样式请和用户代理样式和谐相处 选择器单选择器的优先级选择器组的优先级关于选择器的其他源码顺序尽可能的选择优先级低的选择器 内联样式内联样式和JavaScript !important多个 !important 碎碎念…...

科技赋能冷链园区:可视化带来全新体验

应用图扑可视化技术&#xff0c;冷链园区能够更加直观地监控和管理资源&#xff0c;优化运作流程&#xff0c;提高运营效率与服务质量。...

高通安卓12-安卓系统定制2

将开机动画打包到system.img里面 在目录device->qcom下面 有lito和qssi两个文件夹 现在通过QSSI的方式创建开机动画&#xff0c;LITO方式是一样的 首先加入自己的开机动画&#xff0c;制作过程看前面的部分 打开qssi.mk文件&#xff0c;在文件的最后加入内容 PRODUCT_CO…...

高中数学:数列-解数列不等式问题的常用放缩技巧(重难点)

一、放缩技巧 技巧1 例题 证明&#xff1a;Sn&#xff1c;1 解&#xff1a; 变形 解&#xff1a; 由于第一种情况&#xff0c;我们证明了Sn&#xff1c;1&#xff0c;n≥1&#xff0c;是从第一项就开始放缩的。 发现&#xff0c;无法精确到 3 4 \frac{3}{4} 43​ 这时&am…...

[图解]企业应用架构模式2024新译本讲解17-活动记录1

1 00:00:01,070 --> 00:00:04,180 下一个我们要说的就是 2 00:00:04,190 --> 00:00:06,740 活动记录模式了 3 00:00:07,640 --> 00:00:11,210 同样是数据源架构模式 4 00:00:12,300 --> 00:00:18,480 里面的一个&#xff0c;活动记录 5 00:00:18,490 --> 00…...

[C++深入] --- malloc/free和new/delete

1 new运算符的拓展 1.1 自由存储区与堆的概念 在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区。 自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。 new操作符从自由存储区(free st…...

Spcok测试代码抛异常场景

测试代码抛异常场景 ‍ class ExceptionSpec extends Specification {def validateService new ValidateService()Unrolldef "验证UserInfo"() {when: "调用校验方法"validateService.validateUser(user)then: "捕获异常并设置需要验证的异常值&qu…...

【漏洞复现】脸爱云一脸通智慧管理平台 SystemMng 管理用户信息泄露漏洞(XVE-2024-9382)

0x01 产品简介 脸爱云一脸通智慧管理平台是一套功能强大&#xff0c;运行稳定&#xff0c;操作简单方便&#xff0c;用户界面美观&#xff0c;轻松统计数据的一脸通系统。无需安装&#xff0c;只需在后台配置即可在浏览器登录。 功能包括:系统管理中心、人员信息管理中心、设备…...

新手如何入门Web3?

一、什么是Web3&#xff1f; Web3是指下一代互联网&#xff0c;它基于区块链技术&#xff0c;致力于将各种在线活动变得更加安全、透明和去中心化。Web3是一个广义的概念&#xff0c;涵盖了包括数字货币、去中心化应用、智能合约等在内的多个方面。它的主要特点包括去中心化、…...