Spring源码学习篇之AOP-1

Spring AOP源码解读 aop 是什么 AOP (Aspect-Oriented Programming,面向切面编程),可以说是 OOP 的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP 则显得无能为力。OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布 阅读详情

开启AOP相关注解,xml中配置如下:

<aop:aspectj-autoproxy />

1、AOP 相关组件注册

在这里插入图片描述
spring容器启动时,将会解析配置文件中的指令节点逐一解析,配置了aspectj-autoproxy指令后,在refresh()中的obtainFreshBeanFactory()阶段解析XML配置时,当解析到该指令时,触发AspectJAutoProxyBeanDefinitionParser解析器对其进行解析。

主要由AopNamespaceUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary()注册AOP相关注解组件并通知,便于监听器进一步处理。

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
		ParserContext parserContext, Element sourceElement) {
	// 注册或升级AutoProxyCreator定义beanName为
	BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			parserContext.getRegistry(), parserContext.extractSource(sourceElement));

	//对于proxy-target-class以及expose-proxy属性的处理
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);

	//注册组件并通知,便于监听器进一步处理
	//其中beanDefinition的className为AnnotationAwareAspectJAutoProxyCreator
	registerComponentIfNecessary(beanDefinition, parserContext);
}

2、AOP 创建代理

在上文组件注册章节中已经完成了AnnotationAwareAspectJAutoProxyCreator.class类型的bean注册。在其类层次结构中看出实现了BeanPostProcessor接口,即等于是bean的增强器。
在这里插入图片描述
所以我们只需关注bean初始化前后的两个增强逻辑(postProcessBeforeInitialization,postProcessAfterInitialization)即可。

2.1、postProcessBeforeInstantiation方法:

在该方法中,首先判断是否被处理,若被处理,则判断设置是否代理,紧接着判断是否基础设施类或者是否需要跳过该bean,而跳过逻辑中:根据切点增强方法中的切面名称是否和该bean name一致,一致则跳过。

若未被处理,则根据自定义bean creator创建bean,最后根据切面中方法判断是否要创建该bean代理。

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
	Object cacheKey = getCacheKey(beanClass, beanName);
	// 如果已经被处理过
	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (this.advisedBeans.containsKey(cacheKey)) {
			return null;
		}
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}

	// Create proxy here if we have a custom TargetSource.
	// Suppresses unnecessary default instantiation of the target bean:
	// The TargetSource will handle target instances in a custom fashion.
	TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
	if (targetSource != null) {
		if (StringUtils.hasLength(beanName)) {
			this.targetSourcedBeans.add(beanName);
		}
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
		Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	return null;
}

注意:跳过判断方法中解析了切面中的方法,并添加缓存。下方贴出shouldSkip()代码:

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
	// TODO: Consider optimization by caching the list of the aspect names
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	for (Advisor advisor : candidateAdvisors) {
		//若和切面名称一致,则跳过,无需创建代理类
		if (advisor instanceof AspectJPointcutAdvisor &&
				((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
			return true;
		}
	}
	return super.shouldSkip(beanClass, beanName);
}
protected List<Advisor> findCandidateAdvisors() {
	// Add all the Spring advisors found according to superclass rules.
	// 当时用注解方式配置AOP时,并不是丢弃了对XML的支持
	// 在这里调用父类方法加载配置中的AOP声明
	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;
}
public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;
	//double check lock
	if (aspectNames == null) {
		synchronized (this) {
			aspectNames = this.aspectBeanNames;
			if (aspectNames == null) {
				List<Advisor> advisors = new ArrayList<>();
				aspectNames = new ArrayList<>();
				//获取所有的bean names
				String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						this.beanFactory, Object.class, true, false);
				for (String beanName : beanNames) {
					Class<?> beanType = this.beanFactory.getType(beanName);
					// 如果存在aspect注解
					if (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);
							// 解析标记AspectJ注解中的 增强方法
							List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
							advisors.addAll(classAdvisors);
						}
					}
				}
				this.aspectBeanNames = aspectNames;
				return advisors;
			}
		}
	}
	return advisors;
}
/**
 * 解析 标记AspectJ注解bean中的  增强方法
 */
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
	// 获取标记AspectJ的类
	Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
	// 获取标记AspectJ的切面名称
	String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
	List<Advisor> advisors = new ArrayList<>();
	// getAdvisorMethods(aspectClass) 获取所有切面中非@PiontCut的方法
	for (Method method : getAdvisorMethods(aspectClass)) {
		/**
		 * 1、获取普通增强器的逻辑
		 * 		1.1、对切点的注解的获取
		 * 		1.2、根据不同的注解信息生成不同的增强(before、around、after...)
		 */
		Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}
	return advisors;
}

