JAVA面试(Spring+Spring MVC+Spring Boot+Mybatis+Spring Cloud)


Spring Boot、Spring MVC、Spring的区别

  1. spring是一个IOC容器,用来管理Bean,使用依赖注A实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补00P的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志异常等
  2. springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(ur|到handle的映射) 及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端.
  3. springboot是spring提供的一个快速开发I胞,让程序员能更方便、更快速的开发spring+springmvc应用, 简化了配置(约定了默认配置), 整合了一系列的解决方案(starter机制) 、redis、 mongodb、 es, 可以开箱即用.

IO流
在这里插入图片描述

JVM 加载class 文件的原理机制
● JVM 中类的装载是由ClassLoader 和它的子类来实现的,Java ClassLoader是一个重要的Java 运行时系统组件。它负责在运行时查找和装入类文件的类。

集合
● List: 序,按对象进入的顺序保存对象,可重复,允许多个Null元索对象,可以使用lterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下表的元索

● Set: 无序,不可重复,最多允许有一个Null元素对象,取元素时只能用lterator接口取得所有元素,在逐一遍历各个元素方式。

● ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问) , 扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素), 使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量的node对象)

● LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历,遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get()取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexIOf对list进行 了遍历,当结果为空时会遍历整个列表。

● HashMap:线程不安全,HashMap允许Key和Value为null,底层采用数组加链表结构实现.
jdk8开始,链表高度到8并且数组长度超过64的时候,链表会转变成红黑树,元素以内部类Node节点存在.

● HashTable:使用了synchronized修饰,线程安全,不允许null值出现

1. 什么是Spring?

Spring是一个开源的轻量级的Java EE开发框架.Spring框架的目标是使得Java EE应用程序的开发更加简便,通过使用POJO为基础的编程模型促进良好的编程风格.

2. 什么是控制反转(IOC)?

1. 什么是Spring IOC容器?

Spring框架的核心是Spring容器.容器创建对象,将他们装配在一起,配置他们并管理他们的完整生命周期.Spring使用依赖注入来管理组成应用程序的组件.容器通过读取提供的配置元数据来接收对象进行实例化.

2.什么是依赖注入(DI)?

在依赖注入中,您不必创建对象,但必须描述如何创建他们.不是直接在代码中将组件和服务连接在一起,而是描述配置文件中那些组件需要那些服务,然后由IOC容器将他们装配在一起.

3. 依赖注入的方式:

1. 构造器注入:

public class StupidStudent {
    private SmartStudent smartStudent;
    
    public StupidStudent(SmartStudent smartStudent) {
        this.smartStudent = smartStudent;
    }
    
    public doHomewrok() {
        smartStudent.doHomework();
        System.out.println("学渣抄作业");
    }
}
public class StudentTest {
    public static void main(String[] args) {
        SmartStudent smartStudent = new SmartStudent();
        StupidStudent stupidStudent = new StupidStudent(smartStudent);
        stupidStudent.doHomework();
    }
}

这种方式就好比学渣从一开始就赖上了一个学霸,并且和这个学霸建立了长期合作关系.

2. Setter注入:

public class StupidStudent {
    private SmartStudent smartStudent;
    
    public void setSmartStudent(SmartStudent smartStudent) {
       this.smartStudent = smartStudent;
    }
    
    public doHomewrok() {
        smartStudent.doHomework();
        System.out.println("学渣抄作业");
    }
}
public class StudentTest {
    public static void main(String[] args) {
        SmartStudent smartStudent = new SmartStudent();
        StupidStudent stupidStudent = new StupidStudent();
        stupidStudent.setSmartStudent(smartStudent);
        stupidStudent.doHomework();
    }
}

这种方式学霸和学渣只是暂时的合作关系,如果学渣赖上了另一个学霸(调用set()方法传入了另一个对象),那么学渣和学霸的合作关系就结束了。

3. 注解注入:

注解注入在Spring中是用的最多的一种方式,就是在java代码中使用注解的方式进行装配,在代码中加入@Resource或者@Autowired.

  1. Autowired是自动注入,自动从spring的上下文找到合适的bean来注入.

  2. Resource用来指定名称注入.

  3. Qualifier和Autowired配合使用,指定bean的名称.(Resource注解传递指定Bean名称相当于Autowired+Qualifier注解)

  • 上面的Autowired和Resource是用来修饰字段,构造函数,或者设置方法,并做注入的。而Service,Controller,Repository,Component则是用来修饰类,标记这些类要生成bean。

  • 总结:构造器注入适用于设置多个属性,任意修改都会创建新的实例;Setter注入适用于设置少量属性,任意更改都不会创建新实例.

