(六)Spring源码解析:Spring AOP源码解析

news2024/11/13 4:26:58

〇、AOP概念

Aspect:切面

给业务方法增加到功能,切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

Pointcut:切入点

切入点指声明的一个或多个连接点的集合,通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

Advice:通知、增强

通知表示切面的执行时间,Advice也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

JoinPoint:连接点

连接切面的业务方法,连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

Target:目标对象

目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。

AOP中重要的三个要素:AspectPointcutAdvice

意思是说:在Advice的时间、在Pointcut的位置,执行Aspect。

为了方便大家理解AOP中的相关概念,请见下图所示:

一、动态AOP的使用示例

当我们对某些类有横切性的逻辑时,为了不破坏目标类,我们则可以使用AOP的方式将增强逻辑注入到目标类上。为了更清晰的了解AOP的用法,下面我们通过一个使用案例,实现一下面向切面编程。

首先,我们创建一个普通的业务类MuseAop

public class MuseAop {
    public void goToWork() {
        System.out.println("Muse去上班");
    }
}

其次,创建Advisor,然后对MuseAop的goToWork()方法进行增强操作

@Aspect
public class MuseAspectJ {
    @Pointcut("execution(* *.goToWork(..))")
    public void goToWork() {}

    @Before("goToWork()")
    public void beforeGoToWork() {
        System.out.println("@Before:起床、洗漱、穿衣");
    }

    @After("goToWork()")
    public void afterGoToWork() {
        System.out.println("@After:开始工作了");
    }

    @SneakyThrows
    @Around("goToWork()")
    public Object aroundGoToWork(ProceedingJoinPoint point) {
        System.out.println("@Around-1:听一首摇滚歌曲提提神");
        Object result = point.proceed();
        System.out.println("@Around-2:听一首钢琴乐舒缓情绪");
        return result;
    }
}

然后,在配置文件中添加bean,并且通过配置 <aop:aspectj-autoproxy /> 来开启aop动态代理

<aop:aspectj-autoproxy />
<bean id="museAop" class="com.muse.springbootdemo.entity.aop.MuseAop"/>
<bean class="com.muse.springbootdemo.entity.aop.MuseAspectJ"/>

最后,编写测试类,查看测试结果

二、动态AOP自定义标签

根据上面我们使用AOP的示例,我们可以看到是通过配置<aop:aspectj-autoproxy>来开启动态代理的,因此我们可以将它为AOP源码分析的切入点。请见下图所示,我们在全项目中搜索了aspectj-autoproxy,然后发现注入了新的BeanDefinitionParser实现类—— AspectJAutoProxyBeanDefinitionParser

那么下面,我们来看一下AspectJAutoProxyBeanDefinitionParser类的具体实现,由于AspectJAutoProxyBeanDefinitionParser实现了BeanDefinitionParser接口,而BeanDefinitionParser只有一个方法,即:parse(element, parserContext),所以,我们来看一下AspectJAutoProxyBeanDefinitionParser 类是如何实现parse方法的。

下面,我们先看一下AopNamespaceUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element)方法的具体实现逻辑。

2.1> registerAspectJAnnotationAutoProxyCreatorIfNecessary方法解析

该方法是用于注册或者升级AnnotationAwareAspectJAutoProxyCreator类型的APC,对于AOP的实现,基本都是在AnnotationAwareAspectJAutoProxyCreator类中完成的,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,注册流程如下所示:

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

AUTO_PROXY_CREATOR_BEAN_NAME(“org.springframework.aop.config.internalAutoProxyCreator”)的作用是内部管理的自动代理创建器的bean名称。如果名称为“AUTO_PROXY_CREATOR_BEAN_NAME”的apc实例在容器中已经存在,则试图替换为该bean为AnnotationAwareAspectJAutoProxyCreator类型;否则,在容器中创建AnnotationAwareAspectJAutoProxyCreator类型的APC实例。

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
    /** 步骤1:如果容器中已经存在apc实例,则试图替换为AnnotationAwareAspectJAutoProxyCreator类型 */
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) apcDefinition.setBeanClassName(cls.getName());
        }
        return null;
    }
    /** 步骤2:如果不存在,则向容器中创建AnnotationAwareAspectJAutoProxyCreator类型的apc实例对象 */
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

2.1.1> 关于APC优先级别的补充说明

APC的抽象类AbstractAdvisorAutoProxyCreator默认有4个实现类,如下所示:

我们可以通过AopConfigUtils.findPriorityForClass(...)方法来获得当前APC实现类的优先级别,数字越大,优先级别越高这个优先级,其实就是ArrayList中存储的APC实现类的index序号。源码请见下图所示:

/** 默认初始化3个APC(AutoProxyCreator)实现类 */
private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);
static {
    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class); // 第0级别
    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class); // 第1级别
    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class); // 第2级别
}

/** 通过Class获得优先级别 */
private static int findPriorityForClass(Class<?> clazz) {
    return APC_PRIORITY_LIST.indexOf(clazz);
}

/** 通过className获得优先级别 */
private static int findPriorityForClass(@Nullable String className) {
    for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {
        Class<?> clazz = APC_PRIORITY_LIST.get(i);
        if (clazz.getName().equals(className)) return i; 
    }
}

