Spring Aop源码深度解析

Spring Aop的创建者AnnotationAwareAspectJAutoProxyCreator

这是创建Aop代理类的关键类。它实现了BeanPostProcessor和InstantiationAwareBeanPostProcessor接口(InstantiationAwareBeanPostProcessor继承了BeanPostProcessor)

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {
    @Nullable
    //该方法在bean初始化之前会执行
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    //该方法在bean初始化之后会执行
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
package org.springframework.beans.factory.config;

import java.beans.PropertyDescriptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.lang.Nullable;

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    //该方法在bean实例化之前会执行
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }
	//该方法在bean实例化之后会执行
    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return null;
    }

    /** @deprecated */
    @Deprecated
    @Nullable
    default PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        return pvs;
    }
}

根据方法名我们可以知道,BeanPostProcessor接口的方法分别在bean初始化之前和之后执行,InstantiationAwareBeanPostProcessor接口的方法分别在bean实例化之前和之后执行。
其中代理类的生成是在bean初始化之后生成的,也就是在BeanPostProcessor接口的postProcessAfterInitialization方法中生成的。

AnnotationAwareAspectJAutoProxyCreator的具体实现

这里AnnotationAwareAspectJAutoProxyCreator内部并没有对这两个接口的具体的实现,具体的实现在其父类AbstractAutoProxyCreator中。
主要的逻辑在postProcessBeforeInstantiation和postProcessAfterInitialization方法中。
下面按照执行的先后顺序先来讲解postProcessBeforeInstantiation方法

生成代理类的准备工作:postProcessBeforeInstantiation方法

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
		//获取缓存的key,一般为beanName,如果bean实现了FactoryBean接口,则key为&beanName
        Object cacheKey = this.getCacheKey(beanClass, beanName);
        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            //如果advisedBeans包含了cacheKey,说明该bean是切面不需要生成代理类或者已经完成了代理
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
			//如果该bean是Advice,Pointcut,Advisor,AopInfrastructureBean的子类或者该bean是切
			面类,则添加到advisedBeans中,标记为false,意思是该bean不需要生成代理类
            if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

        TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
        //这里targetSource为null,忽略
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }

            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            return null;
        }
    }

进入isInfrastructureClass方法,可以看到如果该bean是Advice,Pointcut,Advisor,AopInfrastructureBean这些跟aop组件相关的类,就标记为false,表示不需要进行aop

protected boolean isInfrastructureClass(Class<?> beanClass) {
        boolean retVal = Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass);
        if (retVal && this.logger.isTraceEnabled()) {
            this.logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
        }

        return retVal;
    }

接下来看shouldSkip方法,顾名思义就是判断该bean是否跳过aop

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
		//获取bean容器中所有的Advisor类,Advisor是切面的一种实现,内部包含了切点和通知
        List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
        Iterator var4 = candidateAdvisors.iterator();

        Advisor advisor;
        do {
            if (!var4.hasNext()) {
            	//返回false
                return super.shouldSkip(beanClass, beanName);
            }

            advisor = (Advisor)var4.next();
        } while(!(advisor instanceof AspectJPointcutAdvisor) || !((AspectJPointcutAdvisor)advisor).getAspectName().equals(beanName));
		//如果该advisor是AspectJPointcutAdvisor的实例或者该bean是切面类,则返回true
        return true;
    }
获取实现Advisor接口的bean,一般用于事务

紧接着看findCandidateAdvisors方法

protected List<Advisor> findCandidateAdvisors() {
		//获取bean容器中所有实现Advisor接口的bean
        List<Advisor> advisors = super.findCandidateAdvisors();
        if (this.aspectJAdvisorsBuilder != null) {
        	//获取bean容器所有的对象并判断是不是切面类,然后把切面类的前置通知,后置通知,环绕通知等转
        	换成InstantiationModelAwarePointcutAdvisorImpl这个Advisor,并缓存下来
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }

        return advisors;
    }

