SpringBoot笔记

SpringBoot

第一部分 SpringBoot应用

相关概念

约定优于配置

约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计规范。

本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)。

例如在模型中存在一个名为User的类,那么对应到数据库会存在一个名为user的表,此时无需做额外的配置,只有在偏离这个约定时才需要做相关的配置(例如你想将表名命名为t_user等非user时才需要写关于这个名字的配置)。

如果所用工具的约定与你的期待相符,便可省去配置;反之,你可以配置来达到你所期待的方式。简单来说就是假如你所期待的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待时才需要对约定进行替换配置。

好处:大大减少了配置项

SpringBoot主要特性

1.SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中;

2.使编码变得简单:SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,极大的提高了工作效率。

3.自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的bean并自动化配置他们;

4.使部署变得简单:SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow.我们只需要一个Java的运行环境就可以跑SpringBoot的项目了,SpringBoot的项目可以打成一个jar包。

配置文件优先级

  1. 先去项目根目录找config文件夹下找配置文件件

  2. 再去根目录下找配置文件

  3. 去resources下找cofnig文件夹下找配置文件

  4. 去resources下找配置文件

SpringBoot会从这四个位置全部加载主配置文件,如果高优先级中配置文件属性与低优先级配置文件不冲突的属性,则会共同存在— 互补配置 。

备注:

1.如果同一个目录下,有application.yml也有application.properties,默认先读取 application.properties(2.4.0会先读取yml)。

2.如果同一个配置属性,在多个配置文件都配置了,默认使用第1个读取到的,后面读取的不覆盖前面读取到的。

SpringBoot日志框架

日志框架介绍

Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack

下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,直接应用与应用程序中,具体的日志框架实现SLF4J。

截屏2021-08-30 下午6.31.38

注意:由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4j 之后,配置文件还是要使用实现日志框架的配置文件。

统一日志框架使用步骤归纳如下

  1. 排除系统中的其他日志框架。

  2. 使用中间包替换要替换的日志框架。

  3. 导入我们选择的 SLF4J 实现。

SpringBoot使用SLF4J分析

1.在SpringBoot的maven依赖中去除了其他的三方日志框架

截屏2021-08-30 下午6.49.37

2.在 spring-boot-starter中引入了 spring-boot-starter-logging

截屏2021-08-30 下午6.50.38

spring-boot-starter-logging的依赖如下:

截屏2021-08-30 下午6.52.39

其中logback-classic包含了日志框架 Logback的实现

log4j-to-slf4j 是 log4j 向 slf4j 转换的jar

jul-to-slf4j是Java 自带的日志框架转换为 slf4j

IDEA 中查看 Maven 依赖关系如下:

截屏2021-08-30 下午6.54.53

由上可知:Spring Boot 可以自动的适配日志框架,而且底层使用 SLF4j + LogBack 记录日志,如果我们自行引入其他框架,需要排除其日志框架。

第二部分 SpringBoot源码分析

构建源码环境

构建要求jdk是1.8+的,Maven3.5+

1.下载源码:我下的是spring-boot-2.2.9.RELEASE

2.编译:进⼊spring-boot源码根⽬录执⾏mvn命令: mvn clean install -DskipTests -Pfast // 跳过测试⽤例,会下载⼤量 jar 包(时间会长一些)

3.打开源码的pom.xml关闭maven代码检查

<properties> 

<revision>2.2.9.RELEASE</revision> 

<main.basedir>${basedir}</main.basedir> 

<disable.checks>true</disable.checks> 

</properties>

4.新建一个web工程,我在这里叫spring-boot-mytest,然后在工程中添加一个controller

@RequestMapping("/test")
	public String test() {
		System.out.println("源码导入成功");
		return "源码导入成功";
	}

5.运行spring-boot-mytest,在浏览器输入http://localhost:8080/test 浏览器能打印出"源码导入成功"就代表源码构建成功

分析源码主要是为了解决下面的问题

  1. starter是什么?我们如何去使用这些starter?

  2. 为什么包扫描只会扫描核心启动类所在的包及其子包

  3. 在SpringBoot启动的过程中,是如何完成自动装配的?

  4. 内嵌Tomcat是如何被创建及启动的?

  5. 使用了web场景对应的starter,springmvc是如何自动装配?

依赖管理

在pom文件中SpringBoot工程会继承一个spring-boot-starter-parent

截屏2021-08-31 下午5.56.22

spring-boot-starter-parent会继承一个spring-boot-dependencies

截屏2021-08-31 下午5.56.54