总之:在bean初始化前的增强逻辑中,就会把所有的切面及切面内的增强方法全部解析完成,并加入缓存LIST中,为后续的bean后置增强中创建代理提供支持。

2.2 postProcessAfterInitialization方法

bean的后置增强,则是在初始化bean属性后,如果需要代理,则返回代理后的bean。

🎈在这之前先介绍实例化的初始化(initializeBean)阶段,在此阶段将执行bean前置和后置逻辑增强。代码如下:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			// 前置增强
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}
		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			// 后置增强——在此处生成代理类如果需要的话
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}
		return wrappedBean;
	}

后置增强方法

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			// 如果适合被代理,则需要封装指定bean
			return 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;
	}
	// 无需增强的bean
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	// 基础设施类无需被代理,或者配置了指定的bean,不需要自动代理
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	// 如果存在需要增强的方法,则需要针对其创建代理
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		// 具体创建代理
		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;
}

针对不同的模式选择是JDK代理还是CGLIB代理。

该类是否继承接口,存在接口继承则使用JDK代理(生成接口的代理实现类),否则使用CDLIB进行代理(生成完成继承该类所有非final方法的兄弟类)。

注意:若是当前bean不存在实现接口,则直接采用CGLIB生成代理。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
		Class<?> targetClass = config.getTargetClass();
		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}

后续学习深入后再进行补充!!!

Spring源码——AOP 前言 内容主要参考自《Spring源码深度解析》一书,算是读书笔记或是原书的补充。进入正文后可能会引来各种不适,毕竟阅读源码是件极其痛苦的事情。 本文主要涉及书中第七章的部分,依照书中内容以及个人理解对Spring源码进行了注释,详见Github仓库:https://github.com/MrSorrow/spring-framework 正式进入正文前,强烈推荐先阅读文末参考阅读第一篇:关于 S... 阅读详情

相关推荐

Spring之aop源码解析

aop就是面向切面编程,是为了解耦而生。aop是不仅工作常用而且面试也经常问,所以看aop源码是非常有必要的。在看源码之前,我们先要带着结论去看源码,而不是一上来看的一脸懵逼,一定要先总后分。aop的流程就是在spring的生命周期过程中,会缓存所有的切面,然后在bean初始化的时候,它会找当前bean的所有方法是否跟缓存中的切面有匹配的,如果匹配,说明需要代理并创建代理对象,当调用被增强方法时,执行代理逻辑。本文基于springboot2.3.5.RELEASE,如果有错,欢迎指出。

weixin_53067547的博客 1175

Spring中AOP相关的API及源码解析,原来AOP是这样子的

Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗? 这篇文章,我们来谈一谈Spring中的属性注入 推荐阅读: Spring官网阅读 | 总结篇 Spring杂谈 本系列文章将会带你一行行的将Spring的源码吃透,推荐阅读的文章是阅读源码的基础! 因为本文会涉及到动态代理的相关内容,如果对动态代理不

做一个认真的程序员 1万+

Spring的aop源码分析

Spring的aop源码解析

qq_43418737的博客 1668

Spring AOP 源码解读

name: spring-aop-source title: Spring AOP 源码解析 date: 2020-03-25 15:03:38 tags: spring-aop-source categories: 文章目录前言本文使用的调试代码IOC 容器管理 AOP 实例ProxyFactory 详解基于注解的 Spring AOP 源码分析闲聊 InstantiationAwareBeanPostProcessor小结 之前写过 IOC 的源码分析,那篇文章真的有点长,看完需要点耐心。很多读者希.

孤刺 4074

第二十四章 Spring之源码阅读——AOP篇

对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了所以决定花点时间研究研究Spring的源码。《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》前边A君把AOP集成到了IOC容器中,Spring中最基础,也是最重要的两部分内容都整完了。不知道对大家是否有所帮助?不过,这AOP部分,A君换了个写法,之前在写IOC篇章的时候,A君。

一个卑微的java开发 1384

vue3 web项目引入高拍仪

高拍仪的引入步骤就两步,安装SDK,然后根据官网的api进行开发。根据自己需要的功能,请求对应的接口。

qq_42480170的博客 4116

Spring源码之AOP实现

Spring源码之AOP

qq_38706652的博客 2784

【ES】--Elasticsearch的分词器深度研究

