Spring—IoC与Aop总结及Spring+mybatis整合

2 篇文章 0 订阅

1、Spring的体系结构

在这里插入图片描述

1)test测试部分

spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试,它提供了Spring ApplicationContext的一致加载和这些上下文的缓存。

2)Core container 核心容器

  • spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能,核心API。
  • spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean,IoC部分,如何创建对象,控制反转。
  • spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法,上下文,读取配置文件等内容。
  • spring expression(SpEL):模块是统一表达式语言(unified expression language,unified EL)的扩展,用于在运行时查询和操作对象图。该语言支持设置和获取属性值、属性赋值、方法调用、访问数组、集合和索引的内容、逻辑和算术操作符、命名变量,以及从Spring的IoC容器按名称检索对象。还支持列表映射、选择以及常见的列表聚合。

3)AOP 和 Instrumentation、Messaging

  • spring aop:提供面向切面的编程实现,允许你定义方法拦截器和切入点对代码干净地解耦分离。

  • spring aspects:集成自 AspectJ 框架, 主要是为 Spring AOP 提供多种 AOP 实现方法

  • spring instrument:提供用于某些应用服务器的类instrumentation 支持和类加载器实现。

  • Spring Framework 4 :包含spring-messaging模块,来自Spring集成项目的抽象,如Message、MessageChannel、MessageHandler等,用来提供基于消息的基础服务。该模块还包括一组用于将消息映射到方法的注解,类似于基于编程模型的Spring MVC的注解。

4)数据访问和集成

  • spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。主要是提供JDBC模板方式、关系数据库对象化方式、SimpleJdbc方式、事务管理来简化JDBC编程,主要实现类是 JdbcTemplate、SimpleJdbcTemplate 以及 NamedParameterJdbcTemplate
  • spring orm:为流行的 object-relational mapping(对象-关系映射)API(包括JPA、JDO和Hibernate)提供了集成层。使用spring-orm模块,可以结合O/R映射框架与Spring提供的所有其他特性结合,如简单声明性事务管理特性。
  • spring oxm:提供了一个抽象层用于支持 Object/XML mapping (对象/XML映射)的实现,如JAXB、Castor、XMLBeans、JiBX和XStream。
  • spring jms:包含生成和消费消息的功能。从Spring4.1开始,提供了与spring-messaging模块的集成。

5)Web

  • spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
  • spring websocket:主要是与 Web 前端的全双工通讯的协议。
  • spring webmvc-portlet :(也称为Web-Portlet模块)提供了要在Portlet环境中使用的MVC实现,并反映了基于servlet的spring-webmvc模块的功能。(该模块在Spring5中移除)
  • spring webflux:一个新的非堵塞函数式 Reactive Web 框架, 可以用来建立异步的、非阻塞、事件驱动的服务,并且扩展性非常好。

2、Spring中用到了哪些设计模式

  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  2. 单例模式:Bean默认为单例模式。
  3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  4. 模板方法:用来解决代码重复的问题。
  5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

3、Spring Beans

在Java中Bean 分两种类型:
1、封装数据的JavaBean:实体类
满足如下要求 :

  • 提供公共的无参数的构造函数 。
  • 属性要private。
  • 对私有属性封装成对应的get 及set方法。

2、封装业务的JavaBean serviceImpl
要求:

  • 公共的无参数的构造函数
  • 公共的业务方法

自己创建Bean(对象)的缺点:耦合性强

4、Spring IoC的实现

把对象的创建、管理的权限交由Spring框架管理–控制反转IoC
Spring体系结构中的Beans部分就是IoC
分析、实现spring是如何创建bean—自行封装实现

实现思路:

创建配置文件 MyBean.properties
在工厂中,根据参数,创建对应名字的bean
调用工厂,得到了bean

