一文了解spring的aop知识

推荐工具 objectlog

对于重要的一些数据,我们需要记录一条记录的所有版本变化过程,做到持续追踪,为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包,以非侵入方式实现对标记的对象属性进行记录,仅需要导入依赖即可,几乎不需要对原系统代码改动,下面展示简单的效果(根据对象field渲染即可):
在这里插入图片描述

该系统具有以下特点:

  • 简单易用:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。
  • 业务共享:系统可以同时供多个业务系统使用,彼此之间互不影响。
  • 自动解析:能自动解析对象的属性变化,自动生成变化记录。
  • 便于扩展:支持更多对象属性类型的扩展,支持自定义解析处理逻辑等。
  • 工具性能:工具采用线程模式,脱离业务主线程,避免了解析过程对业务性能的影响。

开源地址:https://gitee.com/opensofte/objectlog,有兴趣的朋友可以看看点个star.

使用背景

我们现在有一个业务场景,每次获取数据的时候需要判断有没有权限数据,我们首先以oop角度来说明:

public class BusinessA {public void do(){//1、判断权限//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(curUserId);//1.3 判断当前用户是否有权限if (!permList.contains(curPerm)) {//返回错误信息}//2、执行业务逻辑}
}

现在假如业务B也需要进行权限验证,那么聪明的你一定想到了拷贝一份就好啦!

public class BusinessB {public void do() {//1、判断权限//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(curUserId);//1.3 判断当前用户是否有权限if (!permList.contains(curPerm)) {//返回错误信息}//2、执行业务逻辑}
}

此时聪明的你又发现,两边代码一样,可以提出一个公共方法来处理:


public class BusinessA {public void do(){if (!Util.hasPerm()) {//返回错误信息}//2、执行业务逻辑}
}
public class BusinessB {public void do(){if (!Util.hasPerm()) {//返回错误信息}//2、执行业务逻辑}
}
public class Util {//1、判断权限public static boolean hasPerm(){	//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(Context.currentUserId());//Context中封装了用户信息,提供给上下文使用//1.3 判断当前用户是否有权限return permList.contains(curPerm);}
}

到这里,我们就完成了这次业务的逻辑。过了一段时间你的leader告诉你,有的接口如果标注了@IsNotPerm这个注解,就不需要进行权限验证了。聪明的你马上就想到了在Util方法中加一个参数,改改调用的地方就好了。但此时看着100+的调用链路,你陷入了沉思,为什么当初不说还有这些改动。此时你有些绝望的打开百度,经过一番搜索你发现可以用aop来解决这个问题。

到这里你知道为什么用spring的AOP而不是创建一个工具类来解决呢?

于是你马上写了一个切面信息,然后把权限判断逻辑放入其中,

@Aspect
@Component
@Slf4j
public class LogAspect {private final Logger logger = LoggerFactory.getLogger(LogClient.class);@Pointcut("@annotation(org.sweetie.objectlog.core.annotation.PermValidate)")public void objectLogPointCut() {}@Around(value = "objectLogPointCut()")public <T extends BaseEntity> void saveObject(ProceedingJoinPoint joinPoint) throws Throwable {//获取切入点信息MethodSignature sign = (MethodSignature) joinPoint.getSignature();Method method = sign.getMethod();// 判断有没有IsNotPerm标记IsNotPerm annotation = method.getAnnotation(IsNotPerm.class);boolean allowDoBusiness = annotation != null;// 如果切入点需要校验权限if (!allowDoBusiness) {//1.1 获取当前方法访问路径String curPerm = getCurPerm();//1.2 获取当前用户的权限List permList = getPermByUserId(Context.currentUserId());//Context中封装了用户信息,提供给上下文使用//1.3 判断当前用户是否有权限allowDoBusiness = permList.contains(curPerm);}if(allowDoBusiness) {//执行业务逻辑joinPoint.proceed(joinPoint.getArgs());} else {//抛出异常给controller捕捉}}
}

此时你将业务中的方法进行了改造

public class BusinessA {@PermValidatepublic void do(){//2、执行业务逻辑}
}
public class BusinessB {@PermValidatepublic void do(){//2、执行业务逻辑}
}

此时你发现,业务中调用权限校验的地方都消失了,业务逻辑仿佛一下子轻盈了起来

可以看到aop对业务代码进行了解耦,也方便扩展

Aop思想介绍

Aop的定义

AOP (Aspect Orient Programming),直译过来就是面向切面编程,AOP是一种编程思想,是面向对象编程(OOP)的一种补充。通过上面的例子我们可以发现面向切面编程,能够在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
在这里插入图片描述

AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
一文了解spring事务特性

常见的Aop术语

名称说明
Joinpoint(连接点)指那些被拦截到的点,在Spring中,指可以被动态代理拦截目标类的为方法。
Pointcut(切入点)指要对哪些Joinpoint进行拦截,即被拦截的连接点。
Advice(通知)指拦截到Joinpoint之后要做的事情人即对切入点增强的内容。
Target(目标)指代理的目标对象。
Weaving(植入)指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切入点和通知的结合。

常见的Aop织入时机

时期说明
编译期切面在目标类编译时被织入,这种方式需要特殊的编译器,Aspectj的织入编译器就是以这种方式织入切面的
类加载期切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载我器(ClassLoader),它可以在目标类引入应用之前增强目标类的字节码。
运行期切面在应用运行的某个时期被织入一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP默认采用的就是这种织入方式

常见的Aop的应用场景

