Spring高手之路22——AOP切面类的封装与解析

news2024/9/22 4:11:29

文章目录

  • 1. AOP是如何收集切面类并封装的?
  • 2. Advisor 是什么,它是怎么构建的?
    • 2.1 什么是 Advisor
    • 2.2 Advisor 的构建(源码分析+时序图说明)
  • 3. TargetSource 的构建和作用
    • 3.1 TargetSource 的作用
    • 3.2 TargetSource 的构建

1. AOP是如何收集切面类并封装的?

Spring 中,AOPAspect-Oriented Programming,面向切面编程)通过以下几个步骤收集切面类并进行封装:

1. 定义切面类:

  • 切面类通过 @Aspect 注解来标记,表示这是一个切面。
  • 在切面类中定义通知(advice),例如 @Before@After@Around 等,用于指定在目标方法执行的不同阶段要执行的逻辑。

2. 配置切面扫描:

  • Spring 容器在启动时,会根据配置或注解扫描包路径,寻找带有 @Aspect 注解的类。
  • 通过在配置类上添加 @EnableAspectJAutoProxy 注解启用 AOP 自动代理机制。

3. 解析切面类:

  • Spring 使用 AspectJAnnotationAutoProxyCreator 或类似的类来扫描和解析 @Aspect 注解的类。
  • 在解析过程中,Spring 通过反射机制查找切面类中定义的通知方法,并解析这些方法上面的注解,例如 @Before@After 等。

4. 创建切面和通知对象:

  • 解析切面类后,Spring 会为每个切面创建 AspectMetadata 对象,并将这些元数据保存在 AdvisedSupport 类中。
  • AdvisedSupport 持有目标对象(被代理的类)和通知(advice)列表。

5. 织入切面:

  • Spring 容器创建 bean 的过程中,如果发现某个 bean 需要 AOP 功能,Spring 会为其创建代理对象。
  • Spring 使用 ProxyFactory 或类似的工厂类,根据目标对象和通知列表创建代理对象。
  • 代理对象会将通知逻辑织入到目标方法的调用链中。

6. 执行代理逻辑:

  • 当客户端代码调用代理对象的方法时,代理对象会首先执行切面中的通知逻辑,然后再调用目标方法。
  • 例如,在 @Around 通知中,可以在目标方法执行前后执行额外的逻辑,或者在 @Before 通知中执行方法前的逻辑。

具体的执行流程如下图所示:

定义切面类:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}

配置切面扫描:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // other bean definitions
}

  通过以上步骤,Spring AOP 机制能够自动收集、解析和织入切面类,实现面向切面编程的功能。这种机制在保持业务逻辑和横切关注点分离的同时,也提升了代码的可读性和可维护性。

2. Advisor 是什么,它是怎么构建的?

2.1 什么是 Advisor

  在 Spring AOP 中,Advisor 是包含 AdvicePointcut 的一个概念。Advice 是要执行的具体动作(如方法前、后或环绕方法),而 Pointcut 是定义哪些连接点应该应用这些 Advice 的规则。

2.2 Advisor 的构建(源码分析+时序图说明)

本节源码基于 spring-aop-5.3.16

  在 Spring AOP 中,Advisor 的构建是通过 AspectJAdvisorFactory 来实现的。ReflectiveAspectJAdvisorFactoryAspectJAdvisorFactory 的一个具体实现。

来看看ReflectiveAspectJAdvisorFactorygetAdvisors 方法:

在这里插入图片描述

源码提出来分析:

/**
 * 从给定的 MetadataAwareAspectInstanceFactory 获取所有的 Advisor。
 * 这个方法会处理切面类的方法和字段,创建相应的 Advisor 列表。
 *
 * @param aspectInstanceFactory MetadataAwareAspectInstanceFactory 实例,用于获取切面元数据和实例
 * @return 包含所有 Advisor 的列表
 */
