Spring源码分析 5:AOP

AOP

AOP基本概念

1)连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。

2)切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。

3)增强(Advice)
增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。

4)目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。

5)引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

6)织入(Weaving)
织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
a、编译期织入,这要求使用特殊的Java编译器。
b、类装载期织入,这要求使用特殊的类装载器。
c、动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

7)代理(Proxy)
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

8)切面(Aspect)
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

AOP流程

1、advisor:可以理解为一个pointCut和一个advice的封装,是一个切面;
2、AspectJAwareAdvisorAutoProxyCreator,当实例化所有bean都会执行到AspectJAwareAdvisorAutoProxyCreator类,它会检测bean是否advisor以及advice存在,如果有就说明这个bean有切面,有切面那么就会生成代理;
3、jdk的代理,bean里面的所有advisor加入到proxyFactory;
4、jdkDynamicProxy invoke,拿到bean里面的所有Interceptor,会循环proxyFactory里面的所有advisor
的advice,里面的advice有两种类型,要么是advice,要么是MethodInterceptor类型的;
5、代理对象调用方式,是一个MethodInterceptor类型的类的链式调用过程,直到容器的大小和索引一致的时候调用JoinPoint目标方法。

AOP调用位置

  • 在bean实例化+ioc依赖注入完成以后调用aop的方法,如果bean还没有实例化完成,就不能被代理。
  • 通过bean的实例化过程分析,bean实例化+ioc依赖注入之后的方法是AbstractBeanFactory#initializeBean(beanName, exposedObject, mbd)

在这里插入图片描述
后面断点调试的代码示例https://blog.csdn.net/qq_40977118/article/details/108937745

源码分析

1. AbstractAutowireCapableBeanFactory#initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)

在这里插入图片描述

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
	if (System.getSecurityManager() != null) {
		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
			invokeAwareMethods(beanName, bean);
			return null;
		}, getAccessControlContext());
	}
	else {
		invokeAwareMethods(beanName, bean);
	}

	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	try {
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null),
				beanName, "Invocation of init method failed", ex);
	}
	if (mbd == null || !mbd.isSynthetic()) {
		//这个地方可能生出代理实例,是aop的入口
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}

	return wrappedBean;
}

在这里插入图片描述

2. AbstractAutoProxyCreator#postProcessAfterInitialization(@Nullable Object bean, String beanName)

在这里插入图片描述

在这里插入图片描述

3. AbstractAutoProxyCreator#wrapIfNecessary(Object bean, String beanName, Object cacheKey)

  • 如果这个bean有advice的话,就创建当前bean的代理

在这里插入图片描述

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	//创建当前bean的代理,如果这个bean有advice的话,重点看,重要程度5
	// Create proxy if we have advice.
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	//如果有切面,则生成该bean的代理
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		//把被代理对象bean实例封装到SingletonTargetSource对象中
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
}

4. AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource)

  • 找到合格的切面

在这里插入图片描述

5. AbstractAdvisorAutoProxyCreator#findEligibleAdvisors(Class<?> beanClass, String beanName)

在这里插入图片描述

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	//找到候选的切面,其实就是一个寻找有@Aspect注解的过程,把工程中所有有这个注解的类封装成Advisor返回
	List<Advisor> candidateAdvisors = findCandidateAdvisors();

	//判断候选的切面是否作用在当前beanClass上面,就是一个匹配过程
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
		//对有@Order@Priority进行排序
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
}
  • 进入子类

在这里插入图片描述

6. AnnotationAwareAspectJAutoProxyCreator#buildAspectJAdvisors()

在这里插入图片描述

