一文了解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 {
	 @PermValidate
     public void do(){
		  //2、执行业务逻辑
     }
}
public class BusinessB {
	 @PermValidate
     public 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;
    }

    @Nullable
    public 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;
    }

    @Nullable
    public 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 {
    // 省略............
    @Nullable
    public 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);
    }

    @Nullable
    public 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);
    }

    @Nullable
    public 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 {
            // 为空就不需要创建代理了  直接返回Bean
            this.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;
                 // 遍历所有的Bean
                for(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还是Cglib
        return 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介绍与原理

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++进阶】C++中的map和set

一、关联式容器 在初阶阶段&#xff0c;我们已经接触过STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque&#xff0c; forward_list 等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本…

Llama 3 是怎么回事?Arena 数据分析

4 月 18 日,Meta 发布了他们最新的开放权重大型语言模型 Llama 3。从那时起,Llama 3-70B 就在 English Chatbot Arena 排行榜上迅速上升,拥有超过 50,000 次对战。Meta 的这一非凡成就对开源社区来说是个好消息。在这篇博文中,我们旨在深入探讨为什么用户将 Llama 3-70b 与 GPT…

【第17章】spring-mvc之日志和拦截器

文章目录 前言一、整合log4j1. 引入库2. log4j2.xml 二、拦截器1.拦截器类2.注册拦截器 三、过滤器和拦截器顺序总结 前言 【第2章】整合log4j2框架 在前面的spring中已经完成了对日志框架log4j的整合&#xff0c;这里我们直接拿过来用就行。 场景描述&#xff1a;每个接口请…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

Web实时通信的学习之旅:轮询、WebSocket、SSE的区别以及优缺点

文章目录 一、通信机制1、轮询1.1、短轮询1.2、长轮询 2、Websocket3、Server-Sent Events 二、区别1、连接方式2、协议3、兼容性4、安全性5、优缺点5.1、WebSocket 的优点&#xff1a;5.2、WebSocket 的缺点&#xff1a;5.3、SSE 的优点&#xff1a;5.4、SSE 的缺点&#xff1…

实用的Chrome命令 帮你打开Chrome浏览器的隐藏功能

前言 Chrome作为主力浏览器&#xff0c;支持相当丰富的第三方扩展&#xff0c;其实浏览器本身也内置了大量实用的命令。许多实用的功能并没有直接显示在Chrome的菜单上。在这篇文章中&#xff0c;我们将介绍几个实用的chrome:// commands。 通过下面整理的 Chrome 命令&#x…

nginx_01

1.安装 yum install epel-release -y # 安装yum的扩展包 yum install nginx -y systemctl start nginx.service #启动nginx systemctl enable nginx.service # netstat -lntup # 查看端口占用情况 # 可以看到nginx默认占用了80端口 2.nginx配置 # 注意配置文件的语法格式…

macOS Sonoma 无法打开分段式Dmg文件的解决办法

在macOS Sonoma 14.X及更高版本的系统中&#xff0c;用户可能会遇到一个棘手的问题&#xff1a;无法直接打开“分段式”DMG&#xff08;磁盘映像&#xff09;安装包文件。这种情况通常发生在尝试安装一些大型软件或游戏时&#xff0c;尤其是那些因为文件体积巨大而采用分段压缩…

开源AI大模型测评网站

1、排行榜 多个 AI 模型的排行榜和详细的性能评估,包括总排行榜、基础能力排行榜、安全类模型排行榜、金融领域应用排行榜、汽车领域应用排行榜以及工业领域应用排行榜 地址:SuperCLUEhttps://www.superclueai.com/ 2.报告合集 内容体系:代表性的数据集、基线(预训练)模型…

Kivy UI界面

一、版本介绍 Ubuntu&#xff1a;18.04.6 LTS Conda&#xff1a;4.5.12 Python&#xff1a;3.6.15 Kivy&#xff1a;2.0.0 二、安装Kivy # 更新系统包列表 sudo apt-get update# 安装Kivy的依赖项 sudo apt-get install -y python-pip libsdl2-dev libsdl2-image-dev li…

【JavaWeb】网上蛋糕商城后台-商品管理

概念 本文讲解和实现网上蛋糕商城的后台管理系统中的商品管理功能。 商品列表 点击后台管理系统的head.jsp头部的“商品管理”功能选项&#xff0c;向服务器发送请求/admin/goods_list 因此需要在servlet包中创建AdminGoodsListServlet类&#xff0c;用于获取商品信息列表 …

TDM(BPM)-MIMO-FMCW雷达MATLAB仿真

本文通过对车载毫米波雷达信号流程和链路的仿真&#xff0c;建立基本的算法框架&#xff0c;可用于算法性能的验证。并提供基础MATLAB仿真代码&#xff0c;作为分享和参考。 一、信号的产生 车载毫米波雷达广泛使用线性调频连续波雷达&#xff0c;也即发射信号频率随时间线性变…

C++ | Leetcode C++题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution { public:bool exist(vector<vector<char>>& board, string word) {rows board.size();cols board[0].size();for(int i 0; i < rows; i) {for(int j 0; j < cols; j) {if (dfs(board, word, i, …

面向对象设计之套路——设计模式

1、总则 面向对象的分析设计编程思想&#xff0c;通过封装、继承、多态把程序的耦合度降低&#xff0c;用设计模式使得程序更加灵活&#xff0c;容易修改&#xff0c;并且易于复用。 让业务逻辑与界面逻辑分开&#xff0c;让它们的耦合度下降&#xff0c;只有分离&#xff0c;…

jenkins部署想定报错

报错&#xff1a; 解决办法&#xff1a; 登录被编译的设备&#xff0c;清楚旧代码&#xff0c;在重新执行

亲测有效!关键点检测——COCO格式转YOLO格式代码!!!

话不多收&#xff0c;直接上代码&#xff0c;这个我也是找了好久的&#xff0c;分享不易&#xff0c;给个鼓励&#xff01;&#xff08;记得点赞收藏&#xff09; 大家可以直接使用此代码转换你自己的数据集&#xff0c;路径换成你自己的就行了&#xff0c;注意路径格式&#x…

Springboot集成SpringbootAdmin实现服务监控管理-10

SpringbootAdmin Spring Boot Admin是一个用于管理和监控Spring Boot应用程序的开源软件。 概要介绍 Spring Boot Admin可以监控Spring Boot单机或集群项目&#xff0c;它提供了详细的健康&#xff08;Health&#xff09;信息、内存信息、JVM系统和环境属性、垃圾回收信息、…

AI自动生成PPT工具上新

AI大模型能力持续增强&#xff0c;零一万物&#xff08;李开复领导的团队&#xff09;推出的万知只是其中的一个缩影&#xff0c;生成PPT也只是其中一个能力。 如果你还没用WPSAI的PPT自动生成能力&#xff08;WPS Office AI实战总结&#xff0c;智能化办公时代已来&#xff09…

web安全之登录框渗透骚姿势,新思路

不管漏洞挖掘还是挖SRC&#xff0c;登录框都是重点关注对象&#xff0c;什么漏洞都有可能出现&#xff0c; 本篇文章做个总结&#xff0c;后面发现新思路后会继续更新 万能密码 or 弱口令 SQL注入 水平越权 垂直越权 逻辑漏洞 短信轰炸 邮箱轰炸 信息泄露 验证码DOS XSS万能密…

【C++模板入门】

C模板入门 泛型编程函数模板格式原理函数模板的实例化 类模板 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp l…