@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    // 获取切面类的元数据
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    // 获取切面类的名称
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    // 验证切面类是否合法
    validate(aspectClass);

    // 我们需要用一个装饰器包装 MetadataAwareAspectInstanceFactory,以便它只实例化一次
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

    // 创建一个保存 Advisor 的列表
    List<Advisor> advisors = new ArrayList<>();
    // 遍历切面类的方法,获取所有符合条件的 Advisor 方法
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 在 Spring Framework 5.2.7 之前,advisors.size() 被作为 declarationOrderInAspect
        // 提供给 getAdvisor(...) 以表示在已声明方法列表中的“当前位置”。
        // 然而,自 Java 7 以来,“当前位置”已不再有效,因为 JDK 不再按源代码中声明的顺序返回声明的方法。
        // 因此,我们现在将 declarationOrderInAspect 硬编码为 0,以便通过反射发现的所有通知方法,
        // 以支持 JVM 启动期间的可靠通知排序。
        // 具体来说,值 0 与 AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor) 中使用的默认值对齐。
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
        if (advisor != null) {
            // 将 Advisor 添加到列表中
            advisors.add(advisor);
        }
    }

    // 如果它是每个目标实例化的切面,则发出虚拟实例化的切面
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // 创建并添加一个合成的实例化 Advisor 到列表的开头
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    // 查找引入字段
    for (Field field : aspectClass.getDeclaredFields()) {
        // 获取 DeclareParents 的 Advisor
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            // 将 Advisor 添加到列表中
            advisors.add(advisor);
        }
    }

    // 返回所有的 Advisor
    return advisors;
}

/**
 * 从给定的方法和切面实例工厂获取一个 Advisor。
 * 这个方法会处理切面类的方法并创建相应的 Advisor。
 *
 * @param candidateAdviceMethod 候选的通知方法
 * @param aspectInstanceFactory MetadataAwareAspectInstanceFactory 实例,用于获取切面元数据和实例
 * @param declarationOrderInAspect 在切面中的声明顺序
 * @param aspectName 切面名称
 * @return 对应的 Advisor,如果方法没有对应的 Pointcut 则返回 null
 */
@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
                          int declarationOrderInAspect, String aspectName) {
    // 验证切面类是否合法
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

    // 获取 Pointcut
    AspectJExpressionPointcut expressionPointcut = getPointcut(
            candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (expressionPointcut == null) {
        // 如果没有对应的 Pointcut,则返回 null
        return null;
    }

    // 创建并返回 InstantiationModelAwarePointcutAdvisorImpl
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

/**
 * 获取给定方法的 Pointcut。
 *
 * @param candidateAdviceMethod 候选的通知方法
 * @param candidateAspectClass 候选的切面类
 * @return 对应的 AspectJExpressionPointcut,如果方法没有对应的 AspectJ 注解则返回 null
 */
@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 查找方法上的 AspectJ 注解
    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        // 如果方法没有对应的 AspectJ 注解,则返回 null
        return null;
    }

    // 创建 AspectJExpressionPointcut
    AspectJExpressionPointcut ajexp =
            new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
    // 设置 Pointcut 表达式
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    if (this.beanFactory != null) {
        // 如果 beanFactory 不为空,则设置 beanFactory
        ajexp.setBeanFactory(this.beanFactory);
    }
    // 返回 AspectJExpressionPointcut
    return ajexp;
}

这些源码的关键总结如下:

一. 获取所有 Advisor (getAdvisors 方法)
1. 提取切面元数据:

  • 获取切面类和切面名称,这些信息是从 MetadataAwareAspectInstanceFactory 中提取的。

2. 装饰工厂:

  • LazySingletonAspectInstanceFactoryDecorator 包装原始工厂,这样可以确保切面实例只会被创建一次(懒加载)。

3. 处理通知方法:

  • 遍历切面类中的所有方法,检查每个方法是否是通知方法(如 @Before@After 等)。
  • 对每个通知方法调用 getAdvisor 方法,创建相应的 Advisor 对象,并将其添加到列表中。
  • getAdvisor 方法通过解析方法上的注解来创建 AspectJExpressionPointcut,然后将其与通知方法一起封装成 InstantiationModelAwarePointcutAdvisorImpl 对象。

4. 处理懒加载的切面:

  • 如果切面类是懒加载的,则添加一个 SyntheticInstantiationAdvisorAdvisor 列表的开头,以确保在真正使用切面之前完成实例化。

5. 处理引入增强:

  • 遍历切面类中的所有字段,检查是否有引入增强(@DeclareParents 注解)。
  • 对每个引入增强字段,调用 getDeclareParentsAdvisor 方法,创建相应的 Advisor 对象,并将其添加到列表中。