protected List<Advisor> findCandidateAdvisors() {
	//可以不看
	// Add all the Spring advisors found according to superclass rules.
	List<Advisor> advisors = super.findCandidateAdvisors();
	// Build Advisors for all AspectJ aspects in the bean factory.
	//主要看这里,创建候选的切面
	if (this.aspectJAdvisorsBuilder != null) {
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
}

7. BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()

在这里插入图片描述

public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;

	if (aspectNames == null) {
		synchronized (this) {
			aspectNames = this.aspectBeanNames;
			if (aspectNames == null) {
				List<Advisor> advisors = new ArrayList<>();
				aspectNames = new ArrayList<>();
				// 1.获取spring容器中的所有bean的名称BeanName
				String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						this.beanFactory, Object.class, true, false);
				for (String beanName : beanNames) {
					if (!isEligibleBean(beanName)) {
						continue;
					}
					// We must be careful not to instantiate beans eagerly as in this case they
					// would be cached by the Spring container but would not have been weaved.
					Class<?> beanType = this.beanFactory.getType(beanName);
					if (beanType == null) {
						continue;
					}
					// 2.判断类上是否有@Aspect注解
					if (this.advisorFactory.isAspect(beanType)) {
						aspectNames.add(beanName);
						// 3.把带有@Aspect注解的类的基本信息包装成AspectMetadata对象
						AspectMetadata amd = new AspectMetadata(beanType, beanName);
						if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {

							// 4.创建获取有@Aspect注解类的实例工厂,负责获取有@Aspect注解类的实例
							MetadataAwareAspectInstanceFactory factory =
									new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);

							// 5.创建切面advisor对象
							List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
							if (this.beanFactory.isSingleton(beanName)) {
								this.advisorsCache.put(beanName, classAdvisors);
							}
							else {
								this.aspectFactoryCache.put(beanName, factory);
							}
							advisors.addAll(classAdvisors);
						}
						else {
							// Per target or per this.
							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();
	}
	List<Advisor> advisors = new ArrayList<>();
	for (String aspectName : aspectNames) {
		List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
		if (cachedAdvisors != null) {
			advisors.addAll(cachedAdvisors);
		}
		else {
			MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
			advisors.addAll(this.advisorFactory.getAdvisors(factory));
		}
	}
	return advisors;
}

在这里插入图片描述

8. ReflectiveAspectJAdvisorFactory#getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory)

  • 创建切面advisor对象

在这里插入图片描述

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
	// 1.从工厂中获取有@Aspect注解的类Class
	Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
	//从工厂中获取有@Aspect注解的类的名称
	String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
	validate(aspectClass);

	// 2.创建工厂的装饰类,获取实例只会获取一次
	// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
	// so that it will only instantiate once.
	MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
			new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

	List<Advisor> advisors = new ArrayList<>();

	// 3.这里循环没有@Pointcut注解的方法
	for (Method method : getAdvisorMethods(aspectClass)) {

		// 4. 得到Advisor对象(包含Pointcut和Advice)非常重要,重要程度 5
		Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
		if (advisor != null) {
			// 5.把Advisor对象加入容器
			advisors.add(advisor);
		}
	}

	// If it's a per target aspect, emit the dummy instantiating aspect.
	if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
		advisors.add(0, instantiationAdvisor);
	}

	//判断属性上是否有引介注解
	// Find introduction fields.
	for (Field field : aspectClass.getDeclaredFields()) {
		//判断属性上是否有DeclareParents注解,如果有返回切面
		Advisor advisor = getDeclareParentsAdvisor(field);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}

	return advisors;
}

9. ReflectiveAspectJAdvisorFactory#getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,int declarationOrderInAspect, String aspectName)

在这里插入图片描述

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
		int declarationOrderInAspect, String aspectName) {

	validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

	//获取pointCut对象,最重要的是从注解中获取表达式
	AspectJExpressionPointcut expressionPointcut = getPointcut(
			candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
	if (expressionPointcut == null) {
		return null;
	}

	//创建Advisor切面类,这才是真正的切面类,一个切面类里面肯定要有1、pointCut 2、advice
	//这里pointCut是expressionPointcut, advice 增强方法是 candidateAdviceMethod
	return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
			this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

10. ReflectiveAspectJAdvisorFactory#getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass)

在这里插入图片描述

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
	//从候选的增强方法里面 candidateAdviceMethod  找有有注解
	//Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class
	//并把注解信息封装成AspectJAnnotation对象
	AspectJAnnotation<?> aspectJAnnotation =
			AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
	if (aspectJAnnotation == null) {
		return null;
	}

	//创建一个PointCut类,并且把前面从注解里面解析的表达式设置进去
	AspectJExpressionPointcut ajexp =
			new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
	ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
	if (this.beanFactory != null) {
		ajexp.setBeanFactory(this.beanFactory);
	}
	return ajexp;
}

11. AbstractAspectJAdvisorFactory#findAspectJAnnotationOnMethod(Method method)

在这里插入图片描述

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
	for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
		//找到Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class
		//注解的方法,并且把注解里面的信息封装成AspectJAnnotation对象
		AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
		if (foundAnnotation != null) {
			return foundAnnotation;
		}
	}
	return null;
}