分词器是ik_max_word,会将“龙马家书”拆分为“龙马”、“马家”、“家书”。然后filter配置的是edge_ngram_filter,在上面拆分的token词组基础上,按照edge_ngram_filter规则进一步拆分。从结果看:分词器是keyword,filter支持edge_ngram_filter,字符过滤器支持tsconvert【简繁体】。分词器是keyword,表明是不进行分词,但filtet中edge_ngram_filter是按照布长方式拆分token,所以得到如此结果。

寻梦友的博客 2425

Spring AOP源码详细解析

一 . 以下是bean的初始化过程: 注意: 先明白beanPostProcessor的作用,才能够明白aop的加载时机 在bean实例化完成之前和完成之后分别会自动BeanPostProcessor接口的postProcessBeforeInitialization和postProcessAfterInitialization方法。 二: 介绍一些Spring Aop中一些核心类,大致分为三类: advisorCreator,继承 spring ioc的扩展接口 beanPos...

pengjianglilive的专栏 3678

电商智能客服Agent开发实战:从零到一构建企业级AI助手

电商智能客服Agent是下一代AI应用的核心形态,它不仅能够回答用户问题,还能自主规划任务、调用工具并执行完整工作流程。本文将深入探讨AI Agent的基本概念、技术实现方式及电商场景中的应用,提供完整的开发流程和代码示例,帮助开发者从零开始构建一个功能强大的电商智能客服系统。通过模块化设计、多轮对话记忆管理和工具集成,这个Agent能够处理订单查询、商品推荐、退换货流程等复杂任务,同时具备企业级的安全性和稳定性保障。通过本文的介绍,读者已经掌握了构建电商智能客服Agent的核心技术与实现方法。

m0_64363449的博客 1361

IP路由原理、静态路由及动态路由区分

什么是路由 路由是指导IP报文发送的路径信息。 直连路由:目的地所在网络与路由器直接相连。 间接路由:目的地所在网络与路由器非直接相连。 静态路由是由管理员手工配置而生成的路由。 动态路由路由器使用路由协议从其他路由器那里获悉的路由。

weixin_42227328的博客 5469

Spring 源码学习资料 从手写到源码深度剖析

通过这套 Spring 源码学习资料,大家可以从基础到高级,系统地学习 Spring 框架的核心技术和实现原理。无论是手写代码还是深入源码,这些资料都将帮助大家更好地理解和使用 Spring 框架,提升开发效率和代码质量。如果你对 Spring 源码感兴趣,或者想深入了解 Spring 框架的实现原理,这套资料绝对值得一学!希望这些资料能够帮助你在 Spring 学习的道路上更进一步。

运维老生常谈 3443

weblogic 集群搭建,双机部署,相关问题记录

4.5.集群搭建1 参考网址https://wenku.baidu.com/view/76365129cd1755270722192e453610661ed95aa4.html IP地址 端口 角色 192.168.1.160 9001 AdminServer ...

zl386119974的专栏 1669

从源码解读Spring的AOP

概念 IOC和AOP作为Spring的两大核心,如果你接触过Spring框架,那就一定需要了解这两个核心部分的工作原理。IOC控制反转(或者说依赖注入)的部分已经在我的从源码解读Spring的IOC文章中有介绍,这篇文章主要来讲解AOP面向切面编程的原理 AOP,即Aspect Oriented Programming,面向切面编程,关于什么是面向切面编程,为了避免喧宾夺主,我在这里不进行过多的探...

Ekmr的博客 4504

用 DCL 编写 DEFINE ROLE:把 ABAP CDS 的访问控制做对、做实、做漂亮