6. 返回所有 Advisor:

  • 最终,返回包含所有 Advisor 对象的列表,这些 Advisor 将用于在目标对象上应用切面逻辑。

二. 获取单个 Advisor (getAdvisor 方法)

1. 验证切面类:

  • 确保传入的切面类是有效的切面。

2. 获取 Pointcut:

  • 通过解析方法上的 AspectJ 注解(如 @Before@After),创建 AspectJExpressionPointcut 对象。
  • 如果方法没有相应的注解,则返回 null

3. 创建 Advisor:

  • AspectJExpressionPointcut 和通知方法封装成 InstantiationModelAwarePointcutAdvisorImpl 对象。
  • 返回创建的 Advisor 对象。

三. 获取 Pointcut (getPointcut 方法)

1. 查找方法上的 AspectJ 注解:

  • 检查给定的方法是否有 AspectJ 注解(如 @Before@After)。

2. 创建 Pointcut:

  • 如果找到了 AspectJ 注解,则创建 AspectJExpressionPointcut 对象,并设置相应的切入点表达式。
  • 如果存在 beanFactory,则设置 beanFactory

3. 返回 Pointcut:

  • 返回配置好的 AspectJExpressionPointcut 对象,如果没有找到注解,则返回 null

简要总结

  • getAdvisors 方法从切面工厂中提取元数据,装饰工厂以实现懒加载,处理切面类中的通知方法和引入增强,最终返回所有的 Advisor
  • getAdvisor 方法根据通知方法创建相应的 Advisor,并将其与 Pointcut 结合起来。
  • getPointcut 方法通过解析方法上的注解来创建并配置 Pointcut,用于在指定的连接点上应用通知。

时序图如下:

在这里插入图片描述

关键步骤说明

  • 获取所有 Advisor:由 Spring 容器发起,调用 ReflectiveAspectJAdvisorFactorygetAdvisors 方法。
  • 获取切面类和切面名称:从 MetadataAwareAspectInstanceFactory 中提取切面类和切面名称。
  • 验证切面类是否合法:确保切面类是有效的。
  • 包装工厂以实现懒加载:使用 LazySingletonAspectInstanceFactoryDecorator 包装工厂。
  • 遍历切面类的方法:逐个检查每个方法,是否包含 AspectJ 注解。
    • 方法有 AspectJ 注解:创建 AspectJExpressionPointcut,并与方法封装成 InstantiationModelAwarePointcutAdvisorImpl
    • 方法无 AspectJ 注解:跳过该方法。
  • 切面是懒加载的:添加 SyntheticInstantiationAdvisorAdvisor 列表开头。
  • 遍历切面类的字段:检查每个字段是否包含 DeclareParents 注解。
    • 字段有 DeclareParents 注解:创建相应的 Advisor
    • 字段无 DeclareParents 注解:跳过该字段。
  • 返回所有 Advisor:将包含所有 Advisor 的列表返回给 Spring 容器。

3. TargetSource 的构建和作用

  在 Spring AOP 中,TargetSource 是一个重要的概念,它用于描述 AOP 代理的目标对象。目标对象是被代理对象,即实际执行业务逻辑的对象。TargetSource 定义了如何获取这个目标对象以及它的生命周期管理。

3.1 TargetSource 的作用

  1. 获取目标对象:TargetSource 提供了获取目标对象的机制,代理对象在执行方法调用时,通过 TargetSource 获取实际的目标对象。

  2. 目标对象的生命周期管理:TargetSource 负责目标对象的生命周期管理,比如创建、缓存、销毁等。

  3. 支持多种代理模式:通过不同的 TargetSource 实现,可以支持不同的代理模式,比如单例、多例、懒加载等。

常见的 TargetSource 实现

1. SingletonTargetSource:单例目标源,目标对象是单例的,每次获取的都是同一个实例。

2. PrototypeTargetSource:原型目标源,每次获取目标对象时,都会创建一个新的实例。

3. LazyInitTargetSource:懒加载目标源,目标对象只有在第一次使用时才会被创建。

3.2 TargetSource 的构建