@Nullable
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
	//找的规则就是找注解的父注解,递归的方式去找,直到找到目标注解为止
	A result = AnnotationUtils.findAnnotation(method, toLookFor);
	if (result != null) {
		//把注解里面的信息解析出来,然后包装成AspectJAnnotation对象
		return new AspectJAnnotation<>(result);
	}
	else {
		return null;
	}
}
  • 找到带@Pointcut,@Around,@Before,@After,@AfterReturning,@AfterThrowing的注解

在这里插入图片描述

12. AbstractAspectJAdvisorFactory#AspectJAnnotation(A annotation)

  • 封装了注解里面的基本信息,注解本身,注解类型,参数,表达式,最后返回

在这里插入图片描述
在这里插入图片描述

public AspectJAnnotation(A annotation) {
	this.annotation = annotation;
	//得到注解的类型
	this.annotationType = determineAnnotationType(annotation);
	try {
		//解析注解上面的表达式,如@Around("pc1()")
		//@AfterReturning(pointcut = "execution(public * com.xiangxue.jack.service.*.*(..))")
		this.pointcutExpression = resolveExpression(annotation);
		//获取注解上的参数
		Object argNames = AnnotationUtils.getValue(annotation, "argNames");
		this.argumentNames = (argNames instanceof String ? (String) argNames : "");
	}
	catch (Exception ex) {
		throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
	}
}

13.回到第9步 ReflectiveAspectJAdvisorFactory#getAdvisor()

在这里插入图片描述

14. InstantiationModelAwarePointcutAdvisorImpl#new InstantiationModelAwarePointcutAdvisorImpl()

在这里插入图片描述

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
		Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
		MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

	this.declaredPointcut = declaredPointcut;
	this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
	this.methodName = aspectJAdviceMethod.getName();
	this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
	this.aspectJAdviceMethod = aspectJAdviceMethod;
	this.aspectJAdvisorFactory = aspectJAdvisorFactory;
	this.aspectInstanceFactory = aspectInstanceFactory;
	this.declarationOrder = declarationOrder;
	this.aspectName = aspectName;

	if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		// Static part of the pointcut is a lazy type.
		Pointcut preInstantiationPointcut = Pointcuts.union(
				aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

		// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
		// If it's not a dynamic pointcut, it may be optimized out
		// by the Spring AOP infrastructure after the first evaluation.
		this.pointcut = new PerTargetInstantiationModelPointcut(
				this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
		this.lazy = true;
	}
	else {
		// A singleton aspect.
		this.pointcut = this.declaredPointcut;
		this.lazy = false;
		//这个方法重点看看,创建advice对象
		this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
	}
}

15. InstantiationModelAwarePointcutAdvisorImpl#instantiateAdvice(AspectJExpressionPointcut pointcut)

  • 创建advice对象
  • Advice是一个空的接口,用来区分类型

在这里插入图片描述
在这里插入图片描述

16. ReflectiveAspectJAdvisorFactory#getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName)