第0级别】InfrastructureAdvisorAutoProxyCreator
第1级别】AspectJAwareAdvisorAutoProxyCreator
第2级别】AnnotationAwareAspectJAutoProxyCreator

2.2> useClassProxyingIfNecessary方法解析

步骤1:获得属性PROXY_TARGET_CLASS_ATTRIBUTE(“proxy-target-class”)

如果proxy-target-class等于true,才会执行AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry)方法;

步骤2:属性EXPOSE_PROXY_ATTRIBUTE(“expose-proxy”)

如果expose-proxy等于true,才会执行AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry)方法;

useClassProxyingIfNecessary()方法源码如下所示:

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement {
    if (sourceElement != null) {
        // 设置参数proxy-target-class
        boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        
        // 设置参数expose-proxy
        boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
    }
}

这两个方法逻辑基本一致的,就是首先判断是否存在名字为AUTO_PROXY_CREATOR_BEAN_NAME的BeanDefinition实例对象,如果存在的话,将该对象的属性proxyTargetClass或者属性exposeProxy赋值为true即可。

/** 将definition的proxyTargetClass属性设置为true */
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
    }
}
/** 将definition的exposeProxy属性设置为true */
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
    }
}

proxyTargetClass属性的作用是什么?

<aop:config proxy-target-class="true"> :配置为强制使用CGLIB代理
<aop:aspectj-autoproxy proxy-target-class="true"/> :配置为CGLIB代理+@AspectJ自动代理支持

exposeProxy属性的作用是什么?

<aop:aspectj-autoproxy export-proxy="true"/> :支持通过AopContext.currentProxy()来暴露当前代理类。

exposeProxy场景举例:

public interface AService {
    public void a();
    public void b();
}

@Service
public class AserviceImpl implements AService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        this.b(); // 由于this指向target对象,所以不会执行b事务切面
        ((AService) AopContext.currentProxy()).b(); // 暴露了当前的代理类,所以可以执行b事务切面
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {}
}

三、创建AOP代理

通过上面的介绍,我们可以看到AOP在极力的创建AnnotationAwareAspectJAutoProxyCreator对象作为APC的bean实例:

既然是这样的,那我们就来看一下AnnotationAwareAspectJAutoProxyCreator类的继承结构,请见下图所示:

从上图中我们可以看到它实现了BeanPostProcessor接口,那么这个接口里有我们熟悉的postProcessAfterInitialization(...)方法,该方法是由AbstractAutoProxyCreator类实现的,源码请见如下所示:

下面真正执行对bean增强的方法就是wrapIfNecessary(...)了,在这里有如下几种情况是不需要增加的:

情况1targetSourcedBeans中保存的是已经处理过的bean,如果在其中,则不需要增强;
情况2advisedBeans中value值为false表示不需要增强;
情况3基础设施类(实现AdvicesPointcutAdvisorsAopInfrastructureBeans这四个接口的类),则不需要增强;
情况4】如果是Original实例(以beanClass.getName()开头,并且以".ORIGINAL"结尾),则不需要增强;

wrapIfNecessary(...)方法中,主要有两个重要的方法:getAdvicesAndAdvisorsForBean(...) 和 createProxy(...) ,后续我们会针对这两个方法进行解析。

根据上面的描述,我们可以知道getAdvicesAndAdvisorsForBean(...)方法就是用来获得增强器的方法了,这里通过调用findEligibleAdvisors(beanClass, beanName)方法来获得增强器列表,并进行结果返回;如果没有获得增强器,则返回DO_NOT_PROXY(其值为null),其源码如下所示:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
    /** 寻找增强列表 */
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) return DO_NOT_PROXY;
    return advisors.toArray();
}

Eligible的英文翻译是“符合条件的”,那么findEligibleAdvisors(...)方法的主要作用就是——找到符合条件的增强器,具体操作有如下两步:

步骤1】通过调用findCandidateAdvisors(...)方法,获取所有的增强;
步骤2】通过调用findAdvisorsThatCanApply(...)方法,寻找所有增强中适用于bean的增强并进行应用;

相关源码,请见如下所示:

3.1> findCandidateAdvisors()获取所有增强器

findCandidateAdvisors()方法中,我们可以获得所有增强器,此处一共执行了两个步骤来获得所有增强器:

步骤1】获得xml中配置的AOP声明;
步骤2】获得带有aspectj注释的AOP声明;

如下就是findCandidateAdvisors()方法的相关源码:

3.1.1> findCandidateAdvisors() 获得xml中配置的AOP声明

protected List<Advisor> findCandidateAdvisors() {
    return this.advisorRetrievalHelper.findAdvisorBeans();
}

findAdvisorBeans()方法中我们可以看到,如下逻辑:

步骤1】首先,尝试从缓存cachedAdvisorBeanNames)中获得Advisor类型的bean名称列表。
步骤2】如果没有获得到,则试图去IOC容器中获得所有Advisor类型的bean名称列表。
步骤3】如果都没有获得Advisor类型的bean名称列表,则直接返回空集合。
步骤4】如果不为空,则通过beanFactory.getBean(name, Advisor.class)来获得Advisor实例集合,并进行返回。