public class BeanFactory {
    public  static Object getBean(String name) throws ClassCastException, IllegalAccessException, InstantiationException, IOException, ClassNotFoundException {
        FileInputStream fileInputStream=new FileInputStream("src/main/resources/MyBeans.properties");
        Properties properties=new Properties();
        properties.load(fileInputStream);
        String className=properties.getProperty(name);
        Object obj=Class.forName(className).newInstance();
        return  obj;

    }
}

如何通过Spring 来创建对象

实现步骤:
1.添加Spring的jar依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.1.6.RELEASE</version>
</dependency>

2.编写配置文件,名字任意 spring-context.xml, 编写要创建的bean对象

<!--配置要创建bean的类-->
<!--id:称为对象的标识,在同一配置文件中id不能重复,因为创建的对象都在同一容器中,class:类的完整限定名-->
<bean id="ud" class="com.dl.dao.impl.UserDapImpl"></bean>
<bean id="us" class="com.dl.service.impl.UserServiceImpl" scope="singleton">
       <!--给UserServiceImpl对象的属性通过setUserDao()给userDao属性进行赋值-->
       <!--在配置文件中给属性赋值,称为DI,就是依赖注入-->
       <!--注入方式:设值注入,通过setXXX()方法赋值-->
       <property name="userDao" ref="ud"></property>
</bean>

3.通过ApplicationContext来获取Spring创建的bean
ClassPathXmlApplicationContex 工厂类

ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-context.xml");
//根据配置的id值获取
UserDao userDao=(UserDao) applicationContext.getBean("ud");
System.out.println("通过Spring获取的bean地址:"+userDao);
userDao.insertUser();

【结论】Spring创建的Bean 默认是单例模式
scope=“prototype” bean的作用域,默认是单例模式
prototype 原型模式,调用一次创建一个对象
singleton 单例模式

5、Spring注入bean的几种方式

方式1:set注入 设值注入,通过setXXX()方法进行注入

<bean id="ud" class="com.qf.springpro2001.dao.impl.UserDaoImpl"></bean>
<bean id="us" class="com.qf.springpro2001.service.impl.UserServiceImpl" scope="singleton">
    <!--给UserServiceImpl对象的属性通过setUserDao()给userDao属性进行赋值-->
    <!--在配置文件中给属性赋值,称为DI,就是依赖注入-->
    <!--注入方式:设值注入,通过setXXX()方法赋值-->
    <property name="userDao" ref="ud"></property>
</bean>

方式2:构造注入 通过构造函数进行注入

<bean id="zhaoyan" class="com.qf.springpro2001.test.Student">
    <!--构造注入:通过构造函数给属性赋值,要求类必须有对应的构造函数-->
    <!--name:形参的名字    value:形参的值-->
    <constructor-arg name="stuNo" value="java101"></constructor-arg>
    <constructor-arg name="stuName" value="赵"></constructor-arg>
    <constructor-arg name="stuSex" value="男"></constructor-arg>
</bean>

方式3:自动注入 在bean标签里加 autowire=“byName”
要求对象的id值和属性的名字要相同
byName 同名自动赋值
byType 同一类型自动赋值

<!--自动注入   id值和对象的属性名一致-->
<bean id="userService" class="com.qf.springpro2001.service.impl.UserServiceImpl" autowire="byType"></bean>

6、在spring中BeanFactory 、FactoryBean 区别?

  • BeanFactory:Bean工厂,生产创建Bean,ClassPathXmlApplicationContext Bean工厂之一
  • FactoryBean:是一个复杂的Bean /类,实现了FactoryBean接口的类,称为FactoryBean

7、Bean的创建时间:

1)饿汉模式,在启动BeanFactory时就创建Bean。默认的就是饿汉模式:提前创建好Bean ,随用随取。
2)懒汉模式,在启动BeanFactory时不创建Bean。调用getBean()时,才去创建Bean,在beans标签中设置:default-lazy-init=“true”。
【说明】为了节省内存推荐懒汉模式

