SpringBoot使用及原理

SpringBoot简介

开发一个web应用,从最初开始接触Servlet结合Tomcat,跑出一个HelloWorld程序,要经历很多步骤;接下来出现了SpringMVC这样的框架,直到现在流行的springboot.
Springboot就是一个javaweb的开发框架,和springMVC类似,此框架约定大于配置,能快速的开发web应用。
随着Spring不断的发展,项目整合开发需要配置各种各样的文件,配置太多太复杂。spring boot正是为了解决这样的问题应运而生,更容易继承各种常用的中间件等。Springboot应用中使用第三方库几乎可以零配置开箱即用。
spring boot的主要优点:
1、为所有Spring开发者快速入门
2、开箱即用,提供各种默认配置来简化项目配置
3、内嵌容器
4、配置少

开始 Hello World

环境:
java version 1.8.0_91
Maven3.6.2
SpringBoot2.x

开发工具:
IDEA

使用IDEA创建项目
file->new project ->选择pring initalizr _->一路next,填写对应的工程及存储路径即可->finish

项目构建分析
项目构建成功后生成以下内容
1、程序启动类
2、一个application.properties配置文件
3、一个测试类
4、一个pom.xml

pom分析

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- web启动器 -->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
		<!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写测试接口:
1、在主程序启动类下再创建一个controller包,一定在同级目录下,不然识别不到
在这里插入图片描述
2、在包中新建一个HelloWorldController类

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {

    @RequestMapping("/hello")
    public String hello(){
        return "Hello world";
    }
}

启动程序并访问
在这里插入图片描述

运行原理

pom.xml

父依赖

它主要依赖一个父项目,主要是管理项目的资源过滤及插件
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
点进去还会有一个父依赖
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.2</version>
  </parent>

这里是真正的管理SpringBoot应用里面所有依赖版本的地方,SpringBoot版本控制中心
我们导入依赖默认是不需要写入版本的,但是如果导入的包没有在依赖中管理那么就需要我们手动添加版本号。

web启动器

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
springboot-boot-starter-xxxxxx : 这是spring-boot的场景启动器

spring-boot-starter-web 导入了web模块正常运行的所有依赖组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter(启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来,我们用什么功能就导入什么场景的启动器即可,我们也可以定义自己的starer;

启动类

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//@SpringBootApplication 用来标注主程序类,来说明这是一个Spring boot应用
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

进入@SpringBootApplication注解

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
@ComponentScan
此注解在Spring中很重要,它对应XML配置中的元素;
作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类

进入SpringBootConfiguration注解

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@Configuration,说明这是一个配置类,配置类就是对应Spring的xml配置文件

点击Configuration注解进入查看

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}
@Component说明,启动类本事就是Spring中的一个组件,负责启动应用
@EnableAutoConfiguration :开启自动配置功能
这个注解可以上自动配置生效

点击@EnableAutoConfiguration进入

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
@AutoConfigurationPackage :自动配置包

点击@AutoConfigurationPackage

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
@import: Spring底层的注解,给容器导入一个组件
Registrar.class作用:将启动类的所有的包及包下面所有子包里面的所有组件扫描到Spring容器
@Import({AutoConfigurationImportSelector.class}):给容器导入组件
AutoConfigurationImportSelector:给容器导入选择器

点击AutoConfigurationImportSelector类查看源码

    //获得候选的配置
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //getSpringFactoriesLoaderFactoryClass方法返回的就是最开始的启动导入配置文件的注解类:EnableAutoConfiguration
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    //SpringFactoriesLoader调用loadFactoryNames()方法
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
		//此处调用loadSpringFactories方法
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
   private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
            	//获取META_INF/spring.factories资源
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
				//将读取到的资源封装成一个properties
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }
    }

全局搜索spring.factories 很多处都有这个资源
在这里插入图片描述

自动配置是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应的org.springframework.boot.autoconfigure包下的配置项,通过反射实例化为对应标注了@Configuration的javaConfig形式的IOC容器配置类,然后将这些都汇总称为一个实例并加载到IOC容器中。
1、SpringBoot在启动的时候从类路径下的META-INF/spring.facotries中获取EnableAutoConfiguration指定的值
2、将这些作为自动配置类导入容器
3、整个j2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中
4、它会给容器导入非常多的自动配置类,就是给容器中导入这个场景需要的所有组件

Yaml

SpringBoot使用一个全局配置,名称为application.properties或application.yaml
application.properties
结构:key=value
application.yaml
结构: key"空格value

作用:修改springboot中自动配置的默认值
例如:修改端口号
server.port=8081

YAML概述
YAML是一种标记语言 例:
server:
port: 8080

基本语法
1、空格不能省略
2、以缩进代表层级关系
3、区分大小写

