Spring学习笔记

Spring

== 还没有整理完成,后续几天有空整理 ==

1. Spring简介

  1. Spring框架以interface21框架为基础,经过重启设计,并集成了其它许多开源成果,于2004年3月24日,发布了1.0正式版取名为Spring。

  2. Rod Johnson

  3. Spring是一个开源框架

  4. Spring为简化企业级开发而生

  5. Spring是一个IOC和AOP容器框架(核心重点)

    • IOC介绍
    • AOP介绍
  6. Spring的良好特性

    1. 非入侵式:基于Spring开发应用时,无需我们掌握Spring的全部API,即程序对象可以不依赖于Spring的API,因此我们即是仅仅了解Spring框架的基本使用依然不影响我们日常的开发,我们日常的开发可以无。
    2. 依赖注入:反转控制(IOC)最经典的实现
    3. 面向切面编程:AOP
    4. 容器:Spring可以理解为是一个很大的容器,这个容器里面包含并管理应用对象的生命周期
    5. 组件化:严格意义上讲,Spring自身功能并没有这么强大,但Spring的生态圈中拥有着非常多各种各样功能的组件,根据项目需求,只需在Spring基础框架上组合配置上其他的组件,即可快速地合成一个复杂且强大的集成开发环境。
    6. 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方库(实际上Spring自身也提供了表述层的SpringMVC和持久层的Spring JDBC)
  7. Spring的模块划分图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cph4f5fU-1625908136307)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210701230826234.png)]

    • Test:Spring的单元测试模块
    • Core Container:Spring的核心容器(IOC);黑色的代表这部分的功能由那些jar包构成。即如果要使用这部分的功能,这些jar组件包都需要导入。
    • ACP + Aspects : 面向切面变成模块
  8. Java框架

    • SSH : Struct2 + Spring + Hilbernate
    • SSM : Spring + SpringMVC + Mybatis
  9. Spring拓展生态

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oRwfUm7C-1625908136310)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210703000018887.png)]

    • Spring Boot
      • 一个快速开发的手脚架
      • 基于SpringBoot可以快速开发单个微服务
      • 约定大于配置!
    • Spring Cloud
      • SpringCloud是基于SpringBoot实现的,即使用SpringCloud需要使用先实现SpringBoot
      • SpringCloud现在常被用于做“大数据”的平台实现,如流计算,分布式等
  10. 总结:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。

2. IOC(容器)

2.1 IOC概述

​ IOC全称(Inversion Of Control),即控制反转。为什么叫控制反转呢?因为在过去,我们需要一个实例对象为我们服务时,我们不仅要声明这个实例对象(声明对象的变量名和类型),而且还需要我们主动去创建这个实例对象(new一个对象),这个“主动”就是“正向的一个操作”,而Spring IOC就是代替我们完成这个“主动的正向操作”(代替我们完成对象创建的操作),让我们从“主动创建对象”变成“被动接收对象”的一个过程。

​ 所以有了Spring IOC,我们只需声明要使用的实例对象即可,在编译运行时,Spring会通过DI(Dependency Injection)依赖注入机制,根据具体情况,为我们选择合适的组件和对象进行注入。

2.2 关于IOC的一些细节

  • 控制(Control):资源获取的方式(对比我们主动创建和被动创建的区别)

    • 主动式:需要什么资源自己创建即可

      BookServlet{
      	BookService bs = new BookService();
          AirPlane ap = new AirPlane();
      }
      
    • 被动式:资源的获取并不是由我们来创建,而是由一个容器来创建和设置

      BookServlet{
      	BookService bs;
      	public void test01(){
      		bs.checkout;
      	}
      }
      
  • 容器:管理所有的组件(拥有特定功能的类);假设BookServlet受容器管理,则其内部的BookService类也会受到容器的管理;在容器中的BookService被引用时,容器会自动帮助我们探测当前组件需要哪些依赖的组件,并且容器还会帮我们创建BookService的对象,并把BookService对象赋值给声明的回去。

2.3 DI:(Dependency Injection)依赖注入:

  • 依赖注入DI和控制反转IOC是有区别的,可以简单理解为IOC是“一种思想”,而DI则是基于IOC这种思想在Spring中的一个实现。就比如我们在ACM算法中常见的找零钱的问题:贪心算法与找最少张零钱的一个程序。贪心算法就好比一种思想,而找最少张零钱的一个程序则是基于贪心算法思想的一种实现。

  • 容器可以知道哪个组件(类)在运行的时候,需要其他的哪些组件(类)

  • 容器通过反射的形式,将容器中准备好的对象注入(利用发射的机制给类型变量赋值)到对应的变量中