如下就是findAdvisorBeans()方法的相关源码:

public List<Advisor> findAdvisorBeans() {
    //【步骤1】获得所有被缓存的Advisor的bean名称列表
    String[] advisorNames = this.cachedAdvisorBeanNames;

    //【步骤2】如果缓存中没有,那么我们就从BeanFactoryUtils中获得Advisor的bean名称列表,然后作为已缓存的bean名称列表
    if (advisorNames == null) {
        //【官方注释】这里不要初始化FactoryBeans:我们需要保留所有未初始化的常规bean,以便让自动代理创建器应用于它们!
        // 返回容器中所有Advisor类型的bean名称列表
        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);
        this.cachedAdvisorBeanNames = advisorNames;
    }

    //【步骤3】如果缓存也没有并且从从BeanFactoryUtils中也没获得到,则直接返回空集合
    if (advisorNames.length == 0) return new ArrayList<>();

    //【步骤4】从IOC容器中获得Advisor对象实例集合,并返回
    List<Advisor> advisors = new ArrayList<>();
    for (String name : advisorNames) {
        if (isEligibleBean(name)) {
            if (this.beanFactory.isCurrentlyInCreation(name)) 
                if (logger.isTraceEnabled()) logger.trace("Skipping currently created advisor '" + name + "'");
            else {
                try {
                    advisors.add(this.beanFactory.getBean(name, Advisor.class));
                } catch (BeanCreationException ex) { ... ... }
            }
        }
    }
    return advisors;
}

3.1.2> buildAspectJAdvisors() 获得带有aspectj注释的AOP声明

buildAspectJAdvisors()方法中,主要逻辑有四个步骤:

步骤1】获得Aspect的beanName列表;
步骤2】通过beanName来获得MetadataAwareAspectInstanceFactory实例,具体如下所示:如果per-clauses(aspect实例化模式) 类型等于SINGLETON,则创建BeanFactoryAspectInstanceFactory类型的factory;否则,则创建PrototypeAspectInstanceFactory类型的factory
步骤3】通过调用advisorFactory.getAdvisors(factory)来获得Advisor列表;
步骤4】维护advisorsCache缓存或aspectFactoryCache缓存;如果beanName是单例的,则将beanNameAdvisor列表维护到advisorsCache缓存中;否则,将beanNamefactory维护到aspectFactoryCache缓存中;

buildAspectJAdvisors()方法中,源码及注释如下所示:

public List<Advisor> buildAspectJAdvisors() {
    // 获得已经解析过的Aspect的beanName列表
    List<String> aspectNames = this.aspectBeanNames;
    
    // 步骤1:如果aspectNames为空,则试图从IOC中解析出Aspect的beanName列表
    if (aspectNames == null) {
        synchronized (this) { // 加锁
            aspectNames = this.aspectBeanNames; // double check
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList<>();
                aspectNames = new ArrayList<>();
                // 获得IOC容器中所有的beanName
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                for (String beanName : beanNames) {
                    if (!isEligibleBean(beanName)) continue; // isEligibleBean方法默认返回true
                    // 官方注释:我们必须小心不要急切地实例化bean,因为在这种情况下,它们将被Spring容器缓存,但不会被织入
                    Class<?> beanType = this.beanFactory.getType(beanName, false);
                    if (beanType == null) continue;
                    
                    // 一个类如果包含@Aspect注解并且不是被ajc编译的类,则返回true
                    if (this.advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            // 单例则创建BeanFactoryAspectInstanceFactory类型的factory
                            MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                            if (this.beanFactory.isSingleton(beanName)) 
                                this.advisorsCache.put(beanName, classAdvisors); // 缓存Advisor
                            else 
                                this.aspectFactoryCache.put(beanName, factory); // 缓存MetadataAwareAspectInstanceFactory
                            advisors.addAll(classAdvisors);
                        } else {
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException("Bean with name '" + beanName +
                                        "' is a singleton, but aspect instantiation model is not singleton");
                            }
                            // 非单例则创建PrototypeAspectInstanceFactory类型的factory
                            MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                            this.aspectFactoryCache.put(beanName, factory); // 缓存MetadataAwareAspectInstanceFactory
                            advisors.addAll(this.advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                this.aspectBeanNames = aspectNames;
                return advisors; // 将解析好的Advisor列表执行返回操作
            }
        }
    }
    if (aspectNames.isEmpty()) return Collections.emptyList();

    // 步骤2:如果没有“经历过”步骤1,则再次处解析Advisor列表
    List<Advisor> advisors = new ArrayList<>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) // 情况1:如果在advisorsCache缓存中存在,则直接返回Advisor列表
            advisors.addAll(cachedAdvisors); 
        else { // 情况2:如果在aspectFactoryCache缓存中存在,则需要调用factory的getAdvisors方法来获得Advisor列表
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory)); 
        }
    }
    return advisors;
}

在上面的源码中,我们可以发现,其中通过advisorFactory.getAdvisors(factory) 来获得Advisor集合是非常核心的代码,因为只有它才能帮助我们获得Advisor列表,部分截取代码如下所示:

advisorFactory.getAdvisors(factory)方法的源码如下所示:

3.1.3> getAdvisor(...) 获得普通增强器

getAdvisor(...)方法的源码如下所示:

a> 步骤1:获得切点表达式的相关信息

下面我们来看一下步骤1中的获得切点表达式的相关信息getPointcut(...)方法源码逻辑:

方法上的AspectJ相关注解(AspectJAnnotation),查找注解的顺序按照:@Pointcut——>@Around——>@Before——>@After——>@AfterReturning——>@AfterThrowing ,如果找到了某个注解,则直接返回AspectJAnnotation实例对象,不需要继续寻找了。

b> 步骤2:根据切点信息生成增强

我们可以看到,在步骤2中,是通过创建一个InstantiationModelAwarePointcutAdvisorImpl实例对象来生成切点的增强的,源码如下所示:

getAdvice方法中,我们根据上面解析出的方法上面使用的AspectJ注解来生成相应的AbstractAspectJAdvice,代码如下所示:

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
                        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder,
                        String aspectName) {
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); // 获得切面类
    validate(candidateAspectClass);
    // 获得切面上的AspectJ相关注解
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); 
    if (aspectJAnnotation == null) return null; // 如果没有AspectJ相关注解,那么直接返回null即可
    
    // 如果类上有@Aspect注解 并且 该类是被AspectJ编译的,则直接抛出异常
    if (!isAspect(candidateAspectClass))
        throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" +
                candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");

    // 根据不同的AspectJ相关注解,生成对应不同的springAdvice的实例对象
    AbstractAspectJAdvice springAdvice;
    switch (aspectJAnnotation.getAnnotationType()) {
        case AtPointcut: // @Pointcut
            return null;
        case AtAround: // @Around
            springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtBefore: // @Before
            springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfter: // @After
            springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfterReturning: // @AfterReturning
            springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning()))
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            break;
        case AtAfterThrowing: // @AfterThrowing
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing()))
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            break;
        default:
            throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
    }
    // 为springAdvice实例对象赋值
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) springAdvice.setArgumentNamesFromStringArray(argNames);
    springAdvice.calculateArgumentBindings();
    return springAdvice;
}

3.1.4> new SyntheticInstantiationAdvisor(...) 创建同步实例化增强器

如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器。同步实例化增强器SyntheticlnstantiationAdvisor源码如下所示:

3.1.5> getDeclareParentsAdvisor(field) 获得@DeclareParents配置的增强器

DeclareParents主要用于引介增强的注解形式的实现,如果属性上使用了@DeclareParents注解,那么我们就来创建DeclareParentsAdvisor类型的增强器,其中关于@DeclareParents注解的使用场景,请参照3.1.6部分即可,源码如下所示:

private Advisor getDeclareParentsAdvisor(Field introductionField) {
    // 获得属性上使用了@DeclareParents注解的注解类
    DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
    if (declareParents == null) return null;
    if (DeclareParents.class == declareParents.defaultImpl()) throw new IllegalStateException(...);
    // 创建DeclareParentsAdvisor类型的增强器
    return new DeclareParentsAdvisor(introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
}

3.1.6> @DeclareParents注解的使用

创建接口IPay和实现类OnlinePay

public interface IPay {
    void pay();
}
@Service
public class OnlinePay implements IPay {
    @Override
    public void pay() {
        System.out.println("-------OnlinePay--------");
    }
}

创建接口IPayPlugin和实现类AlipayPlugin

public interface IPayPlugin {
    void payPlugin();
}
@Service
public class AlipayPlugin implements IPayPlugin {
    @Override
    public void payPlugin() {
        System.out.println("-------Alipay--------");
    }
}

使用@DeclareParents注解配置切面。该注解的作用是:可以在代理目标类上增加新的行为(即:新增新的方法)。

@Aspect
@Component
public class PayAspectJ {
    @DeclareParents(value = "com.muse.springbootdemo.entity.aop.IPay+",defaultImpl = AlipayPlugin.class)
    public IPayPlugin alipayPlugin;
}

创建配置类MuseConfig,开启AspectJ的自动代理,然后就会为使用@Aspect注解的bean创建一个代理类。

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class MuseConfig {
}

进行测试,我们发现从IOC中获得的bean实现了IPay和IPayPlugin这两个接口,并且会在后台输出“-------OnlinePay--------”和“-------Alipay-------

3.2> findAdvisorsThatCanApply(...)寻找匹配的增强器

在3.1中,我们已经分析完获取所有增强器的方法findCandidateAdvisors(),那么本节我们将在获取的所有增强(candidateAdvisors)基础上,再去寻找匹配的增强器,即:findAdvisorsThatCanApply(...)方法,相关源码如下图所示:

findAdvisorsThatCanApply(...)方法中,其主要功能是获得所有增强器candidateAdvisors中,适用于当前clazz的增强器列表。而由于针对引介增强普通增强的处理是不同的, 所以采用分开处理的方式,请见下图所示:

那么,什么是引介增强呢? 引介增强是一种特殊的增强,其它的增强是方法级别的增强,即只能在方法前或方法后添加增强。而引介增强则不是添加到方法上的增强, 而是添加到类级别的增强,即:可以为目标类动态实现某个接口,或者动态添加某些方法。具体实现请见下图所示:

那么,在上面的findAdvisorsThatCanApply(...)方法源码中,我们可以发现,canApply(...)方法是其中很重要的判断方法,那么它内部主要做了什么操作呢?在其方法内部,依然根据引介增强普通增强两种增强形式分别进行的判断,其中,如果是引介增强的话,则判断该增强是否可以应用在targetClass上,如果可以则返回true,否则返回false。那么,如果是普通增强,则需要再调用canApply(...)方法继续进行逻辑判断,相关源码请见下图所示:

canApply(...)方法中,主要是逻辑是获得 targetClass类(非代理类) 及 targetClass类的相关所有接口 中的所有方法去匹配,是否满足对targetClass类的增强,如果找到了,则返回false;如果找不到,则返回true;相关源码,请见下图所示:

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    // 如果Pointcut不能应用于targetClass类上,则直接返回false
    if (!pc.getClassFilter().matches(targetClass)) return false;
    
    MethodMatcher methodMatcher = pc.getMethodMatcher();
    if (methodMatcher == MethodMatcher.TRUE) return true;
    
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) 
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    
    Set<Class<?>> classes = new LinkedHashSet<>();
    if (!Proxy.isProxyClass(targetClass)) classes.add(ClassUtils.getUserClass(targetClass)); // 只添加非代理类
    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); // 添加targetClass的所有接口类
    
    // 遍历所有相关类的所有方法,只要有与targetClass匹配的方法,则返回ture
    for (Class<?> clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }
    return false;
}