4. Spring中有多少种IOC容器?

1.BeanFactory

基础类型IOC容器,提供了完整的IOC服务支持.默认采用延迟初始化策略(lazy-load).只有当客户端需要访问到容器中某一个受管对象的时候,才会对受管对象进行初始化以及依赖注入操作,所以相对来说,容器启动初期速度较快,所需要的资源有限.

2. ApplicationContext

这是BeanFactory的升级版,拥有了BeanFactory的所有支持,ApplicationContext还提供了其他的高级特性,比如事件发布、国际化信息支持等.
ApplicationContext默认初始化全部容器对象(及时加载),需要更多的系统资源,启动时间较长.

5. IOC的好处:

  1. 减少了开发中的代码量.
  2. 松耦合,以最小的影响和最少的侵入机制促进松耦合
  3. 支持即时加载的实例化和延时加载服务
  4. 使应用程序易于测试,因为他不需要单元测试用例中的任何单元或JNDI查找机制.

3.Beans

1.什么是Spring Bean?

  1. 他们是构成用户应用程序主干的对象
  2. Bean是由Spring Ioc容器管理的.
  3. 由Sping IoC容器实例化,配置装配和管理.
  4. Bean是基于用户提供给容器的配置元数据创建.

2. 生命周期

  1. 解析类,扫描类,从类中获取到BeanDefinition(定义一个Bean对象)
  2. 如果有多个构造方法,则开始推断构造方法
  3. 确定好构造方法后,进行实例化得到一个对象
  4. 对对象中加了@Autowired注解的属性进行属性填充
  5. 调用初始化前方法,初始化方法以及初始化后方法.
  6. 将创建的bean单例放入单例池.
  7. 使用bean
  8. 调用DisposableBean中的destory()销毁bean.

3. 作用域

  1. singleton: 默认,单例,生命周期与IoC容器一致(第一次注入时才会创建).
  2. prototype: 原型模式,每次注入都会创建一个新对象
  3. request: 每个HTTP请求中创建一个单例对象,单个请求中复用该对象.
  4. session: 每一个会话中创建一个单例对象,会话结束就销毁.
  5. application: ServletContext的双眸周期中复用的一个单例对象.

4. Spring提供了那些配置方式?

  1. 基于XML配置

bean所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多bean定义和特定于应用程序的配置选项。它们通常以bean标签开头。例如:

<bean id="studentbean" class="org.edureka.firstSpring.StudentBean">
 <property name="name" value="Edureka"></property>
 </bean>

  1. 基于注解配置

您可以通过在相关的类,方法或字段声明上使用注解,将bean配置为组件类本身,而不是使用XML来描述bean装配。默认情况下,Spring容器中未打开注解装配。因此,您需要在使用它之前在Spring配置文件中启用它。例如:

<beans> 
<context:annotation-config/> 
<!-- bean definitions go here -->
 </beans>

  1. 基于Java API配置

Spring 的 Java配置是通过使用@Bean和@Configuration 来实现。

  1. @Bean注解扮演与元素相同的角色。

  2. @Configuration类允许通过简单地调用同一个类中的其他@Bean方法来定义bean间依赖关系。

@Configuration public class StudentConfig {
	@Bean public StudentBean myStudent() {
		return new StudentBean();
	}
}

5.AOP

