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

一、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() 寻找在IOC中注册过的Advisor接口实现类

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(...) 创建同步实例化增强器

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

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);
    }
}

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

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

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

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

相关文章

springcloudalibaba入门详细使用教程

目录标题 一、简介二、SpringCloud Alibaba核心组件2-1、Nacos (配置中心与服务注册与发现)2-2、Sentinel (分布式流控)2-3、RocketMQ (消息队列)/RabbitMq/kafka2-4、Seata (分布式事务)2-5、Dubbo (RPC) 三、为什么大家看好 Spring Cloud Alibaba3-1、阿里巴巴强大的技术输出…

【每日一题】307. 区域和检索 - 数组可修改-2023.11.13

题目&#xff1a; 307. 区域和检索 - 数组可修改 给你一个数组 nums &#xff0c;请你完成两类查询。 其中一类查询要求 更新 数组 nums 下标对应的值另一类查询要求返回数组 nums 中索引 left 和索引 right 之间&#xff08; 包含 &#xff09;的nums元素的 和 &#xff0c…

智能井盖传感器具有什么效果?

智能井盖传感器与智慧城市之间有着密切的关联&#xff0c;两者之间属于相辅相成的状态&#xff0c;对于城市的现代化和城市生命线建设有助力作用。智能井盖传感器是其中一个重要的组成环节&#xff0c;它们帮助城市改变原有的生活和生态环境&#xff0c;为政府部门完善城市基础…

【机器学习】 朴素贝叶斯算法:原理、实例应用(文档分类预测)

1. 算法原理 1.1 朴素贝叶斯方法 朴素贝叶斯方法涉及一些概率论知识&#xff0c;我们先来复习一下。 联合概率&#xff1a;包含多个条件&#xff0c;并且所有的条件同时成立的概率&#xff0c;公式为&#xff1a; 条件概率&#xff1a;事件A在另一个事件B已经发生的前提下发…

工作十年+的测试应该具备什么能力?

大概是2014年的时候&#xff0c;我开始接触面试工作&#xff0c;就是从应聘者转为面试官&#xff0c;记得印象深刻的是面试了一位做了8年的测试。对方气场很足&#xff0c;嗯&#xff0c;毕竟那时的我还只是一个3、4年经验的小测试&#xff0c;相反&#xff0c;印象深刻的并不是…

Mysql基本知识

1.SQL分类 DDL【data definition language】 数据定义语言&#xff0c;用来维护存储数据的结构 代表指令: create, drop, alter DML【data manipulation language】 数据操纵语言&#xff0c;用来对数据进行操作 代表指令&#xff1a; insert&#xff0c;delete&#xff0c;up…

十四、W5100S/W5500+RP2040树莓派Pico<NetBIOS>

文章目录 1 前言2 简介2 .1 什么是NetBIOS&#xff1f;2.2 NetBIOS的优点2.3 NetBIOS工作原理2.4 NetBIOS应用场景 3 WIZnet以太网芯片4 NetBIOS网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着…

单片机定时器讲解和实现

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、计数器是什么&#xff1f;二、单片机定时器结构2.1***两个8位如何合成16位&#xff0c;16位如何分成两个8位***2.2 计数器的位数组合&#xff1f;2.3 定时功…

C# OpenCvSharp 基于直线检测的文本图像倾斜校正

效果 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenCvSharp_基于直线检测的文本图像…

基于若依的ruoyi-nbcio流程管理系统增加读取节点扩展属性的方法

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 我们的在流程设计器里会根据需要再不同的节点增加扩展属性&#xff0c;如何动态读取这些扩展属性&#xff…

一文了解芯片测试项目和检测方法 -纳米软件

芯片检测是芯片设计、生产、制造成过程中的关键环节&#xff0c;检测芯片的质量、性能、功能等&#xff0c;以满足设计要求和市场需求&#xff0c;确保芯片可以长期稳定运行。芯片测试内容众多&#xff0c;检测方法多样&#xff0c;今天纳米软件将为您介绍芯片的检测项目都有哪…

下载并安装DevEco Studio 3.1,初尝鸿蒙编程

摘自华为官网 DevEco Studio 3.1配套支持HarmonyOS 3.1版本及以上的应用及服务开发&#xff0c;提供了代码智能编辑、低代码开发、双向预览等功能&#xff0c;以及轻量构建工具DevEco Hvigor 、本地模拟器&#xff0c;持续提升应用及服务开发效率。 下载 官网下载地址 HUAWEI…

取暖器/暖风机上架 亚马逊美国站UL1278测试标准要求

美国是一个对安全要求非常严格的国家&#xff0c;美国本土的所有电子产品生产企业早在很多年前就要求有相关检测。而随着亚马逊在全球商业的战略地位不断提高&#xff0c;境外的电子设备通过亚马逊不断涌入美国市场。“为保证消费者得安全&#xff0c;亚马逊始终强调带电得产品…

CSRF 漏洞验证

CSRF 漏洞验证 环境准备&#xff1a; dvwa csrf 为例 burpsuite 工具 dvwa靶场&#xff08;CSRF&#xff09; 方法一&#xff1a; 1.修改密码抓包 这里是为了理解先抓包查看修改密码时的数据 GET /dvwa_2.0.1/vulnerabilities/csrf/?password_newpassword&password_con…

粉够荣获淘宝联盟区域理事会常务理事,携手共铸淘客新生态

淘宝联盟区域理事会于2021年成立&#xff0c;首届成立成都、广州、武汉&#xff0c;服务近2000个领军淘宝客企业&#xff0c;作为区域生态与官方交流重要枢纽&#xff0c;理事会举办近百场交流分享会&#xff0c;带动淘客跨域跨业态交流成长。 2023年9月7日第二届淘宝联盟理事…

将ChatGPT集成在AR中,Snap玩出了新花样!

著名社交媒体平台Snap在官网宣布&#xff0c;在最新的AR开发平台Lens Studio 5.0版本中&#xff0c;集成ChatGPT功能。 在ChatGPT的帮助下&#xff0c;开发人员可以创建更多有趣、科普、对话、创意的Snapchat镜头&#xff0c;例如&#xff0c;通过ChatGPT创建无限测验和随机生…

双十一电视盒子哪个牌子好?测评工作室整理口碑电视盒子排名

在挑选电视盒子的时候&#xff0c;新手朋友们不知道从何下手&#xff0c;最近很多粉丝评论想要我们分享双11电视盒子推荐&#xff0c;于是我们根据用户的评价整理了目前口碑最好的电视盒子排名&#xff0c;给不懂电视盒子哪个牌子好的朋友们做个参考。 TOP 1、泰捷WEBOX WE40S电…

SpringBoot项目中ModelMapper配置以及使用

这里总结一下ModelMapper的使用方式&#xff0c;供大家参考 前言 项目中对象与对象赋值转换使用的频率非常的高&#xff0c;比如数据库表实体对象(Entity)与业务类对象(Model)之间的赋值传递&#xff0c;或者模型对象&#xff08;Model&#xff09;与视图对象&#xff08;View…

2023数字科技生态大会-数字安全论坛 学习笔记

监管合规->价值创造的方向&#xff0c;在安全领域的发展方向-安全运营服务型 ICT->数字->数据 数字安全&#xff1a;网络安全数据安全 传统信息化以计算为核心&#xff0c;数字化以数据为核心 数字安全技术发展十大趋势&#xff1a; 一、 数字安全技术政策环境将不…