3.3> createProxy(...) 创建AOP代理

在3.1和3.2章节中,我们介绍了如何获取bean所对应的Advisor增强器,那么,下面我们就该开始利用这些增强器去配合创建代理对象了,这部分工作由createProxy()方法负责,源码如下所示:

/** 为beanClass创建AOP代理 */
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, 
                             TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory)
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, 
                                         beanName, beanClass);
    
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this); // 将当前对象中的信息复制到proxyFactory实例中

    // 调用proxyFactory.setProxyTargetClass(...)用于设置是否应该使用targetClass而不是它的接口代理 
    // 调用proxyFactory.addInterface(...)用于添加代理接口
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) proxyFactory.setProxyTargetClass(true);
        else evaluateProxyInterfaces(beanClass, proxyFactory);
    }

    /** 3.3.1> 获得所有增强器 */
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors); // 添加增强器集合
    proxyFactory.setTargetSource(targetSource); // 设置被代理的类
    customizeProxyFactory(proxyFactory); // 定制代理(空方法)
    proxyFactory.setFrozen(this.freezeProxy); // 默认false,表示代理被配置后,就不允许修改它的配置了
    if (advisorsPreFiltered()) proxyFactory.setPreFiltered(true);
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader())
        classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();

    /** 3.3.2> 获得代理对象 */
    return proxyFactory.getProxy(classLoader);
}

从上面的源码我们可以整理出createProxy(...)方法的操作流程:
步骤1】创建ProxyFactory实例对象,后续会对其各个参数进行初始化赋值,为最后调用proxyFactory的getProxy(...)方法做准备;
步骤2】将当前对象(this)中的部分信息复制到proxyFactory实例中;
步骤3】调用proxyFactory.setProxyTargetClass(...)用于设置是否应该使用targetClass而不是它的接口代理
步骤4】调用proxyFactory.addInterface(...)用于添加代理接口
步骤5】获得所有增强器Advisor并添加到proxyFactory中;
步骤6】向proxyFactory中设置被代理的类
步骤7】可以对proxyFactory进行定制化操作,默认是空方法;
步骤8】通过调用proxyFactory的setFrozen(...)方法,来控制代理工厂被配置之后,是否还允许修改通知
步骤9】调用proxyFactory的getProxy(...)方法获得代理对象

3.3.1> buildAdvisors(...)获得所有增强器

buildAdvisors(...)方法中,我们可以看到大致做了两个步骤:

步骤1】获得所有拦截器(普通的+指定的);
步骤2】通过这些拦截器生成Advisor增强集合,

buildAdvisors(...)方法中的源码及注释如下所示:

收集拦截器的代码并不复杂,那么下面我们再来看wrap(...)方法是如何通过拦截器生成Advisor增强的,源码及注释请见下图所示:

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    // 如果待封装的adviceObject本来就是Advisor类型,则直接返回即可
    if (adviceObject instanceof Advisor) return (Advisor) adviceObject; 
    // 如果既不是Advisor类型也不是Advice类型,则直接抛出异常,无法执行包装操作
    if (!(adviceObject instanceof Advice)) throw new UnknownAdviceTypeException(adviceObject);

    Advice advice = (Advice) adviceObject;
    // 如果adviceObject是MethodInterceptor类型,则包装成DefaultPointcutAdvisor实例对象
    if (advice instanceof MethodInterceptor) return new DefaultPointcutAdvisor(advice);
    // 遍历适配器列表adapters,如果也支持advice,则包装成DefaultPointcutAdvisor实例对象
    for (AdvisorAdapter adapter : this.adapters) 
        if (adapter.supportsAdvice(advice)) return new DefaultPointcutAdvisor(advice);
        
    throw new UnknownAdviceTypeException(advice);
}