核心类与方法

  • AdvisedSupport:这个类持有 TargetSource 和其他 AOP 配置。
  • ProxyFactory:这个类用于创建 AOP 代理。
  • AopProxy:具体的代理实现类(如 JdkDynamicAopProxyCglibAopProxy)。

AdvisedSupportSpring AOP 框架中一个核心的配置类,它持有 TargetSource,以及其他关于 AOP 的配置。下面是一些关键代码片段:

public class AdvisedSupport extends ProxyConfig implements Advised {
    // 持有的 TargetSource 实例
    private TargetSource targetSource = EmptyTargetSource.INSTANCE;

    // 设置 TargetSource
    public void setTargetSource(TargetSource targetSource) {
        this.targetSource = (targetSource != null ? targetSource : EmptyTargetSource.INSTANCE);
    }

    // 获取 TargetSource
    public TargetSource getTargetSource() {
        return this.targetSource;
    }

    // 其他相关配置和方法
}

ProxyFactory 类用于创建 AOP 代理。它继承自 AdvisedSupport,所以可以直接访问 TargetSource

相关的类用一张图说明:

在这里插入图片描述

代码提出来分析

/**
 * 根据此工厂中的设置创建一个新的代理。
 * <p>此方法可以重复调用。如果我们添加或删除接口,效果会有所不同。
 * 可以添加和删除拦截器。
 * <p>使用默认的类加载器:通常是线程上下文类加载器(如果代理创建需要)。
 * @return 代理对象
 */
public Object getProxy() {
    // 调用 createAopProxy() 方法创建 AOP 代理,然后调用其 getProxy() 方法返回代理对象
    return createAopProxy().getProxy();
}

/**
 * 创建一个新的 AOP 代理。子类应调用此方法来获取新的 AOP 代理,而不应直接使用 this 作为参数创建 AOP 代理。
 * @return 新的 AOP 代理
 */
protected final synchronized AopProxy createAopProxy() {
    // 如果代理还没有激活,则激活代理
    if (!this.active) {
        activate();
    }
    // 调用 AopProxyFactory 的 createAopProxy 方法创建新的 AOP 代理
    return getAopProxyFactory().createAopProxy(this);
}

/**
 * 根据 AdvisedSupport 配置创建一个 AOP 代理。此方法决定使用哪种代理实现(JDK 动态代理或 CGLIB 代理)。
 **/
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 检查是否在原生镜像中执行,以及是否优化、代理目标类或没有用户提供的代理接口
    if (!NativeDetector.inNativeImage() &&
            (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
        // 获取目标类
        Class<?> targetClass = config.getTargetClass();
        // 如果目标类为空,抛出 AopConfigException 异常
        if (targetClass == null) {
            throw new AopConfigException("TargetSource 无法确定目标类: " +
                    "创建代理需要一个接口或目标对象。");
        }
        // 如果目标类是接口或代理类或 lambda 表达式,使用 JdkDynamicAopProxy
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // 否则,使用 ObjenesisCglibAopProxy
        return new ObjenesisCglibAopProxy(config);
    } else {
        // 默认使用 JdkDynamicAopProxy
        return new JdkDynamicAopProxy(config);
    }
}

  getProxy 方法是调用 createAopProxy() 方法创建 AOP 代理对象,然后调用创建的 AOP 代理对象的 getProxy() 方法返回实际的代理对象。

  createAopProxy 方法首先检查代理是否已激活,如果未激活则调用 activate() 方法激活代理。调用 getAopProxyFactory().createAopProxy(this) 方法,通过 AopProxyFactory 创建新的 AOP 代理对象。

createAopProxy 方法过程如下:

  • 检查是否在原生镜像中执行,以及是否需要优化、代理目标类或没有用户提供的代理接口。
  • 获取目标类,如果目标类为空,抛出 AopConfigException 异常。
  • 如果目标类是接口、代理类或 lambda 表达式,使用 JdkDynamicAopProxy 创建代理。
  • 否则,使用 ObjenesisCglibAopProxy 创建代理。
  • 如果不满足上述条件,默认使用 JdkDynamicAopProxy

下面时序图是说明 TargetSourceSpring AOP 中的构建和使用过程:

在这里插入图片描述
关键步骤说明

1. 创建并配置 AdvisedSupport 实例:

  • Spring 容器创建一个 AdvisedSupport 实例,并根据配置设置其属性,包括 TargetSource