2.4 代码示例

Dao.UserDao

public interface UserDao {
    public void getUser();
}

Dao.UserDaoImpl

public class UserDaoImpl implements UserDao{
    public void getUser() {
        System.out.println("获取默认数据库-传统MVC模式");
    }
}

Dao.UserMysqlImpl

public class UserMysqlImpl implements UserDao{
    public void getUser() {
        System.out.println("Mysql实现!-新型业务实现");
    }
}

Dao.UserOracelImpl

public class UserOracleImpl implements UserDao{
    public void getUser() {
        System.out.println("Oracle实现!-新型业务实现");
    }
}

Service.UserService

public interface UserService{
    public void getUser();
}

Service.UserServiceImpl

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserMysqlImpl();

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void getUser() {
        userDao.getUser();
    }

    public UserServiceImpl() {
    }
}

Test.UserServiceImplTest

public class UserServiceImplTest {

    @Resource
    UserService userService = new UserServiceImpl();

    {
        System.out.println(userService.getClass());
    }

    @Test
    public void getUserTest(){
        userService.getUser();
    }

    @Test
    public void getUserTest01(){
        userService = new UserServiceImpl(new UserOracleImpl());
        userService.getUser();
        System.out.println(userService.getClass());
    }

    @Test
    public void getUserTest02(){
        ((UserServiceImpl) userService).setUserDao(new UserOracleImpl());
        userService.getUser();
        System.out.println(userService.getClass());
    }
}

总结:通过以上的测试对比,我们不难发现,传统的MVC编程模式的代码对于具有同操作但不同类型需求的场景处理并不友好,每一次出现新的业务功能需求时,不仅在Dao层需要实现实体的Dao组件,而且还需要单独在Service层实现新Dao组件的相似服务类,但经过改进后,我们只需要提供给用户一个功能接口即可,用户需要什么类型功能只需要传入指定的对象即可,这样,相当于将Service层的重复操作提交给了Controller层管理,即把主动权交给了调用者,程序无需管对象的具体创建,由外使用者来实现具体功能,这样大大降低了系统Dao模块与Service模块的耦合度,也可以认为这是一个解耦的过程。这是IOC实现的一个原型。

简单来说:控制反转IOC是一种思想,它将原本应该由程序自身创建对象的工作交给了第三方来完成,程序此时从创造者成为了接收者。

附:

  • 控制反转IOC是一种设计思想,DI(依赖注入)是控制反转实现的一种方式,它还有一种实现方式叫“依赖查找”(Dependency Lookup)。
  • 控制反转是一种通过描述(XML或注解)并通过第三方生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,本质来说,Spring的IOC实现的方法就是依赖注(Dependency Injection,DJ)

4. Spring 编程小知识

  • spring默认使用无参构造器创建对象

  • xml配置文件中bean标签配置

    属性描述示例
    classbean对象对应的全限定名:包名+类型。用于描述当前bean元素关联的类,其本质对应的是bean对象的类型
    idbean元素的唯一标识符,我们可以通过指定bean的id来调用当前的bean对象,id不允许重复其中bean元素中的property的ref指定的就是一个id为mysql的bean对象:
    name用于做bean的别名配置,一般是在id创建后使用,但也可以不用id,直接使用name代替id,但name允许重复,这个在使用时要多多注意
    scope
  • xml配置文件中bean标签内元素配置

标签标签-描述属性属性-描述示例
property用于给指定的bean类的成员变量赋值name、value①name:用于指定bean类型中成员名;
②value:用于给name指定的类的基本类型成员变量赋值
property用于给指定的bean类的成员变量赋值name、ref①name:用于指定bean类型中成员名;
②ref:用于给name指定的类的引用类型成员变量赋值

constructor-arg用于给bean类的构造方法赋值name、value①name:用于指定bean类的构造方法的形参名字
②value:用于给name指定的形参赋值
constructor-arg用于给bean类的构造方法赋值index、value①index:通过下标[0-n]指定bean类的构造方法的形参
②value:用于给index指定的形参赋值