3.3.2>proxyFactory.getProxy(...) 获得代理对象

通过上面的操作,我们已经获得了增强Advisor列表了,并且也做好了对proxyFactory的赋值准备操作,下面就该到了获得代理对象的步骤了,具体逻辑代码在getProxy(...)方法中,源码如下所示:

a> createAopProxy() 创建AOP代理

创建aop代理的时候,首先激活所有的Listener,然后再去创建AOP代理,这部分代码很少,很好理解,请见如下所示:

在上面代码中,我们需要再继续分析红框内的createAopProxy(this)方法,

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!NativeDetector.inNativeImage() &&
            (config.isOptimize() || // 默认false
             config.isProxyTargetClass() || // 默认false
             hasNoUserSuppliedProxyInterfaces(config))) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) throw new AopConfigException(...);
        
        // 如果是接口或者是代理类,则使用JDK动态代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) 
            return new JdkDynamicAopProxy(config);
        
        // 否则,使用CGlib代理
        return new ObjenesisCglibAopProxy(config);
    }
    else return new JdkDynamicAopProxy(config); // 使用JDK动态代理
}

isOptimize() :用来控制通过CGLIB创建的代理是否使用激进的优化策略。除非完全了解AOP代理如何处理优化,否则不推荐用户使用这个设置。目前这个属性仅用于CGLIB代理,对于JDK动态代理(默认代理)无效。
isProxyTargetClass() :这个属性为true时,目标类本身被代理而不是目标类的接口。如果这个属性值被设为true, CGLIB代理将被创建,设置方式为:<aop:aspectj-autoproxy-proxy-target-class="true"/> 。
hasNoUserSuppliedProxyInterfaces(config) :是否存在代理接口。

通过createAopProxy(config)方法,根据不同情况,会返回不同代理对象,在下面内容中,我们会分别分析不同代理对象的代理流程:

如果采用JDK动态代理,则返回JdkDynamicAopProxy代理对象;
如果采用CGlib代理,则返回ObjenesisCglibAopProxy代理对象;

b> JdkDynamicAopProxy.getProxy(classLoader) 获得代理对象

我们先来看一下JdkDynamicAopProxy类对getProxy(...)方法的实现,我们发现,里面只调用了Proxy.newProxyInstance(...)方法,源码如下所示:

public Object getProxy(@Nullable ClassLoader classLoader) {
    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

自己编写过JDK动态代理的朋友应该对getProxy(...)方法中的内容并不陌生,它的编写结构如下所示:

概括来说就是3点:
第1点】要实现InvocationHandler接口;
第2点】重写invoke方法;
第3点】通过调用Proxy.newProxyInstance(...)获得代理对象;

那么我们再来看JdkDynamicAopProxy类,它也实现了InvocationHandler接口,即:满足了第1点;

那么就剩下第2点,即:重写invoke方法了,我们把目光注视到JdkDynamicAopProxy类的invoke方法,看看它是如何实现的:

将拦截器封装到ReflectiveMethodInvocation类中,并通过调用proceed方法逐一调用拦截器,下面是proceed源码内容:

public Object proceed() throws Throwable {
    // 执行完所有增强后执行切点方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) 
        return invokeJoinpoint();

    // 获取下一个要执行的拦截器
    Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    //  动态匹配
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { 
        InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) 
            return dm.interceptor.invoke(this); // 调用拦截器
        else 
            return proceed(); // 不匹配不调用拦截器
    }
    // 普通拦截器,直接调用拦截器即可
    else return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); // 将this作为参数传递以保证当前实例中调用链的执行
}

proceed方法中,或许代码逻辑并没有我们想象得那么复杂,ReflectiveMethodlnvocation中的主要职责是维护了链接调用的计数器,记录着当前调用链接的位置,以便链可以有序地进行下去,那么在这个方法中并没有我们之前设想的维护各种增强的顺序,而是将此工作委托给了各个增强器,使各个增强器在内部进行逻辑实现

c> ObjenesisCglibAopProxy.getProxy(classLoader) 获得代理对象

上面介绍完JDK动态代理之后,我们下面来介绍Cglib动态代理是如果获得代理对象的。对于Cglib大家一定不会陌生,下面我们就手写一下通过Cglib获得代理对象的示例,请见如下代码所示:

public class CGlibTest {
    public static void main(String[] args) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CGlibTest.class);
        enhancer.setCallback(new MyMethodInterceptor());
        // 通过增强器,获得CGlib代理对象
        CGlibTest test = (CGlibTest) enhancer.create();
        test.play();
    }

    public void play() {
        System.out.println("play game!");
    }
}

/**
 * 创建GClib拦截器
 */
class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("-----------------preExecutor-----------------");
        Object result =  methodProxy.invokeSuper(o, objects);
        System.out.println("-----------------afterExecutor-----------------");
        return result;
    }
}