  • 日志记录
  • 事务管理
  • 权限验证
  • 性能监测

AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。

SpringAop

Spring AOP就是一款AOP的一种实现,Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。

  • JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
  • CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

下面介绍springAop中常见的通知类型。

Spring常见通知类型

通知说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来,可以实现上述几种通知

下面是通知执行的顺序:
在这里插入图片描述

Spring切入点表达式

表达式类型描述
execution匹配方法切入点
within匹配指定类型
this匹配代理对象实例的类型
target匹配目标对象实例的类型
args匹配方法参数
bean匹配bean的id或名称
@within匹配类型是否含有注解
@target匹配目标对象实例的类型是否含有注解
@annotation匹配方法是否含有注解
@args匹配方法参数类型是否含有注解
  • execution: 匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
    在这里插入图片描述

注意也可以匹配抛出异常类型,省略时匹配任意类型

// 匹配public方法
execution(public * *(..))// 匹配名称以set开头的方法
execution(* set*(..))// 匹配AccountService接口或类的方法
execution(* com.xyz.service.AccountService.*(..))// 匹配service包及其子包的类或接口
execution(* com.xyz.service..*(..))
  • within: 匹配指定类型。匹配指定类的任意方法,不能匹配接口。
// 匹配service包的类
within(com.xyz.service.*)// 匹配service包及其子包的类
within(com.xyz.service..*)// 匹配AccountServiceImpl类
within(com.xyz.service.AccountServiceImpl)
  • this: 匹配代理对象实例的类型,匹配在运行时对象的类型。
// 匹配代理对象类型为service包下的类
this(com.xyz.service.*)// 匹配代理对象类型为service包及其子包下的类
this(com.xyz.service..*)// 匹配代理对象类型为AccountServiceImpl的类
this(com.xyz.service.AccountServiceImpl)

注意:基于 JDK 动态代理实现的 AOP,this 不能匹配接口的实现类,因为代理类和实现类并不是同一种类型,参阅《Spring中的AOP和动态代理》

  • target: 匹配目标对象实例的类型,匹配 AOP 被代理对象的类型。
// 匹配目标对象类型为service包下的类
target(com.xyz.service.*)// 匹配目标对象类型为service包及其子包下的类
target(com.xyz.service..*)// 匹配目标对象类型为AccountServiceImpl的类
target(com.xyz.service.AccountServiceImpl)

在这里插入图片描述