以动态非侵入式的方式增强服务的一种编程思想
AOP(Aspect Oriented Programming,面向切面编程),可以说是 OOP(Object Oriented Programing,面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用来模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如权限管理、异常处理等也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而 AOP 技术则恰恰相反,它利用一种称为 “横切” 的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect” ,即切面。所谓“切面”,简单地说,就是将权限、事务、日志、异常等与业务逻辑相对独立的功能抽取封装,便于减少系统的重复代码,降低模块间的耦合度,增加代码的可维护性。AOP 代表的是一个横向的关系,如果说 “对象” 是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向切面编程,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息,然后又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

切面理解:用刀将西瓜分成两瓣,切开的切口就是切面;炒菜、锅与炉子共同来完成炒菜,锅与炉子就是切面。Web 层级设计中,Controller 层、Service 层、Dao 层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

  1. Advice(通知)
    前置通知(Before advice):在目标方法调用前执行通知
    环绕通知(Around advice):在目标方法调用前后均可执行自定义逻辑
    返回通知(After returning advice):在目标方法执行成功后,调用通知
    异常通知(After throwing advice):在目标方法抛出异常后,执行通知
    后置通知(After advice):在目标方法完成(不管是抛出异常还是执行成功)后执行通知
  2. JoinPoint(连接点)
    就是 Spring 允许你放通知(Advice)的地方,很多,基本每个方法的前、后(两者都有也行)或抛出异常时都可以是连接点,Spring 只支持方法连接点,和方法有关的前前后后都是连接点。

Tips:可以使用连接点获取执行的类名、方法名和参数名等。

  1. Pointcut(切入点)
    是在连接点的基础上来定义切入点。比如在一个类中,有 15 个方法,那么就会有几十个连接点,但只想让其中几个方法的前后或抛出异常时干点什么,那么就用切入点来定义这几个方法,让切入点来筛选连接点。

  2. Aspect(切面)
    是通知(Advice)和切入点(Pointcut)的结合,通知(Advice)说明了干什么和什么时候(通过@Before、@Around、@After、@AfterReturning、@AfterThrowing来定义执行时间点)干,切入点(Pointcut)说明了在哪(指定方法)干,这就是一个完整的切面定义。

  3. AOP 代理
    AOP Proxy:AOP 框架创建的对象,代理就是目标对象的加强。AOP 巧妙的例用动态代理优雅的解决了 OOP 力所不及的问题。Spring 中的 AOP 代理可以是 jdk 动态代理,也可以是 cglib 动态代理。前者基于接口,后者基于子类。

6.Spring中的设计模式

  1. 简单工厂:根据工厂类传入的参数,动态决定应该创建哪一个产品类.
    Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这不要根据具体情况来定。
  2. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点.
    spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory.但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
  3. 适配器模式:
    Spring定义了一个适配接口,使得每一种Controller有一 种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。
  4. 动态代理: AOP
  5. 策略模式.

6.SpringMVC工作流程

1)用户发送请求至前端控制器DispatcherServlet.
2) DispatcherServlet 收到请求调用HandlerMapping处理器映射器。
3)处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生
成)-并返回给DispatcherServlet。
4) DispatcherServlet 调用HandlerAdapter处理器适配器。
5) HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)
6) Controller 执行完成返回ModelAndView。
7) HandlerAdapter 将controller执行结果ModelAndView返回给DispatcherServlet。8) DispatcherServlet
将ModelAndView传给ViewReslover视图解析器。
9) ViewReslover 解析后返回具体View。
10) DispatcherServlet 根据View进行渲染视图(即将模型数据填充至视图中)。
11) DispatcherServlet 响应用户。

7. Spring Boot自动装配原理

@Import + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META-INF/spring.factories下使用Spring spi扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
在这里插入图片描述

8.Mybatis

1. Mybatis优点:

1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理; 提供XML标签,支持编写动态SQL语询,并可重用。
2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
3、很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要DBC支持的数据库MyBatis都支持)。
4、能够与Spring很好的集成;
5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点:
1、SQL语句的编写.工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语询的功底有一定要求。
2、SQL 语句依赖于数据库,导致数据库移植性差 ,不能随意更换数据库。

2. Mybatis与Hibernate对比

hibernate简化了dao层,使用者不需要考虑sql语句的编写与执行以及结果映射,将着重点放在业务逻辑上,而mybatis需要使用者手写sql语句以及resultMap,所以hibernate开发速度要比mybatis快。
但是hibernate门槛要比mybatis高,两者之间做选择时要根据项目实际需求作为标准,如:项目绝大多数是简单的数据操作,比如增删改查,没有太多的复杂sql语句,这时选择hibernate用于dao工具会更简便、效率更高一些;反之,如果项目有很多复杂的sql语句,则适合选择mybatis,因为mybatis可以进行更为细致的sql优化,可以减少查询字段从而提高效率(因为hibernate会把所有字段都进行处理,效率会稍低)。

1.Mybatis缓存

MyBatis提供了一级缓存和二级缓存

  • 一级缓存:也称为本地缓存,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。
  • 二级缓存:也称为全局缓存,是mapper级别的缓存,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的。

2.SQL注入

  • #{}是预编译处理、是占位符I ${}是字符串替换、 是拼接符。
  • Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement来赋值;
  • Mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值, 调用Statement来赋值;
  • #}的变量替换是在DBMS中、变量替换后,#{} 对应的变量自动加上单引号
  • 的 变 量 替 换 是 在 D B M S 外 、 量 替 换 后 , {}的变量替换是在DBMS外、量替换后, DBMS{} 对应的变量不会加上单引号
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。