在这里插入图片描述

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
		MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

	//获取有@Aspect注解的类
	Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
	validate(candidateAspectClass);

	//找到candidateAdviceMethod方法上面的注解,并且包装成AspectJAnnotation对象,这个对象中就有注解类型
	AspectJAnnotation<?> aspectJAnnotation =
			AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
	if (aspectJAnnotation == null) {
		return null;
	}

	// If we get here, we know we have an AspectJ method.
	// Check that it's an AspectJ-annotated class
	if (!isAspect(candidateAspectClass)) {
		throw new AopConfigException("Advice must be declared inside an aspect type: " +
				"Offending method '" + candidateAdviceMethod + "' in class [" +
				candidateAspectClass.getName() + "]");
	}

	if (logger.isDebugEnabled()) {
		logger.debug("Found AspectJ method: " + candidateAdviceMethod);
	}

	AbstractAspectJAdvice springAdvice;

	//根据不同的注解类型创建不同的advice类实例
	switch (aspectJAnnotation.getAnnotationType()) {
		case AtPointcut:
			if (logger.isDebugEnabled()) {
				logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
			}
			return null;
		case AtAround:
			//实现了MethodInterceptor接口
			springAdvice = new AspectJAroundAdvice(
					candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
			break;
		case AtBefore:
			//实现了MethodBeforeAdvice接口,没有实现MethodInterceptor接口
			springAdvice = new AspectJMethodBeforeAdvice(
					candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
			break;
		case AtAfter:
			//实现了MethodInterceptor接口
			springAdvice = new AspectJAfterAdvice(
					candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
			break;
		case AtAfterReturning:
			//实现了AfterReturningAdvice接口,没有实现MethodInterceptor接口
			springAdvice = new AspectJAfterReturningAdvice(
					candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
			AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
			if (StringUtils.hasText(afterReturningAnnotation.returning())) {
				springAdvice.setReturningName(afterReturningAnnotation.returning());
			}
			break;
		case AtAfterThrowing:
			//实现了MethodInterceptor接口
			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);
	}

	// Now to configure the advice...
	springAdvice.setAspectName(aspectName);
	springAdvice.setDeclarationOrder(declarationOrder);
	String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
	if (argNames != null) {
		springAdvice.setArgumentNamesFromStringArray(argNames);
	}

	//计算argNames和类型的对应关系
	springAdvice.calculateArgumentBindings();

	return springAdvice;
}

17. 回到第13步 ReflectiveAspectJAdvisorFactory#getAdvisor()

  • 通过构造方法InstantiationModelAwarePointcutAdvisorImpl得到了Pointcut和Advice

在这里插入图片描述
在这里插入图片描述

18. 回到第3步AbstractAutoProxyCreator#wrapIfNecessary()

  • 如果这个bean有advice的话,创建当前bean的代理

在这里插入图片描述

  • 接下来看代理对象是怎么创建的

在这里插入图片描述

19. AbstractAutoProxyCreator#createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource)

  • 工厂模式创建代理

在这里插入图片描述

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource) {

	if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
		AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
	}

	// 1.创建代理工厂
	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.copyFrom(this);
	// 2.proxyTargetClass这个默认就是false,false使用jdk动态代理,true使用cglib动态代理
	if (!proxyFactory.isProxyTargetClass()) {
		if (shouldProxyTargetClass(beanClass, beanName)) {
			//proxyTargetClass 是否对类进行代理,而不是对接口进行代理,设置为true时,使用CGLib代理。
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}

	// 3.把advice类型的增强包装成advisor切面(specificInterceptors是上一步找到的合格的切面)
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	// 4.把advisors(切面)和targetSource(被代理对象)赋值给代理工厂对象
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	customizeProxyFactory(proxyFactory);

	// 5.用来控制代理工厂被配置后,是否还允许修改代理的配置,默认为false
	proxyFactory.setFrozen(this.freezeProxy);
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}

	// 6.获取代理实例
	return proxyFactory.getProxy(getProxyClassLoader());
}

在这里插入图片描述

20. AbstractAutoProxyCreator#buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors)

  • 扩展interceptorNames属性

在这里插入图片描述
在这里插入图片描述

protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {
	// Handle prototypes correctly...
	//自定义MethodInterceptor.拿到AnnotationAwareAspectJAutoProxyCreator对象调用setInterceptorNames方法
	Advisor[] commonInterceptors = resolveInterceptorNames();

	List<Object> allInterceptors = new ArrayList<>();
	if (specificInterceptors != null) {
		allInterceptors.addAll(Arrays.asList(specificInterceptors));
		if (commonInterceptors.length > 0) {
			if (this.applyCommonInterceptorsFirst) {
				allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
			}
			else {
				allInterceptors.addAll(Arrays.asList(commonInterceptors));
			}
		}
	}
	if (logger.isTraceEnabled()) {
		int nrOfCommonInterceptors = commonInterceptors.length;
		int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
		logger.trace("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
				" common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
	}

	Advisor[] advisors = new Advisor[allInterceptors.size()];
	for (int i = 0; i < allInterceptors.size(); i++) {
		//对自定义的advice要进行包装,把advice包装成advisor对象,切面对象
		advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
	}
	return advisors;
}

21. AbstractAutoProxyCreator#resolveInterceptorNames()

  • 可以自己创建一个实现了MethodInterceptor接口的类,把类的beanName添加到InterceptorNames中,把这个类封装成beanDefiniton注册到spring容器,对应的名称是AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME,就可以做全局拦截了,文章最后演示

在这里插入图片描述
在这里插入图片描述

  • 提供了set方法

在这里插入图片描述
在这里插入图片描述

  • 创建一个实现了MethodInterceptor接口的类,把这个类包装成Advisor加入Advisor[]数组中

在这里插入图片描述

  • advice是传进来的,pointcut是写死的Pointcut.TRUE

在这里插入图片描述

在这里插入图片描述

  • TruePointcut,拦截所有的切面

在这里插入图片描述

22. 回到第20步AbstractAutoProxyCreator#buildAdvisors()方法,DefaultAdvisorAdapterRegistry#wrap(Object adviceObject)

在这里插入图片描述

  • 这里是advice类型的Object,强转返回

在这里插入图片描述

23. 回到第19步 AbstractAutoProxyCreator#createProxy(),ProxyFactory#getProxy(@Nullable ClassLoader classLoader)

  • 创建代理,根据目标对象是否有接口来判断采用什么代理方式,cglib代理还是jdk动态代理

在这里插入图片描述

  • 每个代理对象都是新的,与被代理对象一一对应

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

24. JdkDynamicAopProxy#getProxy(@Nullable ClassLoader classLoader)

  • 得到代理对象

在这里插入图片描述

  • 这个类肯定实现的InvocationHandler接口

在这里插入图片描述

25. JdkDynamicAopProxy#invoke(Object proxy, Method method, Object[] args)

  • 一定会有invoke()方法

在这里插入图片描述

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	MethodInvocation invocation;
	Object oldProxy = null;
	boolean setProxyContext = false;

	// 1.从代理工厂中拿到TargetSource对象,该对象包装了被代理实例bean
	TargetSource targetSource = this.advised.targetSource;
	Object target = null;

	try {
		// 2.被代理对象的equals方法和hashCode方法是不能被代理的,不会走切面
		if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
			// The target does not implement the equals(Object) method itself.
			return equals(args[0]);
		}
		else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
			// The target does not implement the hashCode() method itself.
			return hashCode();
		}
		else if (method.getDeclaringClass() == DecoratingProxy.class) {
			// There is only getDecoratedClass() declared -> dispatch to proxy config.
			return AopProxyUtils.ultimateTargetClass(this.advised);
		}
		else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
				method.getDeclaringClass().isAssignableFrom(Advised.class)) {
			// Service invocations on ProxyConfig with the proxy config...
			return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
		}

		Object retVal;
		// 3.exposeProxy属性默认是false,如果你设置为true,代理对象就会被设置到ThreadLocal中
		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		// Get as late as possible to minimize the time we "own" the target,
		// in case it comes from a pool.
		// 4.这个target就是被代理实例
		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);

		// Get the interception chain for this method.
		// 5.从代理工厂中拿过滤器链 Object是一个MethodInterceptor类型的对象,其实就是一个advice对象
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		// Check whether we have any advice. If we don't, we can fallback on direct
		// reflective invocation of the target, and avoid creating a MethodInvocation.

		// 6.如果该方法没有执行链,则说明这个方法不需要被拦截,则直接反射调用
		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
		else {
			// We need to create a method invocation...
			invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// Proceed to the joinpoint through the interceptor chain.
			// 7.如果有执行链,就链式调用
			retVal = invocation.proceed();
		}

		// Massage return value if necessary.
		Class<?> returnType = method.getReturnType();
		if (retVal != null && retVal == target &&
				returnType != Object.class && returnType.isInstance(proxy) &&
				!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
			// Special case: it returned "this" and the return type of the method
			// is type-compatible. Note that we can't help if the target sets
			// a reference to itself in another returned object.
			retVal = proxy;
		}
		else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
			throw new AopInvocationException(
					"Null return value from advice does not match primitive return type for: " + method);
		}
		return retVal;
	}
	finally {
		if (target != null && !targetSource.isStatic()) {
			// Must have come from TargetSource.
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			// Restore old proxy.
			AopContext.setCurrentProxy(oldProxy);
		}
	}
}