constructor-arg用于给bean类的构造方法赋值type、value①type:通过类型指定bean类的构造方法的形参
②value:用于给type指定的形参赋值
  • 当有多个人开发spring的类开发时,可能会有多个xml文件,这个时候可以选择通过application.xml文件,将多个不同的xml文件以import的方式导入合并成一个大的xml配置文件,所以,这个时候我们程序中的ClassPathApplicationContext( )的参数也无需添加新的xml文件,只需要将参数指定为application.xml文件即可,我们通过修改application.xml文件来添加型的配置项,减少对代码的修改,便于合作开发。
  • bean的作用域(scope属性决定)
scope属性选项描述示例
singleton单例模式(singleton)下:被用于实例化的同一个id的bean始终都将是一个对象
prototype原型模式(prototype)下:被用于实例化的同一个id的bean被调用时将会有唯一与变量对应
requestrequest表示该指针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效;只能在web开发中使用暂无
sessionsession作用域表示指针每一次HTTP响应都会产生一个新的bean,同时该bean仅在当前HTTP session内有效,配置示例;只能在web开发中使用暂无
application不同于request和session,application在全局都有效,只能在web开发中使用暂无

5. Spring IOC反转控制实现——依赖注入DJ(Dependency Injection)

5.1 构造器注入

上述的3种构造器注入方式

实现方式:

  • 无参构造-除了基本指定,无需设置有关construct的内容
  • 有参构造-三种注入方式:index,type,name注入

5.2 Set函数注入【重点】

  • 依赖注入:Set注入
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性都由容器来注入赋值
  • 实现方式:
    • 常量注入
    • Bean注入(ref,常用于引用数据类型)
    • Array数组注入
    • List列表注入
    • Set集合注入
    • Map哈希注入
    • Null空注入
    • Properties元素注入

6. Bean的自动装配

  • 自动装配是Spring满足bean依赖的一种方式

  • Spring会在上下文中自动寻找,并自动给bean装配属性

  • 在Spring中有三种装配的方式

    1. 在xml中显示配置
    2. 在java中显示配置
    3. 隐式的自动装配【常用-重要】
  • XML配置实现自动装配

    自动装配方式(autowire)描述示例
    byNamebyName这种方式其实特别有趣,因为Spring的实现的反转控制的DI,在使用XML文件配置时,给类的成员变量赋值的属性name,其实是与这个类成员变量的setter方法名中set后面的内容绑定的,但name绑定的标签首字母要小写。所以基于这个原理,只要保证类成员的setter方法名中set后面的内容与容器要注入的对象标识符除了首字母要小写不同外,其他必须相同。
    满足以上这些条件即可实现一个byName的自动装配。当byName找不到时,会默认通过类型来找。
    public void setDagTt(Dog dog) { this.dog = dog; }
    byTypebyType会在Spring的容器中寻找与当前对象成员类型相同的对象给自己的对象成员进行赋值(有一个很重要的前提:在Spring的IOC容器中,必须保证要使用的类型唯一,否则会执行失败,注意是整个SpringIOC容器空间,当前程序所有被注释或xml配置的都必须保证只有一个要获取的类型对象)附:byType可以不需要定义bean的id
  • 使用注解实现自动装配

    • 使用须知

      1. 导入约束:context约束
      2. 配置注解的支持:context:annotation-config/
      3. 使用注解后,可以不写setter方法,因为它底层的原理是通过反射来实现的
    • 使用方式

      • @Autowired注解

        • 可以直接在属性上使用!也可以直接在属性的Set方法上使用

        • 使用@Autowired注解时,我们可以不写Set方法,直接实现Get方法即可。前提是Spring的IOC容器中存在需求的对象,即要满足byType

        • @Nullable:字段被标记这个注解后,说明这个字段可以为空

          public void setDog1(@Nullable Dog dog1) {
          	this.dog1 = dog1;
          }
          
        • @Autowired(required=false):如果现实定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空

        • 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解完成的时候,我们使用@Qualifier(value=“xxx”)去配置@Autowired的使用,指定唯一的一个bean去注入

          @Autowired
          @Qualifier(value = "mydog3")    //强制指定当前变量的注入对象
          private Dog dog3;
          
      • @Resource注解

        • @Resource是java的注解,所以使用@Resource注解时,当前类成员并不会与xml文件中的配置产生关联,只有在编译时才会去查找合适的对象进行匹配。但相对来说Resource的功能比Autowire更强大(@Resource在jdk11后移除了)

        • 使用@Resource的时候,它会先通过成员变量名字在IOC容器中查找是否有相关的对象,即byName(这个byName和依赖注入的byName有些不同,这个查找并不是查找类成员的setter方法中set后面的内容,而是查找类成员的变量名),如果发现没有,这时候则开始通过类型查找byType,如果类型查找时发现不止一个相关类型对象或者没有则会报错,只有当类型对象为唯一且只存在一个的时候才不会报差错

        • 有时候因为业务要求,需要强制指定一个bean对象时,可以通过@Resource(name=“xxx”)来强制指定bean对象,name的值是bean对象的id或name

          @Resource(name = "cat2")
          private Cat cat2;
          
  • xml与注解:

    • xml更加万能,适用于任何场合!由于集中在一些配置文件中,所以它的管理可能会方便一些
    • 注解:不是自己的类无法使用,维护相对复杂
  • xml与注解应用

    • xml用来管理bean

    • 注解负责完成属性的注入

    • 我们在使用的过程中,需要注意:想让注解生效,就必须开启注解的支持

      <context:component-scan base-package="com.fuxi.spring.dao"/>
      <context:annotation-config/>
      