8、Bean的作用域

  • singleton : bean在每个Spring ioc 容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

9、Bean的生命周期

从创建到销毁的中间的各个过程
两个重要阶段:

  •  bean的初始化 new之后被调用
  •  bean的销毁

实现方式1:通过配置,调用自定义的初始化方法及销毁方法
                    init-method=“myInitBook” destroy-method=“myDestroyBook”
实现方式2:Bean 直接实现初始化及销毁的相关接口,实现对应接口的方法接口,自动调用
                  implements InitializingBean, DisposableBean
无需设置 init-method=“myInitBook” destroy-method=“myDestroyBook”
实现方式3:注解
单例模式Bean的生命周期: 创建-》构造函数-》调用set方法赋值-》初始化方法
-》使用bean->销毁(bean工厂关闭销毁经即可)

多例模式Bean的生命周期:创建-》构造函数-》调用set方法赋值-》初始化方法
-》使用bean->等待垃圾回收机制GC进行销毁

10、代理设计模式(静态代理/动态代理)

1、静态代理:代理固定的类型
2、动态代理 :
动态代理实现有两种方式:

总结:jdk 和 cglib二者的区别

1、jdk利用反射机制生成一个代理接口的匿名类,在调用具体方法前,使用InvocationHandler接口,Cglib 利用ASM框架,对代理类 通过修改其字节码生成子类来处理。
2、【强调】jdk动态代理只能对实现了接口的类生成代理,不能直接针对类,cglib可以直接针对类实现代理。
3、什么时候使用cglib 什么时候使用jdk动态代理?

  • 目标代理对象实现了接口,默认使用jdk动态的代理,也可以指定使用cglib动态代理。
  • 如果没有实现接口,必须采用cglib动态代理。

4、效率上

  • jdk6以后 由于对jdk动态代理进行优化,所有当调用次数较少时,jdk效率高
  • 大量调用时cglib效率高
  • jdk8及以后 jdk效率高于cglib

【特别强调】cglib不能对final类及final修饰的方法进行代理
sping框架,同时使用了jdk cglib动态代理

11、Spring Aop的实现

1.Aop解决的问题:

​ 把编程的重点放在核心业务上,把“公共的”业务,进行配置,配置后,在执行核心业务的同时,公共业务能够自动执行, 从而减轻了业务的复杂度。

2.Aop原理:动态代理,当执行到核心功能时,代理去执行“公共的”业务。

​ 作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

3.Aop相关概念

切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

连接点:把程序中的核心方法 进行封装 JoinPoint,在“公共的”业务中,通过连接点对象(JoinPoint)就可以获取核心方法信息。

切入点:Pointcut 就是核心业务方法。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

通知/增强Advice:在不同的时间点所执行的“公共”业务,称为通知或增强

执行的时间点不同,增强/通知就不同

  • 在执行核心方法前,所执行的公共业务,称为前置增强/通知
<aop:before method=“beForeOper” pointcut-ref=“insertUserPointCut”/> 
  • 在执行核心方法后,所执行的公共业务,称为后置增强/通知
<aop:after-returning method=“afterOper” pointcut-ref=“insertUserPointCut returning=“result”/>
  • ​ 在执行核心方法发生异常时,所执行的公共业务,称为 异常处理增强
<aop:after-throwing method=“catchExecption” pointcut-ref=“insertUserPointCut”
 throwing=“ex”/>
  • ​ 在执行核心方法的finally语句,执行的公共业务,称为 最终增强
<aop:after method=“runFinally” pointcut-ref=“insertUserPointCut”/>

环绕增强:以上4种增强的统称,简化了以上4种增强。

目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
  • 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
<aop:aspect ref="doException">
    <!--织入:把增强方法和切入点(类)组合在一起的过程-->
    <!--织入异常处理通知-->
    <!--织入后,springAop创建代理对象Proxy-->
    <aop:after-throwing method="catchExecption" pointcut-ref="insertUserPointCut"
    throwing="ex"/>

    <!--织入最终增强方法-->
    <aop:after method="runFinally" pointcut-ref="insertUserPointCut"/>