在spring-boot-dependencies的properties中定义了SpringBoot中相关jar的版本

截屏2021-08-31 下午5.58.49

在spring-boot-dependencies的dependencyManagement中定义了SpringBoot版本的依赖的组件以及相应版本

截屏2021-08-31 下午6.03.58

spring-boot-starter-parent 通过继承 spring-boot-dependencies 从而实现了SpringBoot的版本依

赖管理,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了,这也

就是在 Spring Boot 项目中部分依赖不需要写版本号的原因

在 spring-boot-starter-parent 的 properties 节点中定义了

  • 工程的Java版本为 1.8 。
  • 工程代码的编译源文件编码格式为 UTF-8
  • 工程编译后的文件编码格式为 UTF-8
  • Maven打包编译的版本

截屏2021-08-31 下午6.13.24

在spring-boot-mytest中还引入了spring-boot-starter-web,其打包了Web开发场景所需的底

层所有依赖(基于依赖传递,当前项目也存在对应的依赖jar包)

截屏2021-08-31 下午6.23.01

正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而

不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由

spring-boot-starter-parent父依赖进行的统一管理。

Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,

我们可以打开Spring Boot官方文档,搜索“Starters”关键字可以查询场景依赖启动器,对于官方的启动器都不需要指定版本号

SpringBoot并不是对所有的场景都提供了场景启动器,比如要使用Druid时就没有对应的启动器,但是阿里主动与Spring Boot框架进行了整合,实现了其依赖启动器(druid-spring-boot-starter)。在引入这种第三方的启动器需要指定版本

截屏2021-08-31 下午6.23.45

自动配置

在启动类里面会用到**@SpringBootApplication**点进注解会发现其是一个复合注解

截屏2021-08-31 下午6.43.41

其中和自动配置相关的注解是**@EnableAutoConfiguration**,进入该注解:

截屏2021-09-01 上午11.24.15

Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean

,并加载到 IOC 容器。

@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到

IoC容器。

@EnableAutoConfiguration

@EnableAutoConfiguration有两个比较重要的注解:@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

截屏2021-09-01 上午11.19.21

@AutoConfigurationPackage注解通过@Import注册了一个AutoConfigurationPackages.Registrar.class

截屏2021-09-01 上午11.20.06

截屏2021-09-01 上午11.20.37

register注册了一个bean,该bean的那么是AutoConfigurationPackages.class.getName(),class是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class)导入了AutoConfigurationImportSelector

AutoConfigurationImportSelector可以帮助 SpringBoot 应用将所有符合条件的自动配置类都加载到

当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。AutoConfigurationImportSelector的继承关系如下:

截屏2021-09-01 上午11.45.40

在启动类的run方法中在自动配置时会使用到AutoConfigurationImportSelector,这里先不从run方法进入,直接在AutoConfigurationImportSelector中分析相关代码

在AutoConfigurationImportSelector中有一个内部类AutoConfigurationGroup,其process方法就是run中在实现自动配置时会调用的方法

截屏2021-09-01 下午2.14.44

关键方法是getAutoConfigurationEntry

截屏2021-09-01 下午2.36.01

截屏2021-09-01 下午2.36.14

关键方法是getCandidateConfigurations,getCandidateConfigurations最后会调用loadSpringFactories

截屏2021-09-01 下午3.25.09

截屏2021-09-01 下午3.26.15

loadSpringFactories这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。

spring.factories里面保存着SpringBoot的默认提供的自动配置类。SpringBoot的spring.factories中和自动配置相关的代码如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

filter方法如下:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   
   long startTime = System.nanoTime();
   // 将从spring.factories中获取的自动配置类转成字符串数组
   String[] candidates = StringUtils.toStringArray(configurations);
   // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   // getAutoConfigurationImportFilters方法:拿到OnBeanCondition, OnClassCondition和OnWebApplicationCondition
   // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
   
      // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
      // 这里的filter对象即OnBeanCondition,OnClassCondition或 OnWebApplicationCondition
      invokeAwareMethods(filter);
      // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
      // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里 面的注解值)是否匹配,
      // 注意candidates数组与match数组一一对应
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
   
         // 若有不匹配的话
         if (!match[i]) {
   
            // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一 对应
            skip[i] = true;
            // 因为不匹配,将相应的自动配置类置空
            candidates[i] = null;
            // 标注skipped为true
            skipped = true;
         }
      }
   }
   // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
   if (!skipped) {
   
      return configurations;
   }
   // 建立result集合来装匹配的自动配置类
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
   
      // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
      if (!skip[i]) {
   
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
   
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.<
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值