【Spring底层分析】Spring AOP基本使用+万字底层源码阅读分析

news2025/6/5 14:16:48

一、AOP基本使用

三步:

  • 将业务逻辑组件和切面类都加入到容器中,告诉Spring哪个是切面类(@Aspect)
  • 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时(@Before、@After、@Around……)何地运行(切入点表达式)
  • 开启基于注解的aop模式,@EnableAspectJAutoProxy

定义业务逻辑类、切面类:

public class MathCalculator {

    public int div(int i, int j){
        return i / j;
    }
}
@Aspect //需要标注此注解,spring才知道这是切面类
public class LogAspects {
    @Pointcut("execution(public int org.example.MathCalculator.*(..))")
    public void pointCut(){};

    //@Before(value = "public int org.example.MathCalculator.div(int, int)")
    //@Before("public int org.example.MathCalculator.*(..)")
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("除法方法:" + joinPoint.getSignature().getName() + "开始运行,参数列表是:" + Arrays.asList(args));
    }
    @After("pointCut()")
    public void logEnd(){
        System.out.println("除法方法结束运行");
    }
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(Object result){
        System.out.println("除法正常运行并返回结果:" + result);
    }
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(Exception exception){
        System.out.println("除法异常运行,异常信息:" + exception);
    }

}

将业务逻辑类和切面类注册到spring容器中(这里我用的方式是配置类方式):

@EnableAspectJAutoProxy
@Configuration
public class AopConfiguration {
    // 业务逻辑类加入容器中
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    // 切面类加入容器中
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

测试:

public class AopTest {
    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfiguration.class);

        //MathCalculator mathCalculator = new MathCalculator();不是自己创建对象,而是从spring拿对象,这样切面才会生效
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        mathCalculator.div(4,2); //测试目标方法是否被增强

        applicationContext.close();
    }
}

在这里插入图片描述

二、AOP原理

总结:

1)、@EnableAspectJAutoProxy 开启AOP功能
2)、@EnableAspectJAutoProxy 会给容器中注册一个组件:AnnotationAwareAspectJAutoProxyCreator
3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;

4)、容器的创建流程:
1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator后置处理器

​ 2)、 finishBeanFactoryInitialization () 初始化剩下的单实例bean

​ 1)、如创建业务逻辑类组件、切面类组件

​ 2)、AnnotationAwareAspectJAutoProxyCreator会拦截以上组件的创建过程:组件创建完之后,判断组件是否需要增强?

​ 是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib)

5)、执行目标方法

​ 1)代理对象执行目标方法

​ 1)CglibAopProxy.intercept()来拦截目标方法

​ 2)得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)

​ 3)利用拦截器的链式机制,依次进入每一个拦截器进行执行

​ 4)效果:

​ 正常执行:前置通知->目标方法->后置通知->返回通知
​ 出现异常:前置通知->目标方法->后置通知->异常通知

分析AOP原理时,我们从这个注解开始分析:

启示,如果想研究xx原理,就可以从@EnableXXX注解分析,分析是否给容器注入了什么组件,我们只需要搞清楚这个组件的作用是什么,就可以分析出原理。

一、从@EnableAspectJAutoProxy注解开始分析

1、@EnableAspectJAutoProxy

在这里插入图片描述

  • AspectJAutoProxyRegistrar.class

在这里插入图片描述

  • 跟进registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中,一路跟进,会进入以下方法:

在这里插入图片描述

也就是说@EnableAspectJAutoProxy注解的作用就是给容器中注入bean定义,为后边往容器中注入组件做准备。

internalAutoProxyCreator(组件名),AnnotationAwareAspectJAutoProxyCreator(组件类型)

所以接下来我们就是探究AnnotationAwareAspectJAutoProxyCreator组件的是功能是什么,何时工作?

  • AnnotationAwareAspectJAutoProxyCreator.class

在这里插入图片描述

  • AspectJAwareAdvisorAutoProxyCreator.class

在这里插入图片描述

  • AbstractAdvisorAutoProxyCreator.class

在这里插入图片描述

  • AbstractAutoProxyCreator.class

在这里插入图片描述

可以得出结论,AbstractAutoProxyCreator是一个后置处理器,还实现了aware接口,以下是重写的方法:

在AbstractAutoProxyCreator类中重写了后置处理器的两个方法:

在这里插入图片描述

在这里插入图片描述

在AbstractAdvisorAutoProxyCreator类中重写了aware接口的方法:

在这里插入图片描述

  • SmartInstantiationAwareBeanPostProcessor相较于普通的后置处理器,额外还有以下两个需要重写的方法XXXInstantiation,这两个方法的执行时机也与XXXInitialization方法执行时机(bean初始化前后执行)不同。我们之后会分析。

在这里插入图片描述

在这里插入图片描述

以上是与AOP有关的组件的大致介绍。接下来我们启动AOP的测试demo,从容器创建、创建单实例bean、调用目标方法来分析AOP是怎么起作用的。

这里启动的demo就是本文介绍AOP基本使用的那个demo。

二、启动容器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1)registerBeanPostProcessors():注册所有后置处理器到容器中

1、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor的名字。其中一个就是AbstractAutoProxyCreator后置处理器的,这个定义就是在通过@EnableAspectJAutoProxy创建的

在这里插入图片描述

2、注册后置处理器

2.1、优先注册实现了PriorityOrdered接口的BeanPostProcessor;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2、再给容器中注册实现了Ordered接口的BeanPostProcessor;这里注册的就是我们的AbstractAutoProxyCreator。因为AbstractAutoProxyCreator后置处理器实现了@order接口。

在这里插入图片描述

2.3、注册没实现优先级接口的BeanPostProcessor;

后置处理器注册到容器中的过程:

1、创建bean实例:createBeanInstance()

2、属性赋值:populateBean()

3、初始化bean:initializeBean(),进入initializeBean()

3.1.invokeAwareMethods() -> 如果该bean实现aware接口,则执行重写aware接口的方法

3.2.applyBeanPostProcessorsBeforeInitialization()->执行该bean实现后置处理器接口,重写的postProcessBeforeInitialization()

3.3.invokeInitMethods()->执行初始化方法

3.4.applyBeanPostProcessorsAfterInitialization()->执行该bean实现后置处理器接口,重写的postProcessAfterInitialization()

3、将后置处理器注册到beanFactory中。

在这里插入图片描述

以上,AbstractAutoProxyCreator后置处理器已经注册到容器中了,这个后置处理器会在注册单实例bean(MathCalculator)时起作用。

2)finishBeanFactoryInitialization:注册所有单实例bean到容器中

在这里插入图片描述

1、先尝试用后置处理器获取代理对象【只有当后置处理是InstantiationAwareBeanPostProcessor类型时才有效】

resolveBeforeInstantiation()

在这里插入图片描述

由于AbstractAutoProxyCreator后置处理器就是InstantiationAwareBeanPostProcessor类型的,因此就会执行AbstractAutoProxyCreator里重写的postProcessBeforeInstantiation方法。【我们主要关心MathCalculator、LogAspects】

1.1、applyBeanPostProcessorsBeforeInstantiation()->postProcessBeforeInstantiation()

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        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;
        }
    }

===========================
    最后直接返回null

1.2、applyBeanPostProcessorsAfterInitialization()->postProcessAfterInitialization()

因为postProcessBeforeInstantiation返回null,因此这里这个方法不执行。

2、真正创建bean实例

doCreateBean()
createBeanInstance()

在这里插入图片描述

populateBean()
initializeBean()

在这里插入图片描述

applyBeanPostProcessorsAfterInitialization()->postProcessAfterInitialization()

在这里插入图片描述

在这里插入图片描述

wrapIfNecessary():如果需要则包装成代理对象

在这里插入图片描述

1)获取当前bean的且已排好序的增强方法:Object[] specificInterceptors

  • 获取所有增强器(通知方法)
  • 找哪些通知方法是需要切入当前bean方法的
  • 给增强器排序

2)保存当前bean到advisedBeans中:this.advisedBeans.put(cacheKey, Boolean.TRUE);

3)创建当前bean的代理对象:this.createProxy()

在这里插入图片描述

  • 获取所有增强器(通知方法)

    在这里插入图片描述

  • 保存到proxyFactory

  • 创建代理对象:Spring自动决定

    • JdkDynamicAopProxy(config)->jdk动态代理
    • ObjenesisCglibAopProxy(config)->cglib的动态代理
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
            if (NativeDetector.inNativeImage() || !config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
                return new JdkDynamicAopProxy(config);
            } else {
                Class<?> targetClass = config.getTargetClass();
                if (targetClass == null) {
                    throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
                } else {
                    return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) && !ClassUtils.isLambdaClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
                }
            }
        }
    

4)最后返回代理对象给容器:return proxy


注意,以下情况则返回的是原始对象:

  • 核心基础设施类Advisor, Advice, Pointcut 的实现
  • 切面类:被 @Aspect 注解的类(通过 shouldSkip() 显式排除)
  • 无匹配的 Advisor 的类

解释:核心基础设施类是怎么得到的?