</aop:aspect>

4.SpringAop的使用步骤
1、添加Aop的依赖
2、在spring的配置文件中,添加aop相关的命名空间
3、编写核心方法

  • UserDao :interface insertUser()
  • UserDaoImpl:class 实现方法
  • ​ UserService:interface insertUser()
  • ​ UserServiceImpl:class 实现方法

4、添加log4j的相关配置信息
5、编写公共业务方法–单独提取出来编写,异常处理 日志记录,aop
6、在配置文件中进行配置

12、环绕增强 重点

环绕增强:实现了4种增强,进行了简化

public class Arround {
    private final Logger logger=Logger.getLogger(Arround.class);
    //环绕增强只需要一个方法即可
    public Object doArround(ProceedingJoinPoint jp){
        //存放切入点的返回值
        Object result=null;
        //编写相当于前置增强要执行的代码
        logger.debug("环绕增强前置---执行了"+jp.getTarget()+"类里的"+jp.getSignature()+"方法"+
                "方法的参数是:"+ Arrays.toString(jp.getArgs()));
        try{
            //执行核心方法,切入点对应的方法
            result=jp.proceed();
            //后置增强
            logger.debug("环绕增强后置---"+jp.getSignature()+"方法被执行,返回值为:"+result);
        }catch (Throwable e) {
            //相当于异常处理增强
            logger.error("环绕增强异常处理---"+jp.getSignature()+"方法发生异常,异常信息如下:"+e.getMessage());
            e.printStackTrace();
        }finally {
            //最终增强
            logger.debug("环绕曾强强最终处理---"+jp.getSignature()+"执行结束");
        }
        return result;
    }
}

 

<!--配置切入点-->
        <asp:pointcut id="updateUserPointCut"
                      expression="execution(public int updateUser(String,int))"></asp:pointcut>

 

<!--配置环绕增强-->
        <aop:aspect ref="arround">
            <aop:around method="doArround" pointcut-ref="updateUserPointCut"></aop:around>
        </aop:aspect>

13、增强/通知 实现接口的方式,实现Aop

aop:after-throwing   异常处理增强
aop:after            最终增强
aop:before           前置增强
aop:after-returning  后置增强
aop:around           环绕增强    

代理通过标签的读取,在什么时间执行什么方法

另一种方式,通过实现接口来决定执行时间点

  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterAdvice finally语句块中
  • 后置通知:AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值
  • 异常通知:ThrowsAdvice
  • 环绕通知:MethodInterceptor

14、通用表达式 重要

采用通用的方式,去声明切入点,简化了配置

通用方式语法:

execution(访问修饰符 返回值类型(必填) 所在的包和类 方法名(必填)(参数类型列表) )

1)、execution(* com.qf.springpro2001.service.impl.inserUser())

返回值类型任意的 com.qf.springpro2001.service.impl包下的insertUser无参方法 是切入点

2)、execution( void com…*.updateUser())

访问修饰符任意的 返回值类型是void的com包下的所有类,及其子包下所有类的updateUser无参方法是切入点

3)、execution(* save(…))

访问修饰符任意的,返回值任意的,任意包下的save方法, 方法参数是任意

4)、executiion(* *(…))

所有方法

execution(public * com.qf.springpro2001.service.impl..*.*(..))"
修饰符是public的   com.qf.springpro2001.service.impl包及其子包下的所有类的所有方法   

15、后置处理Bean

目前的Bean的声明周期:

创建bean->构造函数->set()->检验是否实现初始化接口->检验是否配置了init-method->使用->检验是否实现了销毁接口->是否配置了destroy-method

JDK1.8开始BeanPostProcessor接口里的方法已经在接口中实现,无需在实现类中再重写