  • args: 匹配方法参数类型和数量,参数类型可以为指定类型及其子类。
// 匹配参数只有一个且为Serializable类型(或实现Serializable接口的类)
args(java.io.Serializable)// 匹配参数个数至少有一个且为第一个为Example类型(或实现Example接口的类)
args(cn.codeartist.spring.aop.pointcut.Example,..)
  • bean: 通过 bean 的 id 或名称匹配,支持 * 通配符。
// 匹配名称以Service结尾的bean
bean(*Service)// 匹配名称为demoServiceImpl的bean
bean(demoServiceImpl)
  • @within: 匹配指定类型是否含有注解。当定义类时使用了注解,该类的方法会被匹配,但在接口上使用注解不匹配。
// 匹配使用了Demo注解的类
@within(cn.codeartist.spring.aop.pointcut.Demo)
  • @target: 匹配目标对象实例的类型是否含有注解。当运行时对象实例的类型使用了注解,该类的方法会被匹配,在接口上使用注解不匹配。
// 匹配对象实例使用了Demo注解的类
@target(cn.codeartist.spring.aop.pointcut.Demo)
  • @annotation: 匹配方法是否含有注解。当方法上使用了注解,该方法会被匹配,在接口方法上使用注解不匹配。
// 匹配使用了Demo注解的方法
@annotation(cn.codeartist.spring.aop.pointcut.Demo)
  • @args: 匹配方法参数类型是否含有注解。当方法的参数类型上使用了注解,该方法会被匹配。
// 匹配参数只有一个且参数类使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo)// 匹配参数个数至少有一个且为第一个参数类使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo,..)
  • 切点表达式的参数匹配: 切点表达式中的参数类型,可以和通知方法的参数通过名称绑定,表达式中不需要写类或注解的全路径,而且能直接获取到切面拦截的参数或注解信息。
  • @Before(“pointcut() && args(name,…)”)
    public void doBefore(String name) {
    … // 切点表达式增加参数匹配,可以获取到name的信息
    }
  • @Before(“@annotation(demo)”)
    public void doBefore(Demo demo) {
    // 这里可以直接获取到Demo注解的信息
    }
    切点表达式的参数匹配同样适用于 @within, @target, @args
  • 使用 &&、|| 和 ! 来组合多个切点表达式,表示多个表达式“与”、“或”和“非”的逻辑关系。这可以用来组合多种类型的表达式,来提升匹配效率。
  • 匹配doExecution()切点表达式并且参数第一个为Account类型的方法:
    @Before(“doExecution() && args(account,…)”)
    public void validateAccount(Account account) {
    // 自定义逻辑
    }

springAop实现

注解和xml开启aop支持方式如下:

//开启配置文件支持
<!--配置Aspectj的自动代理-->
<aop:aspectj-autoproxy/>//开启注解支持
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
基于接口实现

配置通知时需实现org.springframework.aop包下的一些接口:
前置通知:MethodBeforeAdvice
后置通知:AfterReturningAdvice
环绕通知:MethodInterceptor
异常通知:ThrowsAdvice