重温了cglib动态代理之后,我们来看Spring AOP是如何通过它来获得代理的,此处我们来看一下ObjenesisCglibAopProxy类的getProxy(...)方法是如何实现的:

public Object getProxy(@Nullable ClassLoader classLoader) {
    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Class<?> proxySuperClass = rootClass;
        // 如果类名中包含"$$"
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) 
                this.advised.addInterface(additionalInterface);
        }
        // 针对类进行验证操作
        validateClassIfNecessary(proxySuperClass, classLoader);
        
        // 创建Cglib的Enhancer并对其进行配置操作
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) 
                enhancer.setUseCache(false);
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
        
        /** 设置拦截器 */
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) types[x] = callbacks[x].getClass();
        enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        
        // 生成代理类并创建代理实例对象
        return createProxyClassAndInstance(enhancer, callbacks);
    } 
    catch (CodeGenerationException | IllegalArgumentException ex) {...}
}

/** 创建代理对象 */
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    enhancer.setInterceptDuringConstruction(false);
    enhancer.setCallbacks(callbacks);
    return (this.constructorArgs != null && this.constructorArgTypes != null ?
            enhancer.create(this.constructorArgTypes, this.constructorArgs) : // 采用有参构造函数创建实例对象
            enhancer.create()); // 采用无参构造函数创建实例对象
}

通过上面的代码,我们可以大致了解getProxy方法的处理逻辑分为3个步骤:
步骤1】创建Enhancer实例对象,并对其进行初始化;
步骤2】获得Callback拦截器,并赋值到Enhancer实例对象中;
步骤3】通过Enhancer实例对象的create(...)方法来创建代理对象;

由于我们手工创建过Cglib动态代理了,所以对于上述步骤都会比较熟悉,但是对于第二步获得拦截器,我们还是比较陌生的,那么我们就来着重分析一下这个方法,其源码如下所示:

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // Parameters used for optimization choices...
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();
    // 步骤1:创建aopInterceptor拦截器
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
    
    // 步骤2:创建targetInterceptor拦截器
    Callback targetInterceptor;
    if (exposeProxy) targetInterceptor = (isStatic ?
            new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
            new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
    else targetInterceptor = (isStatic ?
            new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
            new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
    
    // 步骤3:创建targetDispatcher调度器
    Callback targetDispatcher = (isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());
    
    // 步骤4:将不同的拦截器或调度器都保存到Callback数组中
    Callback[] mainCallbacks = new Callback[] {
            aopInterceptor,  // 用于一般的增强器
            targetInterceptor,  // 如果被优化了,调用target而不考虑调用增强
            new SerializableNoOp(),  // 映射到this的方法不能重写
            targetDispatcher,
            this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };
    
    // 步骤5:如果目标是静态的并且Advice链是冻结的,那么我们可以通过使用该方法的固定链直接将AOP调用发送到目标来进行一些优化。
    Callback[] callbacks;
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);
        for (int x = 0; x < methods.length; x++) {
            Method method = methods[x];
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(method, x);
        }
        // callbacks = mainCallbacks + fixedCallbacks;
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else 
        callbacks = mainCallbacks; // callbacks = mainCallbacks;
    return callbacks;
}

从上面我们自定义演示Cglib例子中可以看到,通过enhancer.setCallback(new MyMethodInterceptor())这段代码,可以将我们自定义的拦截器注入到增强中,那么,在上面源码中,我们在步骤1中将advised保存到DynamicAdvisedInterceptor中,并在下面的步骤里,将其保存到Callback数组中,那么,当执行Cglib代理调用的时候,就会调用DynamicAdvisedInterceptor类中的intercept(...)方法了,代码如下所示:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // 没有拦截链,直接调用原方法即可
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else // 调用拦截链
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) targetSource.releaseTarget(target);
        if (setProxyContext) AopContext.setCurrentProxy(oldProxy);
    }
}

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

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

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

相关文章

Nerf-Wild神经辐射场论文学习笔记 Neural Radiance Fields for Unconstrained Photo Collections

前言&#xff1a; 本文为记录自己在Nerf学习道路的一些笔记&#xff0c;包括对论文以及其代码的思考内容。公众号&#xff1a; AI知识物语 B站讲解&#xff1a;出门吃三碗饭 本篇文章主要针对其数学公式来学习其内容&#xff0c;欢迎批评指正&#xff01;&#xff01;&#x…

10-C++学习笔记-字符串

&#x1f4da; 前言 字符串是在编程中广泛使用的数据类型&#xff0c;用于表示一系列字符。在C中&#xff0c;我们可以使用C风格字符串和string类来处理字符串操作。本篇学习笔记将详细介绍字符串的相关知识。 &#x1f4d6; 1 C风格字符串 ✨ C风格字符串初始化 C风格字符…

什么是EMC存储 Clarrion存储的cache dirty或者cache lost(CACD)?

CACD是Cant assign, Cache Dirty的缩写&#xff0c;DELL EMC的专业术语。 在开始之前&#xff0c;先介绍下cache dirty的概念&#xff0c;朴素的语言就是有了脏数据&#xff0c;脏数据当然就是不能使用的数据了。为什么数据会脏呢&#xff1f;先从存储的基本概念聊起来。 为了加…