摘要: 本文详细讲解如何在ABAP CDS中使用DCL(数据控制语言)实现行级授权,确保业务数据安全。核心要点包括: DCL原理:通过DEFINE ROLE将访问条件绑定到CDS实体,自动附加到查询条件(类似隐式WHERE),保障授权一致性。 关键语法: 必需注解@MappingRole: true声明映射角色。 ASPECT pfcg_auth关联PFCG权限对象,动态控制访问。 注解@AccessControl.authorizationCheck定义授权检查策略(如#CHECK启用强制控制)。 优先级

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 165

【FISCO BCOS】二十、多机部署区块链

实际开发中,为了满足高可用性和高容错性的需求,往往会采用多级部署区块链的方式,今天介绍一下FISCO BCOS的多机部署流程。

naihe_fish的博客 3651

Spring AOP源码分析

目录 1、AOP是什么 2、AOP 的作用及其优势 3、AOP 的底层实现 3.1 JDK动态代理: 3.2 CGLIB动态代理 4、AOP相关概念 5、AOP运行流程图 6、AOP源码解析 6.1 源码解析须知 6.2 注册自动代理创建器 6.3 触发后置处理器 6.4 创建Proxy过程分析 6.5 代理回调增强 6.6 增强器执行 6.7 增强器执行顺序 7、源码总结 1、AOP是什么 AOP 为 Aspect Oriented Programming ..

长沙老码农 2446

从零开始:C#上位机集成YOLO完成摄像头目标追踪(保姆级教程)

新手入门C#上位机+YOLO做摄像头目标追踪,最忌讳的是“只抄代码,不懂逻辑”。这个教程不仅给了完整代码,还把核心原理和踩坑点都讲清楚了,新手跟着走,不仅能跑通demo,还能理解为什么这么写。如果你们在运行时遇到具体问题,比如模型导出失败、代码报错,都可以在评论区留言,我会一一解答。后续也可以出一期工业相机+YOLO目标追踪的教程,帮助大家把demo落地到实际生产中。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1030

【Spring AOP 源码解析】彻底搞懂 AOP 是如何执行的

Spring 在真正的通过反射去执行实际调用的方法之前,会有一个名为 **`intercept()`** 的方法,执行一些拦截的逻辑;我们构造的这些切面方法实际上就是在这部分中执行的;Spring 在创建 Bean 的时候会根据 AOP 注解生成一个由一组 interceptor 组成的链式结构,然后在调用实际方法之前,通过循环的方式按照顺序去执行这些 interceptor 中的代码,这样就在合适的时机去执行了合适的代码。

kkq的博客 1365

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【spring源码】AOP底层源码分析

【spring源码】AOP底层源码分析1.测试用例2.AOP流程概述3.AOP源码分析 spring的AOP是什么不在赘述,以下内容默认大家会使用AOP,我们今天来扒源码 1.测试用例 引入必要依赖: <dependencies> <dependency> <groupId>org.springframework&l...

aaa_bbb_ccc_123_456的博客 1万+

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Spring AOP源码学习:基本概念

前言 之前用十几篇文章介绍了SpringIoC的源码,作为与IoC齐名的 AOP自然也不能错过。同样的,接下去将会通过几篇文章来解析Spring AOP的源码。 如何将Spring源码导入 IDEA,请参考:Spring IoC:源码学习总览 注:本文的内容以 AspectJ来进行介绍。 关于 AOP 百度百科:AOP即Aspect Oriented...

程序员囧辉 4710

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Spring AOP 源码解析

之前写过 IOC 的源码分析,那篇文章真的有点长,看完需要点耐心。很多读者希望能写一写 Spring AOP 的源码分析文章,这样读者看完 IOC + AOP 也就对 Spring 会有比较深的理解了。今天终于成文了,可能很多读者早就不再等待了,不过主要为了后来者吧。 本文不会像 IOC 源码分析那篇文章一样,很具体地分析每一行 Spring AOP 的源码,目标读者是已经知道 Spring IOC 源码是怎么回事的读者,因为 Spring AOP 终归是依赖于 IOC 容器来管理的。 阅读建议:1、先

赶路人儿 1830

10. Spring AOP源码解析

10 Spring AOP源码解析 目录 10 Spring AOP源码解析 Pt1 Spring AOP基础知识 Pt1.1 AOP核心概念 Pt1.2 AOP切点配置规则 (1) execution (2) within (3) args Pt2 Spring AOP使用示例 Pt2.1 AOP日志(XML) pom.xml spring.xml配置AOP信息 加载spring.xml 定义切面 定义Controller 运行Controller Pt2.2 JDBC(

Java是世界上最好的语言 1484

Spring Aop初始化源码分析

1 工程简介 1.1 pom <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.tar

qq_34125999的博客 4613

Spring AOP源码学习:AOP 注解的解析

前言 上文介绍了 AOP的基本概念,本文开始进入 AOP的源码解析,本文仍以 AspectJ来进行介绍,首先是 AOP注解的解析。 正文 当使用<aop:aspectj-autoproxy />注解开启 AOP功能时。 Spring会从“META-INF/spring.handlers” 配置文件中拿到该注解对应的 NamespaceHandlerSuppo...

程序员囧辉 6702
上一篇: IDEA之Translation翻译插件-TKK: 更新 TKK 失败
下一篇: Springboot源码解析第一篇:自动装配原理
Musclewl
博客等级 码龄9年 215粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值