先来看下findCandidateAdvisors方法,这个方法是获取bean容器中所有实现Advisor接口的bean,创建bean之后放到集合中并返回。
如果我们开启了事物注解@Transactional那么返回的Advisor实例则是BeanFactoryTransactionAttributeSourceAdvisor(advice是TransactionInterceptor)

protected List<Advisor> findCandidateAdvisors() {
        Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
        //主要逻辑在这个方法中
        return this.advisorRetrievalHelper.findAdvisorBeans();
    }





public List<Advisor> findAdvisorBeans() {
		//去缓存中取
        String[] advisorNames = this.cachedAdvisorBeanNames;
        if (advisorNames == null) {
        	//缓存中没有,则去bean容器中获取所有实现Advisor接口的bean的名称,放到缓存中
            advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);
            this.cachedAdvisorBeanNames = advisorNames;
        }

        if (advisorNames.length == 0) {
        	//没获取到,直接返回
            return new ArrayList();
        } else {
            List<Advisor> advisors = new ArrayList();
            String[] var3 = advisorNames;
            int var4 = advisorNames.length;
			//遍历Advisor集合
            for(int var5 = 0; var5 < var4; ++var5) {
                String name = var3[var5];
                if (this.isEligibleBean(name)) {
                	//该bean是否正在创建中
                    if (this.beanFactory.isCurrentlyInCreation(name)) {
                        if (logger.isTraceEnabled()) {
                            logger.trace("Skipping currently created advisor '" + name + "'");
                        }
                    } else {
                    	//没有在创建,就去创建,创建完成后放到advisors中并返回
                        try {
                            advisors.add(this.beanFactory.getBean(name, Advisor.class));
                        } catch (BeanCreationException var11) {
                            Throwable rootCause = var11.getMostSpecificCause();
                            if (rootCause instanceof BeanCurrentlyInCreationException) {
                                BeanCreationException bce = (BeanCreationException)rootCause;
                                String bceBeanName = bce.getBeanName();
                                if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
                                    if (logger.isTraceEnabled()) {
                                        logger.trace("Skipping advisor '" + name + "' with dependency on currently created bean: " + var11.getMessage());
                                    }
                                    continue;
                                }
                            }

                            throw var11;
                        }
                    }
                }
            }

            return advisors;
        }
    }
获取自定义的切面类@Aspect

接下来看上面的buildAspectJAdvisors方法,这个方法主要是获取bean容器中所有的切面类(有@Aspect注解的类),然后把切面类中的通知转成Advisor。
这里返回的Advisor实例是InstantiationModelAwarePointcutAdvisorImpl

public List<Advisor> buildAspectJAdvisors() {
		//获取缓存中的切面bean的名称
        List<String> aspectNames = this.aspectBeanNames;
        if (aspectNames == null) {
        	//为空,则去获取切面放到缓存中
            synchronized(this) {
                aspectNames = this.aspectBeanNames;
                if (aspectNames == null) {
                    List<Advisor> advisors = new ArrayList();
                    List<String> aspectNames = new ArrayList();
                    //获取bean容器中所有的bean
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                    String[] var18 = beanNames;
                    int var19 = beanNames.length;
					//遍历bean
                    for(int var7 = 0; var7 < var19; ++var7) {
                        String beanName = var18[var7];
                        if (this.isEligibleBean(beanName)) {
                            Class<?> beanType = this.beanFactory.getType(beanName);
                            //判断bean是否有@Aspect注解
                            if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                            	//添加到切面名称集合中
                                aspectNames.add(beanName);
                                AspectMetadata amd = new AspectMetadata(beanType, beanName);
                                //判断切面是不是单例		
                                if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                                	//创建切面工厂类
                                    MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                    //把切面转换成Advisor集合(每个通知如:前置通知,后置通知,异常通知都会转换成一个Advisor)
                                    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                    if (this.beanFactory.isSingleton(beanName)) {
                                    	//单例,则把生成的Advisor集合放到缓存中
                                        this.advisorsCache.put(beanName, classAdvisors);
                                    } else {
                                    	//非单例,把切面工厂类放到缓存中,用于生成Advisor集合
                                        this.aspectFactoryCache.put(beanName, factory);
                                    }

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

                                    MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                    this.aspectFactoryCache.put(beanName, factory);
                                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                                }
                            }
                        }
                    }

                    this.aspectBeanNames = aspectNames;
                    return advisors;
                }
            }
        }

        if (aspectNames.isEmpty()) {
            return Collections.emptyList();
        } else {
            List<Advisor> advisors = new ArrayList();
            Iterator var3 = aspectNames.iterator();

            while(var3.hasNext()) {
                String aspectName = (String)var3.next();
                //取缓存
                List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
                if (cachedAdvisors != null) {
                	//取到直接返回
                    advisors.addAll(cachedAdvisors);
                } else {
                	//取不到,则去缓存中获取切面工厂类,通过工厂生成
                    MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                }
            }

            return advisors;
        }
    }