在这里插入图片描述

26. AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass)

  • 方法有缓存,如果缓存没有传进来的那个方法走getInterceptorsAndDynamicInterceptionAdvice()

在这里插入图片描述

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
	MethodCacheKey cacheKey = new MethodCacheKey(method);
	List<Object> cached = this.methodCache.get(cacheKey);
	if (cached == null) {
		//获取过滤器链
		cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
				this, method, targetClass);
		this.methodCache.put(cacheKey, cached);
	}
	return cached;
}

27. DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass)

在这里插入图片描述

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
		Advised config, Method method, @Nullable Class<?> targetClass) {

	// This is somewhat tricky... We have to process introductions first,
	// but we need to preserve order in the ultimate list.
	AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
	// 1.从代理工厂中获得该被代理类的所有切面advisor,config就是代理工厂对象ProxyFactory(可以看下面的断点结果)
	Advisor[] advisors = config.getAdvisors();
	List<Object> interceptorList = new ArrayList<>(advisors.length);
	Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
	Boolean hasIntroductions = null;

	for (Advisor advisor : advisors) {
		//大部分走这里
		if (advisor instanceof PointcutAdvisor) {
			// Add it conditionally.
			PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
			// 2.如果切面的pointCut和被代理对象是匹配的,说明是切面要拦截的对象
			if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
				MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
				boolean match;
				if (mm instanceof IntroductionAwareMethodMatcher) {
					if (hasIntroductions == null) {
						hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
					}
					match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
				}
				else {
					// 3.接下来判断方法是否是切面pointcut需要拦截的方法
					match = mm.matches(method, actualClass);
				}
				// 如果类和方法都匹配
				if (match) {

					// 4.获取到切面advisor中的advice,并且包装成MethodInterceptor类型的对象放到list中并返回
					MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
					if (mm.isRuntime()) {
						// Creating a new object instance in the getInterceptors() method
						// isn't a problem as we normally cache created chains.
						for (MethodInterceptor interceptor : interceptors) {
							interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
						}
					}
					else {
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		}
		//如果是引介切面
		else if (advisor instanceof IntroductionAdvisor) {
			IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
			if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}
		else {
			Interceptor[] interceptors = registry.getInterceptors(advisor);
			interceptorList.addAll(Arrays.asList(interceptors));
		}
	}
	// 5. 里面包含所有符合条件的advice增强
	return interceptorList;
}

在这里插入图片描述

28. DefaultAdvisorAdapterRegistry#getInterceptors(Advisor advisor)

  • 获取到切面advisor中的advice,并且包装成MethodInterceptor类型的对象

在这里插入图片描述
在这里插入图片描述

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
	List<MethodInterceptor> interceptors = new ArrayList<>(3);
	Advice advice = advisor.getAdvice();
	//如果是MethodInterceptor类型的,如:AspectJAroundAdvice
	//AspectJAfterAdvice
	//AspectJAfterThrowingAdvice
	if (advice instanceof MethodInterceptor) {
		interceptors.add((MethodInterceptor) advice);
	}

	//处理 AspectJMethodBeforeAdvice  AspectJAfterReturningAdvice
	//因为这两个不是MethodInterceptor类型的advice,所以这里就把他俩包装成MethodInterceptor类型的advice
	for (AdvisorAdapter adapter : this.adapters) {
		if (adapter.supportsAdvice(advice)) {
			interceptors.add(adapter.getInterceptor(advisor));
		}
	}
	if (interceptors.isEmpty()) {
		throw new UnknownAdviceTypeException(advisor.getAdvice());
	}
	return interceptors.toArray(new MethodInterceptor[0]);
}
  • 把MethodBeforeAdviceAdapter和AfterReturningAdviceAdapter放入adapters集合中

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 包装成MethodInterceptor类型的对象返回

在这里插入图片描述

  • AfterReturningAdviceInterceptor实现了MethodInterceptor接口,并持有AfterReturningAdvice对象的引用

在这里插入图片描述
在这里插入图片描述

29. 回到第25步 JdkDynamicAopProxy#invoke(),ReflectiveMethodInvocation#ReflectiveMethodInvocation()

  • 通过构造函数把得到所有advice增强方法放入interceptorsAndDynamicMethodMatchers集合,也就是执行链

在这里插入图片描述
在这里插入图片描述

30. ReflectiveMethodInvocation#proceed()

  • 链式调用,功能增强的具体实现

在这里插入图片描述
在这里插入图片描述

public Object proceed() throws Throwable {
	//	We start with an index of -1 and increment early.
	// 1.如果执行链中的advice全部执行完,则直接调用joinPoint方法,就是被代理方法
	//currentInterceptorIndex默认是-1,没执行一次就+1,直到把所有增强方法都执行完,下面的两个值就相等了
	//如果相等,则执行被代理对象的被代理方法
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		// 2.反射调用被代理方法
		return invokeJoinpoint();
	}
	// 3.在集合中按顺序(第0个,1个,2个...)执行advice的增强方法(例如around,before,after这些具体方法)
	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		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 {
			// Dynamic matching failed.
			// Skip this interceptor and invoke the next in the chain.
			return proceed();
		}
	}
	else {
		// It's an interceptor, so we just invoke it: The pointcut will have
		// been evaluated statically before this object was constructed.

		// 4.调用MethodInterceptor中的invoke方法
		// 这里就体现出把所有的advice都包装成MethodInterceptor对象的好处,即不需要写大量的if,else去判断advice的类型
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
}