2. 设置 TargetSource:

  • AdvisedSupport 实例中设置 TargetSource,它定义了如何获取目标对象。

3. 创建 AopProxy:

  • Spring 容器调用 AopProxyFactory 创建 AopProxy 实例,传递 AdvisedSupport 作为配置。

4. 获取配置信息:

  • AopProxyFactoryAdvisedSupport 获取创建 AopProxy 所需的配置信息。

5. 根据配置创建 AopProxy 实例:

  • AopProxyFactory 根据 AdvisedSupport 配置决定使用哪种 AopProxy 实现(如 JdkDynamicAopProxyCglibAopProxy),并创建 AopProxy 实例。

6. 获取 TargetSource:

  • AopProxyAdvisedSupport 中获取 TargetSource 实例。

7. 获取目标对象:

  • AopProxy 调用 TargetSourcegetTarget 方法获取实际的目标对象实例。

8. 创建代理对象:

  • AopProxy 使用目标对象创建代理对象。

9. 返回代理对象:

  • AopProxy 将创建的代理对象返回给 Spring 容器。

10. 返回代理对象给调用者:

  • Spring 容器将代理对象返回给调用者。

欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

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

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

相关文章

java实现,PDF转换为TIF

目录 ■JDK版本 ■java代码・实现效果 ■POM引用 ■之前TIF相关的问题&#xff08;两张TIF合并&#xff09; ■问题 ■问题1&#xff1a;无法生成TIF&#xff0c;已解决 ■问题2&#xff1a;生成的TIF过大&#xff0c;待解决 ■相关工具 SVF foxit ■相关知识 Imag…

Fastjson1.2.24(CVE-2017-18349)分析

前言 Fastjson在1.2.24版本之前存在两条利用链&#xff0c;分别是 JNDI com.sun.rowset.JdbcRowSetImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 我们本次也是对这两条链进行分析和过程复现 在fastjson漏洞中&#xff0c;我们往往是寻找一个类&#xff0…

【苍穹外卖】Day 5 Redis、店铺营业状态接口

1 基本介绍 Redis是一个基于 内存 的 key-value 结构数据库 基于内存存储&#xff0c;读写性能高适合存储热点数据(热点商品、资讯、新闻)企业应用广泛 运行 在cmd下 redis-server.exe redis.windows.conf 启动状态下&#xff0c;再 redis-cli.exe 测试&#xff1a; 也可以…

Win32远线程注入

远线程注入 远线程(RemoteThread)注入是指一个进程在另一个进程中创建线程的技术&#xff0c;这是一种很经典的DLL注入技术。 虽然比较古老&#xff0c;但是很实用。通过远线程注入&#xff0c;再配合api函数的hook技术&#xff0c;可以实现很多有意思的功能。 实现远线程注入…

自己封装栈和队列

队列 #include <iostream>using namespace std; class queue { private:int *data;int size;int front;int tail;public://无参构造queue():size(20){datanew int [size];front0;tail0;}//有参构造queue(int s){datanew int [size];sizes;front0;tail0;}~queue(){delete …

MySQL三大日志详解

binlog相关 bin log是什么?作用是什么呢? 答: bin log实际上是一个物理日志,当我们对某个数据页进行修改操作时我们就会将这个操作写到bin log中,当我们数据库需要进行主备、主从复制等操作时,都可以基于bin log保证数据一致性。 那bin log缓冲区了解嘛? 答: 如下图…

硬件产品经理进阶:产品层次划分的3个方法

目录 1、内容简介 2、产品三层次概念 3、产品四层次概念 4、产品五层次概念 作者简介 1、内容简介 产品本身指的是能够满足需求和欲望的一种媒介物。 可以是实体、也可以是虚拟的服务。 在产品竞争白热化的今天&#xff0c; 如果只是考虑把产品做出来、 仅仅在实际产…

【微信小程序入门】3、微信小程序开发基础及微信开发者工具的使用

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

vue2———组件

一个简单的组件 组件进行注册并使用 结果&#xff1a; 在进行对组件的学习时遇见一些问题&#xff1a; 1、组件的命名 解决方法&#xff1a; 组件的命名 Vue.js 组件的命名遵循一些最佳实践&#xff0c;这些实践有助于保持代码的清晰和一致性。 多单词命名&#xff1a;Vue 官…