  • 前置通知拦截器MethodBeforeAdviceInterceptor:
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {private final MethodBeforeAdvice advice;public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//前置处理  这个就是利用反射执行我们定义的前置方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());// 调用链条return mi.proceed();}
}
  • 后置通知拦截器AfterReturningAdviceInterceptor:
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {private final AfterReturningAdvice advice;public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//先执行链条Object retVal = mi.proceed();// 后利用反射执行我们定义的后置通知方法this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;}
}
  • 异常通知拦截器ThrowsAdviceInterceptor :
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {// 省略............@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {try {// 这个就是链条return mi.proceed();} catch (Throwable var4) {// 链条报错了 就异常处理(还需要判断是不是需要处理的异常)// 异常通知可以指定需要处理的异常Method handlerMethod = this.getExceptionHandler(var4);if (handlerMethod != null) {this.invokeHandlerMethod(mi, var4, handlerMethod);}throw var4;}}// 省略...............
}
  • 最终通知AspectJAfterAdvice :
public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJBeforeAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {Object var2;try {// 先执行链条var2 = mi.proceed();} finally {//最终执行this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);}return var2;}}
  • 环绕通知AspectJAroundAdvice :
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {public AspectJAroundAdvice(Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJAroundAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {if (!(mi instanceof ProxyMethodInvocation)) {throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);} else {ProxyMethodInvocation pmi = (ProxyMethodInvocation)mi;ProceedingJoinPoint pjp = this.lazyGetProceedingJoinPoint(pmi);JoinPointMatch jpm = this.getJoinPointMatch(pmi);// 这个就是去执行我们 自己写的环绕通知方法  // 所以环绕通知方法一定会有个参数嘛 joinPoint.proceed()就是执行链条return this.invokeAdviceMethod(pjp, jpm, (Object)null, (Throwable)null);}}protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {return new MethodInvocationProceedingJoinPoint(rmi);}
}

以上就是关于通知链条里面所有最后会执行的方法,可以看到共同点就是invoke方法的传参MethodInvocation ,这不就是我们之前说的连接点嘛,当然还有很多内置的其他拦截器,但这都跟我们AOP拦截器没关系

基于XML配置实现
public class LogAspectj {//前置通知public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知 public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//环绕通知public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【环绕通知中的后置通知】##########");return returnVale;}/*** 异常通知:方法出现异常时,执行该通知*/public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("出现异常:" + ex.getMessage());}}
<!--开启aop支持,注意需要引入aop标签-->
<aop:aspectj-autoproxy/><!--业务组件bean, 需要使用到切面的bean-->
<bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/><!--日志Aspect切面-->
<bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/><!--使用Aspectj实现切面,使用Spring AOP进行配置-->
<aop:config><!--配置切面--><!--注入切面bean--><aop:aspect ref="logAspectjBean"><!--定义Pointcut:通过expression表达式,来查找 特定的方法(pointcut)--><aop:pointcut id="pointcut"expression="execution(* com.apesource.service.impl.*.create*(..))"/><!--配置"前置通知"--><!--在pointcut切入点(serviceMethodPointcut)查找到 的方法执行"前",来执行当前logAspectBean的doBefore--><aop:before method="beforeAdvice" pointcut-ref="pointcut"/><!--配置“后置通知”--><!--returning属性:配置当前方法中用来接收返回值的参数名--><aop:after-returning returning="returnVal" method="afterReturningAdvice" pointcut-ref="pointcut"/> <aop:after method="afterAdvice" pointcut-ref="pointcut"/><!--配置"环绕通知"--><aop:around method="aroundAdvice" pointcut-ref="pointcut"/><!--配置“异常通知”--><!--throwing属性:配置当前方法中用来接收当前异常的参数名--><aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/></aop:aspect></aop:config>
基于注解的实现
//声明当前类为Aspect切面,并交给Spring容器管理
@Component
@Aspect
public class LogAnnotationAspectj {private final static String EXPRESSION = "execution(* com.apesource.service.impl.*.create*(..))";//前置通知   @Before(EXPRESSION)public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知@AfterReturning(value = EXPRESSION,returning = "returnVal")public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}//后置通知@After(EXPRESSION)public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//环绕通知@Around(EXPRESSION)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【环绕通知中的后置通知】##########");return returnVale;}// 异常通知:方法出现异常时,执行该通知@AfterThrowing(value = EXPRESSION,throwing = "ex")public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("********** 【Aspectj异常通知】执行开始 **********");System.out.println("出现异常:" + ex.getMessage());System.out.println("********** 【Aspectj异常通知】执行结束 **********");}}

SpringAop失效场景

  • 避免 Spring 的 AOP 的自调用问题在 Spring 的 AOP 代理下,只能目标方法由外部调用。我们可以使用ltw来解决这个问题https://blog.csdn.net/c39660570/article/details/106791365
  • 切面类未被注册为bean。

SpringAop生效原理

aop切入点

从AOP配置加载点一看便知,开启AOP的配置注解是 @EnableAspectJAutoProxy
在其内部导入了一个类AspectJAutoProxyRegistrar
在这里插入图片描述

<aop:aspectj-autoproxy/>注意这个和上面一样
在这里插入图片描述

AspectJAutoProxyRegistrar这个类实现了ImportBeanDefinitionRegistrar接口,这个接口之前说过了,可以注册BeanDefination,所以我们要看看注册的这个是什么?干了什么?
在这里插入图片描述
沿着那个方法一路往下,发现注册了AnnotationAwareAspectJAutoProxyCreator
在这里插入图片描述

AnnotationAwareAspectJAutoProxyCreator这个类可谓是最重要的类了,从下方的类图上看,它实现了很多接口,还有我们非常熟悉的后置处理器,在这里面主要实现了4个方法:

  • setBeanFactory:实例化后,初始化前调用
  • getEarlyBeanReference:和三级缓存有关,存在循环依赖里面会调用
  • postProcessBeforeInstantiation:实例化前执行
  • postProcessAfterInitialization:初始化后执行

别看有4个方法,其实下面三个方法内部都会调用一样的方法,只是需要注意在Bean生成流程中的介入点
在这里插入图片描述

  • AbstractAutoProxyCreator实例前执行postProcessBeforeInstantiation()

实例前执行,主要是判断代理目标对象是否已经存在了,存在了就走getAdvicesAndAdvisorsForBean方法,然后调用createProxy()方法创建代理对象

Object cacheKey = this.getCacheKey(beanClass, beanName);if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {if (this.advisedBeans.containsKey(cacheKey)) {return null;}if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}// 判断代理目标对象是否已经存在了 存在了就进入代理流程TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);// 创建动态代理对象Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {return null;}
  • 初始化后执行postProcessAfterInitialization

初始化后执行,会调用wrapIfNecessary()方法

//该bean初始化完毕之后,回调该方法判断该bean是否需要被代理
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);//如果该bean未执行过AOP,则进行封装;如果执行过,则不再进行封装if (this.earlyProxyReferences.remove(cacheKey) != bean) {return this.wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

wrapIfNecessary()方法也会调用getAdvicesAndAdvisorsForBean方法来获取对应的通知处理,如果没获取到通知处理方法说明不需要代理,获取到了就要创建代理对象了createProxy()

注意: 这里的通知处理就是切面里面的通知方法,getAdvicesAndAdvisorsForBean就是获取所有的切面类里面的切点及通知方法与Bean来匹配,匹配上了说明这个Bean要被代理,同时会封装匹配的切点对应的所有通知方法返回

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {// 获取该bean的所有的通知处理Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);// 获取的通知处理不为空 说明要代理if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {// 为空就不需要创建代理了  直接返回Beanthis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}} else {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}
}

循环依赖会调用getEarlyBeanReference三级缓存,存在循环依赖则会调用,这里put进去代表已经生成代理了,所以后续初始化后调用的时候会get判断一次,这个也会调用wrapIfNecessary() 方法

public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return this.wrapIfNecessary(bean, beanName, cacheKey);
}

总结: 所以会在Bean实例化前、循环依赖、初始化后介入处理,当然只会处理一次,最终都会调用getAdvicesAndAdvisorsForBean方法来对Bean进行切点匹配,匹配上了就调用createProxy方法生成代理对象然后返回

获取所有切面处理

AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()会先获取所有的切面其下的通知方法,然后根据切点表达式去和这个Bean对象匹配,将匹配成功的通知方法返回,这就说明该Bean需要被代理,匹配成功的通知方法排序后就是需要执行的方法调用链

@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 获取所有切面其下的切面通知方法List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);// 为空返回空数组 不为空转成数组返回return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}// 获取所有切面及其下的切面通知方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 获取所有切面及其下的切面通知方法List<Advisor> candidateAdvisors = this.findCandidateAdvisors();// 从中根据切点筛选出符合Bean的通知方法List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);this.extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors

有个父类的方法是获取一些实现了Advisor接口的Bean,我们重点关注被@Aspect注解标识的Bean的处理

protected List<Advisor> findCandidateAdvisors() {// 获取所有实现了Advisor接口的Bean 有些内置的比如事务List<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {// 获取被注解@Aspect标识的Bean 以及其下的切点和通知方法advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}
  • 处理所有切面其下通知方法: BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors

会遍历所有的Bean找到其中被注解 @Aspect 标识的,然后去处理其下的切点和通知方法

public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized(this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new ArrayList();List<String> aspectNames = new ArrayList();String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);String[] var18 = beanNames;int var19 = beanNames.length;// 遍历所有的Beanfor(int var7 = 0; var7 < var19; ++var7) {String beanName = var18[var7];if (this.isEligibleBean(beanName)) {Class<?> beanType = this.beanFactory.getType(beanName, false);// 判断是否被@Aspect注解标识  标示的就需要去处理其下的切点和通知方法if (beanType != null && this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 去获取其下的切点和通知方法List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);} else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} // 省略..............}}}this.aspectBeanNames = aspectNames;return advisors;}}}// 省略..............
}

获取切面下所有的通知方法

  • ReflectiveAspectJAdvisorFactory.getAdvisors

遍历切面下的所有方法,去找方法上是否有相应的注解,如果有则需要封装处理

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();this.validate(aspectClass);MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new ArrayList();// 获取切面下的所有方法Iterator var6 = this.getAdvisorMethods(aspectClass).iterator();// 遍历所有方法while(var6.hasNext()) {Method method = (Method)var6.next();// 判断该方法是否被相关注解标识  标识的方法处理后封装返回Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);if (advisor != null) {advisors.add(advisor);}}// 省略......return advisors;}
  • 获取具体通知方法:ReflectiveAspectJAdvisorFactory.getAdvisor

遍历我需要的注解,在方法上找注解是否存在,存在的就需要封装处理

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 获取方法上的注解 实际就是遍历需要的注解 一个个找AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());// 没有对应的注解就返回null  有对应的注解就需要处理封装后返回return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {// 看下面方法AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else {// 找到了就设置一下切点上的表达式AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);ajexp.setExpression(aspectJAnnotation.getPointcutExpression());if (this.beanFactory != null) {ajexp.setBeanFactory(this.beanFactory);}return ajexp;}
}
// ASPECTJ_ANNOTATION_CLASSES = new Class[]{Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
protected static AbstractAspectJAdvisorFactory.AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {// 遍历需要的注解,一个一个找Class[] var1 = ASPECTJ_ANNOTATION_CLASSES;int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {Class<?> clazz = var1[var3];AbstractAspectJAdvisorFactory.AspectJAnnotation<?> foundAnnotation = findAnnotation(method, clazz);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}

通知方法的封装

  • InstantiationModelAwarePointcutAdvisorImpl

这个在构造里面就会对通知方法进行处理封装

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {this.declaredPointcut = declaredPointcut;this.declaringClass = aspectJAdviceMethod.getDeclaringClass();this.methodName = aspectJAdviceMethod.getName();this.parameterTypes = aspectJAdviceMethod.getParameterTypes();this.aspectJAdviceMethod = aspectJAdviceMethod;this.aspectJAdvisorFactory = aspectJAdvisorFactory;this.aspectInstanceFactory = aspectInstanceFactory;this.declarationOrder = declarationOrder;this.aspectName = aspectName;if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);this.pointcut = new InstantiationModelAwarePointcutAdvisorImpl.PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);this.lazy = true;} else {this.pointcut = this.declaredPointcut;this.lazy = false;// 封装通知方法this.instantiatedAdvice = this.instantiateAdvice(this.declaredPointcut);}}

ReflectiveAspectJAdvisorFactory.getAdvice 所有的通知方法都会被封装成对应处理类

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();this.validate(candidateAspectClass);AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else if (!this.isAspect(candidateAspectClass)) {throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);}Object springAdvice;// 根据方法上的注解类型 封装对应的通知方法处理类switch(aspectJAnnotation.getAnnotationType()) {case AtPointcut:if (this.logger.isDebugEnabled()) {this.logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");}return null;case AtAround:springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtBefore:springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter:springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning:springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {((AbstractAspectJAdvice)springAdvice).setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing:springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {((AbstractAspectJAdvice)springAdvice).setThrowingName(afterThrowingAnnotation.throwing());}break;default:throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);}((AbstractAspectJAdvice)springAdvice).setAspectName(aspectName);((AbstractAspectJAdvice)springAdvice).setDeclarationOrder(declarationOrder);String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);if (argNames != null) {((AbstractAspectJAdvice)springAdvice).setArgumentNamesFromStringArray(argNames);}((AbstractAspectJAdvice)springAdvice).calculateArgumentBindings();return (Advice)springAdvice;}
}

通知方法与Bean匹配