31. AspectJAroundAdvice#invoke(MethodInvocation mi)

  • 比如这里是around方法的增强方法,点进AspectJAroundAdvice类的invoke()方法

在这里插入图片描述
在这里插入图片描述

  • AbstractAspectJAdvice#invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,@Nullable Object returnValue, @Nullable Throwable t)

在这里插入图片描述

  • invokeAdviceMethodWithGivenArgs(Object[] args)

在这里插入图片描述

  • jdk方法的反射调用

在这里插入图片描述

  • 一定会走到增强的具体方法,又会走到proceed()方法

在这里插入图片描述
在这里插入图片描述

  • MethodInvocationProceedingJoinPoint#proceed()

在这里插入图片描述

  • ReflectiveMethodInvocation#proceed(),回到第30步链式调用最开始的地方

在这里插入图片描述

32. AspectJAroundAdvice#invoke(MethodInvocation mi)

  • 再比如这里是before方法的增强方法,点进MethodBeforeAdviceInterceptor类的invoke()方法

在这里插入图片描述

  • 先执行具体增强方法,AspectJMethodBeforeAdvice#before(Method method, Object[] args, @Nullable Object target)

在这里插入图片描述
在这里插入图片描述

  • AbstractAspectJAdvice#invokeAdviceMethod(@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)

在这里插入图片描述

  • AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs(Object[] args)