对象、Map
key:
v1:
v2:
例:
person:
name: li
age: 18
或:
person: {name: li,age: 18}

数组(List,set)
animals:
- cat
- dog
- pig

pets: [cat,dog,pig]

注入配置文件

yaml可以给我们的实体类直接注入匹配值
1、在resources目录下新建一个文件application.yml
2、编写一个person实体类

package com.example.demo.bean;

public class Person {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

原来的配置

package com.example.demo.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Person {
    @Value("li")
    private String name;
    @Value("18")
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


//测试类
package com.example.demo;

import com.example.demo.bean.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
    }

}

//结果
Person{name='li', age=18}

使用yaml配置进行注入
1、修改person类及配置文件application.yml

package com.example.demo.bean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Component//注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean trueOrfalse;
    private Date birthday;
    private Map<String,Object> map;
    private List<Object> list;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getTrueOrfalse() {
        return trueOrfalse;
    }

    public void setTrueOrfalse(Boolean trueOrfalse) {
        this.trueOrfalse = trueOrfalse;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Map<String, Object> getMap() {
        return map;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public List<Object> getList() {
        return list;
    }

    public void setList(List<Object> list) {
        this.list = list;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", trueOrfalse=" + trueOrfalse +
                ", birthday=" + birthday +
                ", map=" + map +
                ", list=" + list +
                '}';
    }
}

//此时会提示查找文档引入依赖,根据官网导入依赖并冲洗启动才能生效
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

修改yml文件

person:
  name: li
  age: 18
  trueOrfalse: false
  birthday: 2021/02/17
  map: {k1: v1,k2: v2}
  list:
    - list1
    - list2
    - list3

执行测试结果
Person{name=‘li’, age=18, trueOrfalse=false, birthday=Wed Feb 17 00:00:00 CST 2021, map={k1=v1, k2=v2}, list=[list1, list2, list3]}

多环境切换

我们在主配置文件编写的时候名称可以是application-{profile}.properties/yml,用来指定不同环境
例如:开发环境application-dev.properties,生产环境application-prod.properties
激活配置:
在配置文件中指定dev生效
spring.profiles.active=dev

使用yml不需要多个文件

server:
  port: 8081
#选择生效的环境
spring:
  profiles:
    active: prod
---
server:
  port: 8083
spring:
  profiles: dev #开发环境

---
server:
  port: 8084
spring:
  profiles: prod #开发环境

springboot启动会扫描application.properties或application.yml文件

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下的配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级从高到低,高优先级配置会覆盖低优先级配置

我们可以通过spring.config.location来改变默认的配置文件位置
外部指定优先级最高
java -jar ***.jar --spring.config.location=C:/application.properties

自定义starter

官方启动器命名规范
spring-boot-starter-xxxxxxx
例:spring-boot-starter-web

自定义命名规范
xxxxx-spring-boot-start
例:mybatis-spring-boot-start

创建启动器

1、创建一个maven工程(不是web工程,简单的一个web工程即可,我的名称li-spring-boot-starter)
2、引包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.li</groupId>
    <modelVersion>4.0.0</modelVersion>
    <version>1.0-SNAPSHOT</version>

    <artifactId>li-spring-boot-starter</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

3、目录结构
在这里插入图片描述
4、编写服务HelloService、编写配置类HelloProperties、编写自动配置类并注入

package com;

public class HelloService {
    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name){
        return helloProperties.getPrefix()+name+helloProperties.getSuffix();
    }
}


package com;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "li.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}




package com;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication//web应用生效
@EnableConfigurationProperties(HelloProperties.class)//将该类注册为属性配置类
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}

5、在resources下新建META-INF/spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.HelloServiceAutoConfiguration

6、打包 maven-install,新建web项目引入依赖包(此处默认了解maven的使用,不再赘述)

7、新建web项目引入依赖后测试
在这里插入图片描述

package com.example.demo.configTest;

import com.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Helloworld {
    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("test");
    }
}

8、配置类application.properties

li.hello.prefix="preTest"
li.hello.suffix="sufTest"

9、启动工程并访问
在这里插入图片描述

自动配置类添加组件的时候会从properties配置文件中获取属性,我们只需要在文件中获取属性即可
xxxxxAutoConfiguration:自动配置类 给容器添加组件
xxxxxproperties:封装配置文件中相关属性

我们想让自动配置在一定条件下才进行,所有由@Conditional派生出很多注解,只有在满足条件时才进行容器组件的添加

衍生注解判断是否满足当前条件
@ConditionalOnJava系统java版本是否满足需求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定的Bean
@ConditionalOnExpression满足SpEL表达式
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的bean,或者这个bean是首选的
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditonalOnWebApplication当前是web环境
@ConditonalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lizhiqiang3344521

您的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值