7. 注解开发常用注解

注解描述示例
@Autowired用于做自动装配,有属性required用于配置变量是否允许为空
有byName和byType两种对象获取方式
@Autowired(required=false)
@Resource可以用于做自动装配,有属性name,可以用于强制指定bean对象名称进行注入
有byName和byType两种对象获取方式
@Resource(name=“gxxx”)
@Quilfiler通常与Autowired配合使用,辅助@Autowired完成自充装配工作@Autowired
@Qualifier(value = “mydog3”) //强制指定当前变量的注入对象
private Dog dog3;
@Component、
@Repository、
@Service、
@Controller
@Component有很多个衍生注解,常见的我们在web开发中,会按照MVC三层架构分层
①dao:@Repository
②service:@Service
③controller:@Controller
且这四个注解功能都类似,都代表将某个类注册到Spring容器中,即装配Bean
@Component
public class User { private String name = “张三”;
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
@Scope用于控制Bean对象的范围,常见的是单例和原型,在web开发中还有session,required,application选项@Component @Scope(“singleton”) public class User {…
@Component @Scope(“prototype”) public class User {…
@Value复制,通常对基本数据类型和String使用@Value(“张三”) private String name;
@Import可以用于引入相关的配置类(@Configuration注解的类)
@ComponentScan用于引入相关bean,参数是bean类所在的包路径@ComponentScan(“com.fuxi.spring.dao”

8. 代理模式

为什么要学代理模式?因为这是SpringAOP的底层!【SpringAOP和SpringMVC面试必问】同时,在企业中,改变原有完备的代码是“大忌”,为了不修改原有的代码,且又要拥有原来的业务功能,则可以选择代理模式开发测试。

代理模式的分类

  • 静态代理
  • 动态代理

8.1 静态代理

角色分析

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代理模式的好处

  • 可以使得真实角色的操作更加纯粹!不必关心公共业务的实现,相当于将数据层的业务实现剥离,实现了更明确的业务分工,间接地降低了数据层与业务层间的耦合度
  • 公共业务的实现由代理角色实现!实现了业务上的分工!
  • 公共业务发生拓展的时候,方便集中管理

缺点

  • 一个真实角色往往会产生一个代理角色;这时候代码量将会大大增加,开发效率会降低

代理模式的步骤示例:

  1. 租贷接口-租贷

    public interface Rent {
        public void rent();
    }
    
  2. 真实类-房东

    public class Host implements Rent{
        public void rent() {
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理类-中介

    public class Proxy implements Rent {
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        public void rent() {
            host.rent();
        }
    
        //看房
        public void seeHouse(){
            System.out.println("中介带客户看房子");
        }
    
        //签合同
        public void heTong(){
            System.out.println("签租贷合同");
        }
    
        //收取中介费
        public void fare(){
            System.out.println("收取中介费");
        }
    
    }
    
  4. 客户类-租贷者

    public class Client {
        public static void main(String[] args){
            Host host = new Host();
           //代理房东-通常dialing
            Proxy proxy = new Proxy(host);
            proxy.seeHouse();
            proxy.heTong();
            proxy.fare();
            proxy.rent();
        }
    }
    

8.2 动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口:jdk动态代理
    • 基于类:cglib
    • java字节码实现:javasist
  • 这里使用jdk接口动态代理,需要两个类:
    • Proxy:代理
    • InvocationHandler:调用

9. AOP

9.1 什么是AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发的一个热点,也是Spring框架的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率。

9.2 AOP在Spring中的作用

  • 横切关注点:跨越应用程序多个模块的方法或功能。即,与我们业务逻辑无关,但又需要我们关注的部分,就是横切关注点。如日志,安全,缓存,事务等等。
  • 切面(ASPECT):横切关注点 “被模块化” 的特殊对象。即,它是一个类。【例如:日志实现的Log类】
  • 通知(Advice):切面必须完成的工作。即,它是类中的一个方法。【例如:日志实现的Log类内部的方法】
  • 目标(Target):被通知的对象。【例如:被调用的日志实现的Log类要执行所依赖的对象】
  • 代理(Proxy):向目标对象“发送”通知后创建爱你的对象。
  • 切入点(PointCut):切面通知执行的“地点”的定义
  • 连接点(JointPoint):与切入点匹配的执行点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M2bsvxvW-1625908136317)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210706191724573.png)]

目前在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neV6QHlY-1625908136321)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210706193032252.png)]