假设有以下切面:

@Aspect
public class SecurityAspect {
    @Around("@annotation(RequireAuth)")
    public Object checkAuth(ProceedingJoinPoint pjp) {
        // 权限检查逻辑
    }
}

Spring会创建以下基础设施对象:

// 1. 创建Pointcut
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("@annotation(RequireAuth)");

// 2. 创建Advice
Advice advice = new MethodInterceptor() {
    public Object invoke(MethodInvocation mi) {
        // 包装checkAuth方法的逻辑
    }
};

// 3. 创建Advisor
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

那么,Advice与Advisor的区别在哪?

  • Advisor:封装了 Pointcut(切点) + Advice(增强)。定义"在哪里增强"和"如何增强"的完整规则

  • Advice:只包含增强行为。定义"具体增强什么"

    • @Around:AspectJAroundAdvice
    • @Before:AspectJMethodBeforeAdvice
    • @After:AspectJAfterAdvice
    • @AfterReturning:AspectJAfterReturningAdvice
    • @AfterThrowing:AspectJAfterThrowingAdvice
@Aspect
public class LogAspect {
    // 👇 被拆解为 Pointcut + Advice
    @Before("execution(* com.service.*.*(..))") 
    public void logBefore() {
        System.out.println("Before method");
    }
}

转换过程:

  1. @Before 注解 → 创建 AspectJExpressionPointcut(Pointcut)
  2. logBefore() 方法 → 创建 MethodBeforeAdviceInterceptor(Advice)
  3. 组合两者 → 创建 InstantiationModelAwarePointcutAdvisor(Advisor)

三、调用目标方法

前面我们分析完启动容器后,容器会生成代理对象。当代理对象调用目标方法时,就会走增强的逻辑,具体是怎样的过程,我们现在来分析。

在这里插入图片描述

在这里插入图片描述

👆容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx)

1)CglibAopProxy.intercept()

在这里插入图片描述

1、根据ProxyFactory对象获取将要执行的目标方法拦截器链:List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

getInterceptorsAndDynamicInterceptionAdvice()
  • List interceptorList = new ArrayList(advisors.length);interceptorList保存所有拦截器5:一个默认的ExposeInvocationInterceptor 和4个增强器;

    • 遍历所有的增强器,将其转为Interceptor;

      Interceptor[] interceptors = registry.getInterceptors(advisor)

      interceptorList.addAll(Arrays.asList(interceptors));

    • 将增强器转为List; 如果是MethodInterceptor,直接加入到集合中。如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor; 转换完成返回MethodInterceptor数组。(统一转换为 MethodInterceptor目的是标准化执行接口,实现调用链的通用处理机制)

      MethodInterceptor[] interceptors = registry.getInterceptors(advisor);

      interceptorList.addAll(Arrays.asList(interceptors));

      public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
              List<MethodInterceptor> interceptors = new ArrayList(3);
              Advice advice = advisor.getAdvice();
              if (advice instanceof MethodInterceptor) { // 如果增强器是MethodInterceptor类型,那么直接添加
                  interceptors.add((MethodInterceptor)advice);
              }
      
              Iterator var4 = this.adapters.iterator();
      
              while(var4.hasNext()) {
                  AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
                  if (adapter.supportsAdvice(advice)) { // 否则使用适配器进行转换
                      interceptors.add(adapter.getInterceptor(advisor));
                  }
              }
      
              if (interceptors.isEmpty()) {
                  throw new UnknownAdviceTypeException(advisor.getAdvice());
              } else {
                  return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
              }
          }
      
      
      ===================================
          1public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice implements AfterReturningAdvice, AfterAdvice, Serializable {}
      --->未实现MethodInterceptor接口,因此需要使用对应适配器转换。
          1.1、转换方法:adapter.getInterceptor(advisor)public MethodInterceptor getInterceptor(Advisor advisor) {
              AfterReturningAdvice advice = (AfterReturningAdvice)advisor.getAdvice();
              return new AfterReturningAdviceInterceptor(advice);
          }
      也就是说,将AspectJAfterReturningAdvice-->AfterReturningAdviceInterceptor1.2、还有AspectJMethodBeforeAdvice-->MethodBeforeAdviceInterceptor
          
          2、其他的advice本身就实现了MethodInterceptor接口,因此无需转变:
          public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {}
      	public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {}
      	public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {}
      
          
      =================================
          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;
          }
      }
      =================================
          @FunctionalInterface
          public interface MethodInterceptor extends Interceptor {
              @Nullable
              Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
          }
      
      可以看到,MethodInterceptor接口里只包含一个invoke抽象方法,将advice都转换为MethodInterceptor类型,就是为了之后统一调用invoke方法。
      