在这里插入图片描述

  • jdk方法反射调用

在这里插入图片描述

  • 回到第32步一开始,具体增强方法执行完,执行proceed()方法

在这里插入图片描述

  • 又回到了第30步链式调用最开始的地方

在这里插入图片描述

添加一个全局AOP

1. 创建一个实现MethodInterceptor接口的类

  • 添加全局拦截方法

在这里插入图片描述

package org.example.aspectJ;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;

@Component
public class WholeMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("我是全局拦截AOP,拦截了" + invocation.getMethod().getName() + "方法");
        return invocation.proceed();
    }

}

2. 创建一个配置类

  • 用于把入口类AnnotationAwareAspectJAutoProxyCreator注册到spring容器中,对应的beanName必须是AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME(这个在上一篇文章中https://blog.csdn.net/qq_40977118/article/details/108937745介绍过了),把拦截类的beanName设置到InterceptorNames中

在这里插入图片描述
在这里插入图片描述

package org.example.aspectJ;

import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Configuration
public class TargetSourceCreatorBean {

    // beanDefinition中的factoryMethod属性实现的
    @Bean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)
    public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
        AnnotationAwareAspectJAutoProxyCreator creator = new AnnotationAwareAspectJAutoProxyCreator();
        creator.setInterceptorNames("wholeMethodInterceptor");
        return creator;
    }
}

3. 测试方法

  • 这里的userService对象是一个代理对象,所以可以拦截

在这里插入图片描述

  • 这里想要全局拦截,一定要在bean生成代理前,把这个拦截类添加到InterceptorNames中
  • 源码中是先遍历InterceptorNames中的beanName之后,才生成的代理,如果代理已经生成,你再去向InterceptorNames中添加beanName就不起作用了

4. 情况一

  • 注释掉@Configuration

在这里插入图片描述

  • 全局拦截方法没有走

在这里插入图片描述

  • 这里是因为在执行bean.setInterceptorNames(“wholeMethodInterceptor”)之前,UserService已经被spring实例化,而且代理对象已经被创建完了

解决方法:

  • 添加@Lazy

在这里插入图片描述

  • 全局拦截方法被执行了,因为是懒加载,在手动调用applicationContext.getBean(UserService.class)方法之后,UserService才会被实例化,这时InterceptorNames中已经添加了wholeMethodInterceptor的beanName,所以在UserService生成代理之前,会添加这个TruePointcut

在这里插入图片描述

5. 情况二

  • getBean先执行,同样是因为UserService在遍历InterceptorNames前先生成了代理对象,所以全局拦截方法没有执行

在这里插入图片描述

总结

initializeBean

1、检查当前的bean是否有advice

  • 找到合格的切面,把工程中所有有@Aspect的类封装成Advisor返回,包含pointCut和advice
  • 判断这些切面是否作用在当前bean的类上面,就是一个匹配过程

2、如果有就创建当前bean的代理

  • 创建代理工厂,默认使用jdk动态代理
  • 把advisors(切面)和targetSource(被代理对象)赋值给代理工厂对象
  • 通过代理工厂获取代理实例,每个代理对象都是新的,被new出来的,而且实现了InvocationHandler接口
  • 在代理实例的invoke方法中,从代理工厂中拿到TargetSource对象,该对象包装了被代理实例bean
  • 被代理对象的equals方法和hashCode方法是不能被代理的,不会走切面
  • 从代理工厂中拿到被代理对象的所有切面advisor,获取到匹配的advice,并且包装成MethodInterceptor类型的对象放到list中并返回,进行链式调用invocation.proceed()
  • 执行advice的增强方法,调用MethodInterceptor中的invoke方法,会走到增强的具体方法,如果是around类型的增强方法又会走到point.proceed()方法,和invocation.proceed()调用的是同一个类ReflectiveMethodInvocation中的方法,形成链式调用
  • 每次执行会对次数进行加1,直到等于执行链中方法数量-1(执行次数初始值是-1),执行链中的advice全部执行完,则直接调用被代理方法,为getBean方法返回一个代理对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值