在JDK1.8之前,实现类必须实现BeanPostProcessor接口中的方法

16、Spring+mybatis进行整合

1、新建项目

2、添加jar依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

<!-- Spring整合mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>

<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>

<!-- mysql驱动 依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.25</version>
    <scope>runtime</scope>
</dependency>

<!-- 连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

3、改变 映射文件的存放位置

<build>
    <!-- 更改maven编译规则 -->
    <resources>
        <resource>
            <!-- 资源目录 -->
            <directory>src/main/java</directory>
            <includes>
                <include>*.xml</include><!-- 默认(新添加自定义则失效) -->
                <include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

4、编写数据库连接的配置文件 jdbc.properties配置文件

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_shopping?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
jdbc.init=1
jdbc.minIdle=1
jdbc.maxActive=3

5、编写spring配置文件 spring-context.xml

        <!--数据库配置信息文件的位置-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--原来在Mybatis中配置数据源连接池信息,整合后,拿到spring中配置-->
    <!--配置Druid连接池:详细参数配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--通过设值注入给属性赋值-->
        <!--基本配置-->
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>

        <!--初始化连接数量   最小空闲数量   最大活跃数量-->
        <property name="initialSize" value="${jdbc.init}"></property>
        <property name="minIdle" value="${jdbc.minIdle}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>

        <!--配置获取连接的超时的时间-->
        <property name="maxWait" value="60000"></property>
        <!--配置检查的时间间隔:检查需要关闭的空闲连接的时间间隔,单位ms-->
        <property name="timeBetweenEvictionRunsMillis" value="60000"></property>

        <!--配置连接池中最小生存的时间-->
        <property name="minEvictableIdleTimeMillis" value="30000"></property>
    </bean>

    <!--创建mybatis操作数据库是需要的SqlSeesionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--为其注入属性值-->
        <property name="dataSource" ref="dataSource"></property>
        <!--告知Mybatis接口对应的映射文件的位置-->
        <property name="mapperLocations">
            <list>
                <value>classpath:com/qf/sam/dao/*.xml</value>
            </list>
        </property>
        <!--配置实体类的别名-->
        <property name="typeAliasesPackage" value="com.qf.sam.entity"></property>
     </bean>
    <!--配置MapperCannerConfigurer  在dao中通过此配置就可以得到sqlSession-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--dao接口所在的包:如果接口在多个包中,可以使用逗号进行分割-->
<!--        <property name="basePackage" value="com.qf.sam.dao,com.qf.sam.entity"></property>-->
        <property name="basePackage" value="com.qf.sam.dao"></property>
        <!--配置得到sqlSession需要的sqlSessionFactory
           【说明】引入另一个对象时,需要使用ref进行引入,但是此处通过value引入
        -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

6、编写dao及mapper.xml映射文件,和以往没有任何变化

7、编写service及实现类

//利用springIoC实现了解耦
private DeptDao deptDao;

public void setDeptDao(DeptDao deptDao) {
this.deptDao = deptDao;
}

public boolean insertDept(Dept dept) {
int result=deptDao.insertByBach(dept);
if (result==1){
return true;
}else{
return false;
     }
}

8、spring-context.xml配置文件中

<bean id="ds" class="com.qf.sam.service.impl.DeptServiceImpl">
    <!--mybatis创建的dao的对象,deptDao
    dao接口名字的首字母小写-->
    <property name="deptDao" ref="deptDao"></property>
</bean>

9、测试


public static void main(String[] args) {
    ApplicationContext applicationContext=new
            ClassPathXmlApplicationContext("spring-context.xml");
    DeptService ds=(DeptService)applicationContext.getBean("ds");
    Dept dept=new Dept();
    dept.setDeptName("软件开发超级俱乐部");
    dept.setDeptLoc("软件园15号楼");
    boolean result=ds.insertDept(dept);
    if (result){
        System.out.println("添加成功!");
    }else {
        System.out.println("添加失败!");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值