postProcessBeforeInstantiation方法看完了,这个方法是为了下面的postProcessAfterInitialization方法做铺垫,这个方法才是生成代理类的关键。

生成代理类的地方:postProcessAfterInitialization方法

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
        	//获取key
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            //earlyProxyReferences是用来保存有循环依赖的bean的,如果bean存在earlyProxyReferences中,说明该bean不需要aop。
              因为循环依赖的时候已经判断是否需要生成代理类了
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            	//判断需不需要生成代理类
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }

紧接着看wrapIfNecessary方法

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        	//标记为false的bean直接返回
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        	//获取符合条件的Advisor集合
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }
找到能够匹配该bean的Advisor

进入getAdvicesAndAdvisorsForBean方法

@Nullable
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    	//获取符合条件的Advisor
        List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
        return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
    }
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		//获取所有的Advisor,上面已经讲过了,跳过
        List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
        //找到符合条件的Advisor
        List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        this.extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
        	//对Advisor集合进行排序
            eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
        }

        return eligibleAdvisors;
    }
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
        ProxyCreationContext.setCurrentProxiedBeanName(beanName);

        List var4;
        try {
        	//主要逻辑在这个方法
            var4 = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
        } finally {
            ProxyCreationContext.setCurrentProxiedBeanName((String)null);
        }

        return var4;
    }
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        } else {
            List<Advisor> eligibleAdvisors = new ArrayList();
            Iterator var3 = candidateAdvisors.iterator();
			
            while(var3.hasNext()) {
                Advisor candidate = (Advisor)var3.next();
                //我们的Advisor不属于IntroductionAdvisor,跳过
                if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
                    eligibleAdvisors.add(candidate);
                }
            }

            boolean hasIntroductions = !eligibleAdvisors.isEmpty();
            Iterator var7 = candidateAdvisors.iterator();

            while(var7.hasNext()) {
                Advisor candidate = (Advisor)var7.next();
                //主要的判断逻辑在canApply方法,这里的Advisor以事务的Advisor实例BeanFactoryTransactionAttributeSourceAdvisor为例子,进行讲解
                if (!(candidate instanceof IntroductionAdvisor) && canApply(candidate, clazz, hasIntroductions)) {
                    eligibleAdvisors.add(candidate);
                }
            }

            return eligibleAdvisors;
        }
    }
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor)advisor).getClassFilter().matches(targetClass);
        } else if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor)advisor;
            //关键点在这里
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        } else {
            return true;
        }
    }
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
        Assert.notNull(pc, "Pointcut must not be null");
        //这里的参数pc为TransactionAttributeSourcePointcut
        //这里的判断为false,跳过
        if (!pc.getClassFilter().matches(targetClass)) {
            return false;
        } else {
        	//获取方法匹配器MethodMatcher,这里就是它自身也就是pc
            MethodMatcher methodMatcher = pc.getMethodMatcher();
            //methodMatcher是不是TrueMethodMatcher,显然不是,跳过
            if (methodMatcher == MethodMatcher.TRUE) {
                return true;
            } else {
                IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
                //切面的advisor才会符合条件,这里是事务的advisor,跳过
                if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
                    introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher)methodMatcher;
                }
				
                Set<Class<?>> classes = new LinkedHashSet();
                //判断targetClass是不是代理类,不是代理类就添加到classes集合中
                if (!Proxy.isProxyClass(targetClass)) {
                    classes.add(ClassUtils.getUserClass(targetClass));
                }
				//获取targetClass所有的接口
                classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
                Iterator var6 = classes.iterator();

				//遍历classes
                while(var6.hasNext()) {
                    Class<?> clazz = (Class)var6.next();
                    //获取clazz的方法
                    Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
                    Method[] var9 = methods;
                    int var10 = methods.length;
					//遍历获取到的方法
                    for(int var11 = 0; var11 < var10; ++var11) {
                        Method method = var9[var11];
                        //这里introductionAwareMethodMatcher为null,跳过
                        if (introductionAwareMethodMatcher != null) {
                            if (introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) {
                                return true;
                            }
                        } else if (methodMatcher.matches(method, targetClass)) {
                            return true;
                        }
                    }
                }

                return false;
            }
        }
    }