PoseiSwap 将向 Zepoch 节点持有者发放新一轮空投,生态启动在即

目前&#xff0c;随着各类 Layer2 空投不断内卷&#xff0c;越来越多的用户疲于参与其中&#xff08;参与交互也很有可能难以获得空投资格&#xff09;。Nautilus Chain 作为目前模块化 Layer3 架构链&#xff0c;在初期就明确了空投计划&#xff0c;即所有上线的应用都将会拿出…

各类农作物分布遥感监测数据大全

最近收集整理了大量的农作物分布的遥感监测数据&#xff0c;废话不多说&#xff0c;分享给大家&#xff0c;后面会持续更新&#xff01;&#xff01; 数据查看地址&#xff1a; https://www.dilitanxianjia.com/%e9%81%a5%e6%84%9f%e8%a7%a3%e8%af%91%e5%90%8e%e6%88%90%e6%9…

LeetCode 0002. 两数相加

【LetMeFly】2.两数相加 力扣题目链接&#xff1a;https://leetcode.cn/problems/add-two-numbers/ 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff…

2016年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

华为OD机试真题 Java 实现【开心消消乐】【2023 B卷 100分】

目录 一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示1、输入2、输出3、说明 一、题目描述 给定一个N行M列的二维矩阵&#xff0c;矩阵中每个位置的数字取值为0或1。矩阵示例如&#xff1a; 1 1 0 0 0 0 0 1 0 0 1 1 1 1 1 1 现需要将矩阵中所有的1进行反…

maven高级开发

分模块设计 例如 如果需要用到其他的模块&#xff0c;直接导入模块坐标就可以了 分模块开发&#xff0c;需建立的模块&#xff0c;选择maven模型 分模块设计需要先针对模块功能进行设计&#xff0c;再进行编码。 继承与聚合 继承&#xff1a;描述的是两个工程间的关系&…

UE4/5通过插件,用GeneratedDynamicMeshActor编辑静态网格到content中

目录 制作 直接复制下面代码到蓝图中粘贴即可&#xff1a; 效果1【用了第一个函数】&#xff1a; 效果2【用了第二个函数】&#xff1a; 制作 首先我们要打开插件&#xff1a; 然后继承GeneratedDynamicMeshActor进行创建&#xff1a; 我们制作一个函数&#xff0c;这个函数将…

Draw.io | 一款强大且支持在线编辑和导出的流程图绘制神器

目录 &#x1f48c; 写在前面 &#x1f5a5;️ 软件介绍 &#x1f5a5;️ 使用方式 1. Draw.io 的核心设计元素 1.1 绘图区 1.2 快速开始 2. Draw.io 的基本操作 2.1 移动、多选、复制与删除 2.2 创建链接 2.3 图形替换与旋转 3. 使用 Draw.io 绘制简单流程图 3.1 基本…

ChatGPT Plugins内幕、源码及案例实战(三)

ChatGPT Plugins内幕、源码及案例实战(三) ChatGPT Plugins内幕、源码及案例实战 6.4 ChatGPT Retrieval Plugin全流程内幕解析 以ChatGPT检索插件为例,我们讲解一下它整个的流程,让大家有一个比较明确、清晰的统一认知:  数据存储:设立的前提是你有文档,会有一些文…

大学教材征订管理系统数据库设计

摘 要 随着计算机及Internet迅速的发展&#xff0c;越来越多的行业实现了管理的信息化和自动化&#xff0c;教育行业也不例外。但在很多高校中,教材征订作为学校教学工作中很重要的一个环节还没完全实现信息化管理。因此本系统针对高校教材征订管理过程中人工管理存在的困难&a…

(02)Cartographer源码无死角解析-(67) 2D后端优化→FastCorrelativeScanMatcher2D -分支定界算法(BranchAndBound)1

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文…

Java——包(package)

一、Java中的包(package) 在Java中&#xff0c;包&#xff08;package&#xff09;是一种用于组织类和接口的命名空间的机制。它用于将相关的类和接口组织在一起&#xff0c;以便更好地管理和维护代码。 其实就是一个个文件夹 二、包(package)的作用 防止类和接口命名冲突&…

时间序列分解 | Matlab变分模态分解(VMD)的信号分解

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列分解 | Matlab变分模态分解(VMD)的信号分解 部分源码 %--------------------

尚硅谷Docker2022版学习笔记(基础篇 下)

目录 五、本地镜像发布到阿里云 5.1、本地镜像发布到阿里云流程 5.2、镜像的生成方法 基于当前容器创建一个新的镜像&#xff08;第一种方法&#xff09; DockerFile&#xff08;第二种方法&#xff09; 5.3、将本地镜像推送到阿里云 本地镜像素材原型 创建仓库镜像 选…

hive row_number()对单列进行去重

学到一个高级用法 row_number() select * ,row_number() over (partition by c_id order by s_score) from score; https://www.jb51.net/article/254952.htm

【Kafka】第一章

【Kafka】第一章 1.课程内容 1.课程内容 >课程学习