即AOP在不改变原有代码的情况下,去增加新的功能

9.2 使用Spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包!

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
</dependency>
  • 实现方式
    • 方式一:使用Spirng的API接口实现【主要SpringAPI接口实现】
    • 方式二:自定义来实现AOP【主要是切面定义】
    • 方式三:通过注解实现!

10. Mybatis

10.1 Mybatis程序编写过程:

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试

10.2 Mybatis-spring

  1. 编写数据源配置
  2. sqlSessionFactory
  3. sqlSessionTemplate
  4. 需要给接口加实现类【mapper的Impl类】
  5. 将自己写的实现类【xxImpl类】,注入到Spring中
  6. 测试使用即可!

附:

  • SqlSessionTemplate是MyBatis-Spring的核心,作为SqlSession的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的SqlSession。SqlSessionTemplate是线程安全的,可以被多个DAO或映射器共享使用,这不仅保证了同个Session的复用,同时线程安全还保证了操作上的正确性

11. 声明式事务

11.1 回顾事务

  • 把一组业务当成一个事务来做;要么成功,要么失败
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎
  • 确保完整性和一致性

11.2 事务ACID原则

  • 原子性:确保一个“事务”的执行过程,要么成功,要么失败
  • 一致性:确保提交的“事务的结果”的一致,要么"全部数据"都提交,要么"全部数据"都不提交
  • 隔离性:多个业务可能操作同一个资源,保证这种情况下数据不被损坏
  • 持久性:事务一旦提交,无论系统发生什么,结果都不会再被影响到,被持久化地写到了存储器中

11.3 Spring中的事务管理

  • 声明式事务:AOP
  • 编程式事务:需要在代码中,进行事务管理

11.4 为什么需要事务?

  • 如果不配置事务,那么在多个sql请求(多用户)提交数据时,则非常容易出现数据提交不一致的情况
  • 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务!
  • 事务在项目开发中非常的重要,它与数据的一致性和完整性的实现和保证密切相关,不容疏忽!

sion的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的SqlSession。SqlSessionTemplate是线程安全的,可以被多个DAO或映射器共享使用,这不仅保证了同个Session的复用,同时线程安全还保证了操作上的正确性

11. 声明式事务

11.1 回顾事务

  • 把一组业务当成一个事务来做;要么成功,要么失败
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎
  • 确保完整性和一致性

11.2 事务ACID原则

  • 原子性:确保一个“事务”的执行过程,要么成功,要么失败
  • 一致性:确保提交的“事务的结果”的一致,要么"全部数据"都提交,要么"全部数据"都不提交
  • 隔离性:多个业务可能操作同一个资源,保证这种情况下数据不被损坏
  • 持久性:事务一旦提交,无论系统发生什么,结果都不会再被影响到,被持久化地写到了存储器中

11.3 Spring中的事务管理

  • 声明式事务:AOP
  • 编程式事务:需要在代码中,进行事务管理

11.4 为什么需要事务?

  • 如果不配置事务,那么在多个sql请求(多用户)提交数据时,则非常容易出现数据提交不一致的情况
  • 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务!
  • 事务在项目开发中非常的重要,它与数据的一致性和完整性的实现和保证密切相关,不容疏忽!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值