  • :AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);List var4;try {// 通知方法集合与Bean匹配var4 = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);} finally {ProxyCreationContext.setCurrentProxiedBeanName((String)null);}return var4;
}

总结: 所以这一步会找到所有的切面,遍历其下的所有切点和通知方法,然后根据切点中的表达式去与Bean对象匹配,获取所有匹配成功的通知方法,将这些通知方法排序后就是最后的方法执行链,同时也说明该Bean需要被代理,所以需要创建代理对象

创建代理对象

AbstractAutoProxyCreator.createProxy
这里实际就是在创建代理对象前填充一下必要信息,然后创建代理对象,默认是采用JDK动态代理,如果被代理的目标对象不是接口,则会采用Cglib动态代理

  • CglibAopProxy:Cglib动态代理逻辑类
  • JdkDynamicAopProxy:Jdk动态代理逻辑类(我们以这个为例)
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);// 省略一大段...........// 匹配成功的某些通知方法会被包装成拦截器 上面说过了Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);this.customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (this.advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}ClassLoader classLoader = this.getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();}// 上面设置搞定后 就要获取代理对象 JDK还是Cglibreturn proxyFactory.getProxy(classLoader);}

JdkDynamicAopProxy.getProxy

这一步很简单就是直接创建代理对象,处理类是this,说明该类本身就是处理类

public Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());}return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

代理执行方法

我们以JDK动态代理为例,最终代理对象在执行方法的时候就会调用该方法:

JdkDynamicAopProxy.invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;Class var8;try {//   省略...........if (method.getDeclaringClass() != DecoratingProxy.class) {Object retVal;//   省略...........target = targetSource.getTarget();Class<?> targetClass = target != null ? target.getClass() : null;// 根据具体要执行的方法 再去之前匹配成功的通知方法集合中找对应的增强方法// 前面匹配的通知方法集合并不一定是针对类下的所有方法 所以还需要匹配一次List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 为空说明该方法并不需要增强 所以直接调用原本方法即可if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);} else {// 不为空说明需要增强 所以会包装一个连接点 // 然后执行 调用链条 MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}Class<?> returnType = method.getReturnType();if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {retVal = proxy;} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}Object var12 = retVal;return var12;}var8 = AopProxyUtils.ultimateTargetClass(this.advised);} finally {//   省略...........}return var8;}

原理总结

  • AOP代理对象的生成是在Bean实例化前、循环依赖、初始化后这三个位置判断生成的(以初始化后为主,其他两个阶段属于特殊阶段)
  • 通过获取所有的切面下的通知方法以切点表达式来与Bean匹配,来判断该Bean是否需要被代理,同时准备好了与该Bean相关的所有增强方法
  • AOP默认采用JDK动态代理的方式,如果被代理目标对象不是接口,则会采用Cglib的代理方法
  • AOP的底层原理虽然是动态代理,但是我觉得最重要的还是执行的方法调用链非常巧妙
  • 在逻辑实现上:每种通知在调用链上执行的方式及其执行顺序决定了其扮演的角色

最后附上个执行结构图
在这里插入图片描述

参考博文

Spring AOP切点表达式(Pointcut)详解

SpringAop介绍

SpringAop介绍与原理

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

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

相关文章

机器学习实战宝典:用scikit-learn打造智能应用

书接上文——《数据探险家的终极指南&#xff1a;用Python挖掘机器学习的奥秘》 前文我们在这段精彩的机器学习探险之旅中&#xff0c;从基础概念出发&#xff0c;深入探索了使用Python和scikit-learn库进行数据分析和模型构建的全过程。 我们首先了解了机器学习的基本原理&am…

Mysql 锁

锁 从锁的性能有乐观锁和悲观锁&#xff1b;锁的粒度有行锁、页锁、表锁&#xff1b;锁的对数据库操作类型有读锁、写锁、意向锁 乐观锁&#xff1a;采用cas机制&#xff0c;不会阻塞数据库操作&#xff0c;只会针对当前事务进行失败重试。(用于写操作不多的情况)悲观锁&…

[c++]多态的分析

多态详细解读 多态的概念多态的构成条件 接口继承和实现继承: 多态的原理:动态绑定和静态绑定 多继承中的虚函数表 多态的概念 -通俗的来说&#xff1a;当不同的对象去完成某同一行为时&#xff0c;会产生不同的状态。 多态的构成条件 必须通过基类的指针或者引用调用虚函数1虚…

C语言/数据结构——(链表的回文结构)

一.前言 今天在牛客网上刷到了一道链表题——链表的回文结构https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?&#xff0c;巧合的是它的解题思路恰好是我们一起分享过两道链表题的汇总。这两道题分别是反转链表和链表的中间节点。废话不多数&#xff0c…

Redis 源码安装和入门介绍

Linux下的redis源码安装 redis介绍 Redis 是一个开源&#xff08;BSD许可&#xff09;的&#xff0c;内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构&#xff0c;如 字符串&#xff08;strings&#xff09;&#xff0c;…

智能商品计划系统:引领未来零售业的革新之路

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;和大数据技术已成为推动各行业革新的关键动力。在零售行业中&#xff0c;智能商品计划系统的出现&#xff0c;正逐步改变着传统的商品规划与管理方式&#xff0c;为品牌注入新的活力与竞争力。本文将对智能商…

Java入门基础学习笔记14——数据类型转换

类型转换&#xff1a; 1、存在某种类型的变量赋值给另一种类型的变量&#xff1b; 2、存在不同类型的数据一起运算。 自动类型转换&#xff1a; 类型范围小的变量&#xff0c;可以直接赋值给类型范围大的变量。 byte类型赋值给int类型&#xff0c;就是自动类型转换。 pack…

Android 屏幕适配全攻略(中)-从九宫格到矢量图,揭秘Android多屏幕适配的正确打开方式

在移动互联网时代&#xff0c;无论是小小的手机屏幕&#xff0c;还是大大的平板显示器&#xff0c;Android 应用都必须做到完美适配&#xff0c;给用户以极佳的体验。本文将剖析 Android 多屏幕适配背后的种种技术细节&#xff0c;为您揭开最佳实践的正确打开方式&#xff0c;让…

速卖通ip地址会相互影响吗?如何防止账号关联?

在跨境电商行业&#xff0c;大部分平台都是不允许一个卖家操作多个店铺的&#xff0c;如果被平台检测出账户关联&#xff0c;可能会被封店。在速卖通平台&#xff0c;会通过IP地址来判断是否经营多个账号吗?IP地址会使店铺相互影响吗? 一、速卖通IP地址会关联吗? 首先各位卖…

解决mybatis的配置文件没代码提示的问题

1.将org.apache.ibatis.builder.xml包里的两个dtd文件复制出来&#xff0c;jar包里复制 2.复制dtd的url地址&#xff1a; http://mybatis.org/dtd/mybatis-3-mapper.dtd 一样的做法&#xff01; 3.关闭两个配置文件&#xff0c;重新打开&#xff0c;就可以有代码提示了&…

【智能优化算法】白鲨智能优化算法(White Shark Optimizer,WSO)

白鲨智能优化算法(White Shark Optimizer,WSO)是期刊“KNOWLEDGE-BASED SYSTEMS”&#xff08;中科院一区期刊 IF8.6&#xff09;的2022年智能优化算法 01.引言 白鲨智能优化算法(White Shark Optimizer,WSO)的核心理念和基础灵感来自大白鲨的行为&#xff0c;包括它们在导航和…

从项目开始学习Vue——02(若依框架)

往期&#xff1a; 从项目开始学习Vue——01 目录标题 一、基础插件&#xff08;一&#xff09;路由Vue Router&#xff08;二&#xff09;导航守卫&#xff08;路由拦截器&#xff09;二、Vuex&#xff08;一&#xff09;什么是VuexVuex的部分介绍内容&#xff1a; &#xff08…

QQ超大文件共享(别用,传进去后,压缩都显示不出来,LJ qq!)(共享文件)

文章目录 需要共享双方同时在线开启方法第一次会提示设置默认共享目录&#xff0c;默认是E:\QQFileShare\<qq号>\&#xff1a;然后新建共享会在其后创建共享目录&#xff0c;共享目录中只能共享文件。需要点击添加文件&#xff0c;直接把文件拷贝到目录里好像还不行&…

C语言/数据结构——(相交链表)

一.前言 今天在力扣上刷到了一道题&#xff0c;想着和大家一起分享一下这道题——相交链表https://leetcode.cn/problems/intersection-of-two-linked-lists废话不多说&#xff0c;让我们开始今天的分享吧。 二.正文 1.1题目描述 是不是感觉好长&#xff0c;我也这么觉得。哈…

网络编程套接字和传输层tcp,udp协议

认识端口号 我们知道在网络数据传输的时候&#xff0c;在IP数据包头部有两个IP地址&#xff0c;分别叫做源IP地址和目的IP地址。IP地址是帮助我们在网络中确定最终发送的主机&#xff0c;但是实际上数据应该发送到主机上指定的进程上的&#xff0c;所以我们不仅要确定主机&…

OAuth 2.0 和 OAuth 2.1

OAuth 2.0 和 OAuth 2.1比较&#xff1a; OAuth 2.0 和 OAuth 2.1 是授权框架的不同版本&#xff0c;它们用于允许应用程序安全地访问用户在另一个服务上的数据。以下是它们之间的一些主要区别&#xff1a; 安全性增强&#xff1a;OAuth 2.1 旨在提高安全性&#xff0c;它整合…

什么是云原生架构,我们该如何做好云原生安全,引领云计算时代的应用程序革新

随着云计算技术的飞速发展&#xff0c;企业面临着前所未有的机遇和挑战。在这个高度竞争的市场中&#xff0c;传统的应用程序架构因其僵化、不易扩展和维护的特点&#xff0c;已难以满足当今企业对灵活性、可伸缩性和高效性的追求。在这样的背景下&#xff0c;云原生架构应运而…

git rebase 合并当前分支的多个commit记录

git rebase 合并当前分支的多个commit记录 git rebase 相关的选项和用法step1&#xff1a;找到想要合并的 commitstep2. 使用 rebase -istep3. 编辑提交历史&#xff1a;step4.编辑合并后的提交信息step5.完成 rebase 过程&#xff1a;step6.**推送更新&#xff1a;**step6.**再…

FFmpeg常用API与示例(三)—— 音视频解码与编码

编解码层 1.解码 (1) 注册所有容器格式和 CODEC:av_register_all() (2) 打开文件:av_open_input_file() (3) 从文件中提取流信息:av_find_stream_info() (4) 穷举所有的流&#xff0c;查找其中种类为 CODEC_TYPE_VIDEO (5) 查找对应的解码器:avcodec_find_decoder() (6) …

数据结构-二叉树结尾+排序

一、二叉树结尾 1、如何判断一棵树是完全二叉树。 我们可以使用层序遍历的思路&#xff0c;利用一个队列&#xff0c;去完成层序遍历&#xff0c;但是这里会有些许的不同&#xff0c;我们需要让空也进队列。如果队列里到最后只剩下空那么这棵树就是完全二叉树。具体的实现如下…