进入matches方法,位于TransactionAttributeSourcePointcut中

public boolean matches(Method method, Class<?> targetClass) {
		//获取事务属性资源类
        TransactionAttributeSource tas = this.getTransactionAttributeSource();
        //获取事务属性,能获取到就匹配成功了
        return tas == null || tas.getTransactionAttribute(method, targetClass) != null;
    }

进入getTransactionAttribute方法,位于AbstractFallbackTransactionAttributeSource中

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        } else {
        	//获取缓存的key
            Object cacheKey = this.getCacheKey(method, targetClass);
            //根据key去缓存中取
            TransactionAttribute cached = (TransactionAttribute)this.attributeCache.get(cacheKey);
            if (cached != null) {
                return cached == NULL_TRANSACTION_ATTRIBUTE ? null : cached;
            } else {
            	//没有取到,调用computeTransactionAttribute获取
                TransactionAttribute txAttr = this.computeTransactionAttribute(method, targetClass);
                if (txAttr == null) {
                    this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
                } else {
                    String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
                    if (txAttr instanceof DefaultTransactionAttribute) {
                        ((DefaultTransactionAttribute)txAttr).setDescriptor(methodIdentification);
                    }

                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
                    }

                    this.attributeCache.put(cacheKey, txAttr);
                }

                return txAttr;
            }
        }
    }
解析方法和类上的@Transactional

进入到computeTransactionAttribute方法

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
        if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } else {
            Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            //如果该方法上有@Transactional注解,则会把注解中的参数封装到TransactionAttribute中
            TransactionAttribute txAttr = this.findTransactionAttribute(specificMethod);
            if (txAttr != null) {
                return txAttr;
            } else {
            	//方法上没有@Transactional注解,那就去看类上有没有@Transactional注解,有的话就封装成TransactionAttribute
                txAttr = this.findTransactionAttribute(specificMethod.getDeclaringClass());
                if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
                    return txAttr;
                } else {
                    if (specificMethod != method) {
                        txAttr = this.findTransactionAttribute(method);
                        if (txAttr != null) {
                            return txAttr;
                        }

                        txAttr = this.findTransactionAttribute(method.getDeclaringClass());
                        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
                            return txAttr;
                        }
                    }

                    return null;
                }
            }
        }
    }

至此事务的Advisor和bean匹配完成

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        	//标记为false的bean直接返回
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        	//不是切面类会进入到这里
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
            	//如果至少有一个Advisor匹配成功,则会创建代理类
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                //创建代理类
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                //缓存代理类
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
            	//没有一个Advisor匹配成功,则标记为false,表示不需要生成代理类
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }

至此创建代理类完成~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值