3.Mybatis实现原理

  • 1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息.
  • 2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
  • 3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  • 4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
  • 5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  • 6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
  • 7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
  • 8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
    在这里插入图片描述

4. 索引的基本原理(mysql b+tree)

索引用来快速地寻找那些具有特定值的记录。如果没有索引,-般来说执行查询时遍历整张表。
索弓的原理:就是把无序的数据变成有序的查询

  • 1.把创建了索弓的列的内容进行排序
  • 2.对排序结果生成倒排表
  • 3.在倒排表内容上拼上数据地址链
  • 4.在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

5. 聚簇索引和非聚簇索引

都是B+树的数据结构

  • 聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的。即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
  • 非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引l查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节, 那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。

Redis

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

1.RDB和AOF机制

  • RDB:快照,存储当前数据状态,产生一个子进程fork进行数据集写入.
  • AOF: 日志形式记录数据的写和删除操作.

2. 缓存雪崩、穿透、击穿

  1. 雪崩:缓存同一时间大面积失效.
    解决方案:
  • 缓存过期时间随机
  • 缓存预热
  • 互斥锁
  1. 缓存穿透:缓存中和数据库中都没有数据,造成数据库短时间内承受大量请求而崩掉.
    解决方案:
  • 前端和后端进行参数校验.
  • 从缓存取不到的数据,在数据库中也没有取到,可以在缓存中保存一个value为null的key-value的值.
  • 采用布隆过滤器,设置一个足够大的bitmap.
    3.缓存击穿: 缓存中没有,数据库中有的数据(一般发生在缓存时间到期).
    解决方案:
  • 设置热点数据永不过期.
  • 加互斥锁.

Spring Cloud

1.Spring Cloud核心组件

  1. Eureka:服务注册与发现.
  2. Ribbbon:负载均衡,通过服务名进行调用()
  3. Feign:动态代理机制,在Ribbon的基础上对其URL地址进行拼接,简化了服务之间的调用.
  4. Hystrix:服务熔断和降级.
  5. Zuul: 网关,前端调用统一从Zuul网关转发请求给对应的服务.
    在这里插入图片描述

2. SpringCloud Alibaba核心组件

  1. Nacos (配置中心与服务注册与发现)
  • Nacos实现了服务的配置中心与服务注册发现的功能,Nacos可以通过可视化的配置降低相关的学习与维护成本,实现动态的配置管理与分环境的配置中心控制。 同时Nacos提供了基于http/RCP的服务注册与发现功能。
  1. Sentinel (分布式流控)
  • Sentinel是面向分布式微服务架构的轻量级高可用的流控组件,以流量作为切入点,从流量控制,熔断降级,系统负载保护等维度帮助用户保证服务的稳定性。常用与实现限流、熔断降级等策略。
  1. RocketMQ (消息队列)
  • RocketMQ基于Java的高性能、高吞吐量的消息队列,在SpringCloud Alibaba生态用于实现消息驱动的业务开发,常见的消息队列有Kafka、RocketMQ、RabbitMQ等,相关的比较文档可以自行去翻阅。
  1. Seata (分布式事物)
  • 既然是微服务的产品,那么肯定会用到分布式事物。Seata就是阿里巴巴开源的一个高性能分布式事物的解决方案。
  1. Dubbo (RPC)
  • Dubbo已经在圈内很火了,SpringCloud Alibaba基于上面提到的Nacos服务注册中心也同样整合了Dubbo。

3.RabbitMq

工作模式

  • 简单模式:
    在这里插入图片描述
    RabbitMQ是一个消息中间件,你可以想象它是一个邮局。当你把信件放到邮箱里时,能够确信邮递员会正确地递送你的信件。RabbitMq就是一个邮箱、一个邮局和一个邮递员。
  • 工作模式:
    在这里插入图片描述
    在这里插入图片描述
  • 发布订阅模式:
    在这里插入图片描述
  • 路由模式
    在这里插入图片描述
  • 主题模式
    在这里插入图片描述
  • RPC模式
    在这里插入图片描述
  1. Broker: rabbitmq的服务节点
  2. Queue:队列,是RabbitMQ的内部对象, 用于存储消息。RabbitMQ中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注意: RabbitMQ不支 持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式。
  3. Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。
  4. RoutingKey:路由Key。生产者将消息发送给交换器的时候,- 般会指定一个RoutingKey, 用来指定这个消息的
    路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键固定的情况下,生产可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。
  5. Binding:通过绑定将交换器和队列关联起来,在绑定的时候-般会指定一个绑定键, 这样RabbitMQ就可以指定如何正确的路由到队列了。
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值