【网络安全】Collabora在线存储型XSS(CVE-2024-29182)+代码审计

未经许可,不得转载。 文章目录 前言正文代码审计前言 Collabora 是一家专注于开源软件的公司,主要提供与文档协作、办公套件和企业解决方案相关的服务。 Collabora 提供了 Collabora Online,这是一个基于 Web 的在线办公套件,允许用户在浏览器中实时编辑文档。这种服务特…

Two to One——C语言提高题【7 kyu】

一、原题 链接&#xff1a;Training on Two to One | Codewars Take 2 strings s1 and s2 including only letters from a to z. Return a new sorted string (alphabetical ascending), the longest possible, containing distinct letters - each taken only once - coming…

继承:复杂的菱形继承与虚继承

目录 前言 复杂的菱形继承及菱形虚拟继承 继承方式 virtual关键字 虚拟继承的原理 原理&#xff1a; 额外消耗&#xff1a; 构造顺序为什么是ABCD 不允许使用间接非虚拟基类原理 假设只有A B 为什么virtual加在B C中而不是D中&#xff1f; 如何实现一个不能被继承的类…

AtCoder ABC 359 F 题解

本题要看出性质并进行验证&#xff0c;程序难度低。&#xff08;官方 Editorial 似乎没有写证明过程&#xff1f;难道是过于显而易见了吗…&#xff09; 题意 给你一个数组 a a a&#xff0c;对于一棵 n n n 个节点的树 T T T&#xff0c; d i d_i di​ 为每个节点的度&am…

基于Java+SpringBoot+Vue的校园社团信息管理

基于JavaSpringBootVue的校园社团信息管理 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;&公&粽&号 查找《智能编…

C++ 栈和队列的简单封装(9.3)

1.栈的封装 代码 #include <iostream>using namespace std;typedef int datatype; class Stack { private:datatype *data;int max_size; //栈的大小int the_top; //栈顶 public:Stack(){data new int[50];max_size 50;the_top -1;}Stack(int a){data n…

一个好用的Maven依赖冲突解决插件:Maven Helper

在项目开发&#xff0c;或项目Maven需要新增依赖、项目依赖组件升级时&#xff0c;经常会出现添加后&#xff0c;因为各个模块中有相同的依赖、不同的版本而导致依赖冲突&#xff0c;从而导致项目启动不起来&#xff0c;这种冲突非常恶心&#xff0c;因为是传递依赖所以会看不出…

【数据推荐】我国省市县三级的人口受教育状况数据(分年龄\性别\户籍)

人口数据是我们在各项研究中都经常使用的数据。之前我们为大家分享过基于《2020中国人口普查分县资料》整理的全国范围的第七次人口普查人口数据&#xff0c;具体包括如下8个分表&#xff08;均可查看之前的文章获悉详情&#xff09;&#xff1a; 表1&#xff1a;我国省市县三…

大二暑假去龙旗科技(上海)做了两个月软件测试实习生,讲讲我的经历和感受

目录 1.为什么选择软件测试 2.入职&#xff0c;辞职流程以及实习工作内容 3.行业选择和个人感悟 新的学期开始了兄弟们&#xff0c;我也已经断更几个月了&#xff0c;这几个月我并没有摆烂&#xff0c;我选择了备考蓝桥杯&#xff0c;复习期末&#xff0c;暑假出去实习。结果…

SpringDataJPA系列(5)@Query应该怎么用?

SpringDataJPA系列(5)Query应该怎么用&#xff1f; 之前说到过&#xff0c;DMQ查询策略有两种&#xff1a;方法命令和Query注解的方式。为什么需要两种呢&#xff1f;它们分别适用的场景是怎么样的&#xff1f; Query使用 定义一个通过名字查询用户的方法 以下是测试方法&…

将泛型和函数式编程结合,竟然会让代码这么优雅!

但这种方式却太表象了&#xff0c;没有灵魂和深度&#xff0c;过去的那些日子&#xff0c;我感觉自己的编程水平也就限于把重复的代码抽一抽&#xff0c;&#xff08;如下图所示一样&#xff09;&#xff0c;甚至觉得代码优化不就是这样吗&#xff0c;这样的状态一直维持很久。…