得到的拦截器链如下:

在这里插入图片描述

2、如果没有拦截器链,直接执行目标方法。

3、如果有拦截器链,把需要执行的目标对象、目标方法、拦截器链等信息传入创建一个CglibMethodInvocation对象,
并调用 Object retVal = mi.proceed();

retVal = (new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();

有了拦截器链后,我们现在来分析proceed方法,探究是怎么个拦截法。

在这里插入图片描述

递归过程就是:

1、依次按顺序0-4遍历每个拦截器,然后执行invoke方法。

2、0-3号的拦截器中的invoke方法中,第一步都是直接递归调用proceed方法。

3、直到遍历到4号拦截器,也就是MethodBeforeAdviceInterceptor拦截器。

在这里插入图片描述

4、接着再一次进入proceed方法时,由于满足出递归的条件判断,因此执行目标方法。

5、目标方法执行完则返回到上一个拦截器的invoke方法中。3号拦截器就是AspectJAfterAdvice。

在这里插入图片描述

6、接着继续返回到上一层拦截器的invoke方法中。2号拦截器是AfterReturningAdviceInterceptor。执行对应的增强方法。(只有没有异常才会执行。)

7、接着继续返回到上一层拦截器的invoke方法中。1号拦截器是AspectJAfterThrowingAdvice。执行对应的增强方法。(有异常才会执行。)

以上,就是整个拦截器调用过程。

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

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

相关文章

新德通科技:以创新驱动光通信一体化发展,赋能全球智能互联

在数字经济与AI技术高速发展的今天&#xff0c;光通信作为信息传输的核心基础设施&#xff0c;正迎来前所未有的升级浪潮。深圳新德通科技有限公司&#xff08;以下简称“新德通科技”&#xff09;凭借其深厚的技术积累与一体化产品布局&#xff0c;成为行业内的中坚力量。本文…

C++ 内存泄漏检测器设计

文章目录 1. C中的动态内存分配2. 什么是内存泄漏3. 内存泄漏的代码案例4. 内存泄漏检查器的设计模块1&#xff1a;位置信息捕获&#xff1a;模块2&#xff1a;内存分配跟踪&#xff1a;模块3&#xff1a;内存释放跟踪&#xff1a;模块4&#xff1a;泄漏记录存储&#xff1a;模…

破局与进阶:ueBIM 在国产 BIM 赛道的差距认知与创新实践

作为国产BIM领域的探索者&#xff0c;斯维尔ueBIM自诞生以来始终以追赶国际头部技术为目标&#xff0c;但不可否认的是&#xff0c;在核心功能覆盖、行业生态成熟度以及全球市场占有率等方面&#xff0c;我们与Autodesk Revit、Bentley Systems等国际巨头仍存在显著差距。这种差…

分布式流处理与消息传递——向量时钟 (Vector Clocks) 算法详解

Java 实现向量时钟 (Vector Clocks) 算法详解 一、向量时钟核心原理 #mermaid-svg-JcZ1GT0r1ZNSy6W7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JcZ1GT0r1ZNSy6W7 .error-icon{fill:#552222;}#mermaid-svg-JcZ…

20250603在荣品的PRO-RK3566开发板的Android13下的命令行查看RK3566的温度

20250603在荣品的PRO-RK3566开发板的Android13下的命令行查看RK3566的温度 2025/6/3 11:58 RK3566的cpu运行效率 top rk3566_t:/ # rk3566_t:/ # rk3566_t:/ # cd /sys/class/thermal/ rk3566_t:/sys/class/thermal # ls -l rk3566_t:/sys/class/thermal # cd thermal_zone0/ r…

帝可得 - 设备管理

一. 需求说明 设备管理主要涉及到三个功能模块&#xff0c;业务流程如下&#xff1a; 新增设备类型: 允许管理员定义新的售货机型号&#xff0c;包括其规格和容量。 新增设备: 在新的设备类型定义后&#xff0c;系统应允许添加新的售货机实例&#xff0c;并将它们分配到特定的…

【iOS安全】使用LLDB调试iOS App | LLDB基本架构 | LLDB安装和配置

LLDB基本架构 参考&#xff1a; https://crifan.github.io/ios_re_dynamic_debug/website/debug_code/lldb_debugserver.html https://book.crifan.org/books/ios_re_debug_debugserver_lldb/website/ LLDB安装和配置 1. 让iPhone中出现/Developer/usr/bin/debugserver 最初…

Idea 配置 Maven 环境

下载 Maven 官网&#xff1a;https://maven.apache.org/index.html 点击左侧 Downloads&#xff0c;然后选择 Files 中的 zip 包下载&#xff08;下载慢可以使用迅雷&#xff09; 配置 Maven 将压缩包解压&#xff0c;比如我解压后放到了 D:\developer\environment\apache-…

Kafka 如何保证不重复消费

在消息队列的使用场景中&#xff0c;避免消息重复消费是保障数据准确性和业务逻辑正确性的关键。对于 Kafka 而言&#xff0c;保证不重复消费并非单一机制就能实现&#xff0c;而是需要从生产者、消费者以及业务层等多个维度协同配合。接下来&#xff0c;我们将结合图文详细解析…

RNN结构扩展与改进:从简单循环网络到时间间隔网络的技术演进

本文系统介绍 RNN 结构的常见扩展与改进方案。涵盖 简单循环神经网络&#xff08;SRN&#xff09;、双向循环神经网络&#xff08;BRNN&#xff09;、深度循环神经网络&#xff08;Deep RNN&#xff09; 等多种变体&#xff0c;解析其核心架构、技术特点及应用场景&#xff0c;…

类 Excel 数据填报

类 Excel 填报模式&#xff0c;满足用户 Excel 使用习惯 数据填报&#xff0c;可作为独立的功能模块&#xff0c;用于管理业务流程、汇总采集数据&#xff0c;以及开发各类数据报送系统&#xff0c;因此&#xff0c;对于报表工具而言&#xff0c;其典型场景之一就是利用报表模…

Office文档图片批量导出工具

软件介绍 本文介绍一款专业的Office文档图片批量导出工具。 软件特点 这款软件能够批量导出Word、Excel和PPT中的图片&#xff0c;采用绿色单文件设计&#xff0c;体积小巧仅344KB。 基本操作流程 使用方法十分简单&#xff1a;直接将Word、Excel或PPT文件拖入软件&#xf…

【iOS】ARC 与 Autorelease

ARC 与 Autorelease 文章目录 ARC 与 Autorelease前言何为ARC内存管理考虑方式自己生成的对象,自己持有非自己生成的对象,自己也可以持有不再需要自己持有的对象时释放非自己持有的对象无法释放 ARC的具体实现编译期和运行期ARC做的事情ARC实现: __autoreleasing 与 Autoreleas…

铁电液晶破局 VR/AR:10000PPI 重构元宇宙显示体验

一、VR/AR 沉浸感困境&#xff1a;传统显示技术的天花板在哪&#xff1f; &#xff08;一&#xff09;纱窗效应与眩晕感&#xff1a;近眼显示的双重枷锁 当用户戴上 VR 头显&#xff0c;眼前像素网格形成的 “纱窗效应” 瞬间打破沉浸感。传统液晶 500-600PPI 的像素密度&…

竞争加剧,美团的战略升维:反内卷、科技与全球化

5月26日&#xff0c;美团发布2025年第一季度业绩报告&#xff0c;交出了一份兼具韧性与创新性的成绩单。 报告显示&#xff0c;公司一季度总营收866亿元&#xff0c;同比增长18%&#xff1b;核心本地商业收入643亿元&#xff0c;同比增长18%&#xff1b;季度研发投入58亿元&a…

(17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。

&#xff08;89&#xff09;例三登录时间 &#xff1a; 保留代码版本 &#xff1a; CREATE TABLE sql_8( user_id varchar(2), login_date date ); insert into sql_8(user_id,login_date) values(A,2024-09-02),(A,2024-09-03),(A,2024-09-04),(B,2023-11-25),(B,2023-12- 3…

高性能分布式消息队列系统(二)

上一篇博客将C进行实现消息队列的用到的核心技术以及环境配置进行了详细的说明&#xff0c;这一篇博客进行记录消息队列进行实现的核心模块的设计 五、项目的需求分析 5.1、项目框架的概念性理解 5.1.1、消息队列的设计和生产消费者模型的关系 在现代系统架构中&#xff0c;…

华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《天然蓄水库》: 目录 题目…

【Harmony OS】数据存储

目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题&#xff0c;使得数据能够存储在外存中&#xff0c;达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…

MybatisPlus--核心功能--service接口

Service接口 基本用法 MyBatisPlus同时也提供了service接口&#xff0c;继承后一些基础的增删改查的service代码&#xff0c;也不需要去书写。 接口名为Iservice&#xff0c;而Iservice也继承了IRepository&#xff0c;这里提供的方法跟BaseMapper相比只多不少&#xff0c;整…