Spring知识点

为什么要学?

        Spring技术时JavaEE开发必备技能,企业开发技术选型命中率>90%

        专业角度:        1、简化开发        2、框架整合

学什么?

        简化开发:

                IOC

                AOP

                        事务处理

        框架整合:

                MyBatis、MyBatis-plus、Struts、Struts2、Hibernate...

怎么学?

        学习Spring框架设计思想

        学习基础操作,思考操作与思想间的关系

        学习案例,熟练应用操作的同时,体会思想

初始Spring:

        官网:spring.io 

        Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能

Spring Framework系统架构:

 Spring Framework学习路线:

        核心概念:

                代码书写现状:耦合度偏高

                解决方案:使用对象时,在程序中不要主动使用new产生对象,转换为外部提供对象

                IOC(Inversion of control)控制反转

                        对象的创建控制权由程序转移到外部,这种思想称为控制反转

                Spring技术对Ioc思想进行了实现

                        Spring提供了一个容器,称为Ioc容器,用来充当Ioc思想中的“外部” 

                        Ioc容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在Ioc容器中统称为Bean

                        DI(Dependency Injection)依赖注入

                                在容器中建立bean和bean之间的依赖关系的整个过程,称为依赖注入

        Ioc入门案例:

                1、导入spring的坐标spring-context,对应的版本是5.2.10.RELEASE              

        2、配置bean
           bean标签表示配置bean
           id属性表示给bean起名字
           class属性表示给bean定义类型
              <bean id="bookDao" class="com.zsb.dao.impl.BookDaoImpl"/>
              <bean id="bookService" class="com.zsb.service.impl.BookServiceImpl"/>

                3、获取Ioc容器        

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

                4、获取bean

                BookDao bookDao = (BookDao) ctx.getBean("bookDao");
                bookDao.save();

        DI入门案例:

        1、删除业务层中使用new的方式创建的dao对象
        2、提供对应的set方法
                public void setBookDao(BookDao bookDao){
                    this.bookDao = bookDao;
                }
        3、配置service 与 dao的关系
               property标签表示配置当前bean的属性
               name表示配置哪一个具体的属性
               ref表示参照哪一个具体的bean

        bean别名配置:        名称:name        

                                          类型:属性

                                          所属:bean标签

                                          功能:定义bean的别名,可定义多个,使用(,)分号(;)空格( )分隔

                                          范例:

<bean id="bookDao" name="dao bookDaoImpl" class="com.zsb.dao.impl.BookDaoImpl"/>

                                获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常

NoSuchBeanDefinitionException

        bean作用域配置:        名称:scope

                                              类型:属性

                                              所属:bean标签

                                              功能:定义bean的作用范围,可选范围如下:

                                                        singleton:单例(默认)        

                                                        prototype:非单例

                                              范例:

<bean id="bookDao" name="dao bookDaoImpl" class="com.zsb.dao.impl.BookDaoImpl" scope="singleton"/>

                                为什么bean默认为单例?

                                        为了缓解内存压力

                                        适合交给容器进行管理的bean:

                                                表现层对象、业务层对象、数据层对象、工具对象

                                        不适合交给容器进行管理的bean                

                                                封装实体的域对象

        实例化bean的三种方式——构造方法(常用)

                提供可访问的构造方法        无参构造方法如果不存在,将抛出异常BeanCreationException

                配置:

<bean id="bookDao" class="com.zsb.dao.impl.BookDaoImpl"/>

        实例化bean的三种方式——静态工厂(了解)

                静态工厂:

public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

                配置:

<bean id="orderDao" class="com.zsb.factory.OrderDaoFactory" factory-method="getOrderDao"/>

        实例化bean的三种方式——实例工厂(了解)

                实例工厂:

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

                配置:

<bean id="userFactory" class="com.zsb.factory.UserDaoFactory"/>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>

        实例化bean的第四种方式——FactoryBean(实用)

                FactoryBean:

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代理原始实例工厂中创建对象的方法
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }


    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

                配置:

<bean id="userDao" class="com.zsb.factory.UserDaoFactoryBean"/>

        bean生命周期:

                生命周期:从创建到消亡的全整过程

                bean生命周期:bean从创建到销毁的整体过程

                bean生命周期控制:在bean创建后到销毁前做一些事情

                        1、提供生命周期控制方法:                  

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save...");
    }

    public void init(){
        System.out.println("book dao init...");
    }

    public void destory(){
        System.out.println("book dao destory...");
    }
}
<bean id="bookDao" class="com.zsb.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

                        2、接口控制(了解):

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {

    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }

    public void setBookDao(BookDao bookDao){
        this.bookDao = bookDao;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("book service destory");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("book service init");
    }
}

                bean销毁时机:

                        容器关闭前出发bean的销毁

                        关闭容器方式:

                                手动关闭容器

                                        ConfigurableApplicationContext接口close()操作

                                注册关闭钩子,在虚拟机退出前关闭容器再退出虚拟机

                                        ConfigurableApplicationContext接口registerShutdownHook()操作

        依赖注入方式:

                思考:向一个类中传递数据的方式有几种?

                        普通方法(set方法)

                        构造方法

                思考:依赖注入描述了容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?

                        引用类型

                        简单类型(基本数据类型与String)

                依赖注入方式:

                        setter注入:

                                简单类型

                                        在bean中定义简单类型属性并提供可访问的set方法

                                        配置中使用property标签value属性注入简单类型数据

                                引用类型

                                        在bean中定义引用类型属性并提供可访问的set方法

                                        配置中使用property标签ref属性注入引用类型对象

                        构造器注入:

                                简单类型

                                        在bean中定义简单类型属性并提供可访问的构造方法

                                        配置中使用constructor-arg标签value属性注入简单类型对象

                                引用类型

                                        在bean中定义引用类型属性并提供可访问的构造方法

                                        配置中使用constructor-arg标签ref属性注入引用类型对象

                                        配置中使用constructor-arg标签type属性设置按形参类型注入

                                        配置中使用constructor-arg标签index属性设置按形参位置注入

                        依赖注入方式选择:

                                1、强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现

                                2、可选依赖使用setter注入进行,灵活性强

                                3、Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

                                4、如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入

                                5、实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入

                                6、自己开发的模块推荐使用setter注入

                依赖自动装配:

                        Ioc容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程成为自动装配

                        自动装配方式:

                                按类型(常用)

                                按名称

                                按构造方法

                                不启用自动装配

                                依赖中使用bean标签autowire属性设置自动装配的类型:        

<bean id="bookService" class="com.zsb.service.impl.BookServiceImpl" autowire="byType"/>

                                

                        依赖自动装配特征:

                                自动装配用于引用类型依赖注入,不能对简单类型进行操作

                                使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用

                                使用按名称装配时(byName)必须保障容器中有具有指定名称的bean,因变量名与配置耦合,不推荐使用

                                自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

                集合注入:

                        array注入

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

                        list注入

<property name="list">
    <list>
        <value>hello</value>
        <value>world</value>
        <value>wuhu</value>
    </list>
</property>

                        set注入

<property name="set">
    <set>
        <value>hi</value>
        <value>wo</value>
        <value>hi</value>
    </set>
</property>

                        map注入

<property name="map">
    <map>
        <entry key="country" value="中国"/>
        <entry key="province" value="江苏"/>
        <entry key="city" value="泰州"/>
    </map>
</property>

                        properties注入

<property name="properties">
    <props>
        <prop key="country">中国</prop>
        <prop key="province">江苏</prop>
        <prop key="city">泰州</prop>
    </props>
</property>

        加载properties文件: 

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd
 ">

<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>

<bean id="bookDao" class="com.zsb.dao.impl.BookDaoImpl">
    <property name="name" value="${username}"/>
</bean>

                不加载系统属性:

<context:property-placeholder location="jdbc,properties" system-properties-mode="NEVER"/>

                加载多个properties文件:

<context:property-placeholder location="jdbc.properties, msg.properties"/>

                加载所有properties文件:

<context:property-placeholder location="*.properties"/>

                加载properties文件标准格式:

<context:property-placeholder location="classpath:*.properties"/>

                从类路径或jar包中搜索并加载properties文件:

<context:property-placeholder location="classpath*:*.properties"/>

                创建容器:

                        方式一:类路径加载配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

                        方式二:文件路径加载配置文件

ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\study\\Java\\code\\spring-demo\\spring_10_container\\src\\main\\resources\\applicationContext.xml");

                        方式三:加载多个配置文件

Application ctx = nwe ClassPathCmlApplicationContext("bean1.xml", "bean2.xml");

                获取bean:

                        方式一:使用bean名称获取:

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

                        方式二:使用bean名称获取并指定类型:

BookDao bookDao = ctx.getBean("bookDao", BookDao.class);

                        方式三:使用bean类型获取:

BookDao bookDao = ctx.getBean(BookDao.class);

                BeanFactory初始化(了解)            

Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);

BookDao bookDao = (BookDao) bf.getBean("bookDao");
bookDao.save();

                        BeanFactory创建完毕后,所有的bean均为延迟加载

        注解开发定义bean

                使用@Component定义bean       

@Component("bookDao")
public class BookDaoImpl implements BookDao {

    @Override
    public void save() {
        System.out.println("book dao save...");

    }
}

                核心配置文件中通过组件扫描加载bean

<context:component-scan base-package="com.zsb"/>

                

                Spring提供@Component注解的三个衍生注解:

                        @Controller:用于表现层bean定义

                        @Service:用于业务层bean定义

                        @Repository:用于数据层bean定义

        纯注解开发:

                Spring3.0开启了纯注解开发模式,使用Java类代替配置文件,开启了Spring快速开发赛道

                读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象

                @Configuration
                @ComponentScan("com.zsb")
                public class SpringConfig {
                }

                        @Configuration注解用于设定当前类为配置类

                        @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式

                                例如:@ComponentScan({"com.zsb.dao","com.zsb.service"})

//加载配置文件初始化容器

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

//加载配置类初始化容器

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

                        

                        依赖注入:

                                使用@Autowired注解开启自动装配模式(按类型)

                                

@Service("bookService")
public class BookServiceImpl implements BookService {
    @Autowired
    @Qualifier("bookDao2")
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
}

                                注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法

                                注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法

                                使用@Qualifier注解开启指定名称装配bean

@Service("bookService")
public class BookServiceImpl implements BookService {
    @Autowired
    @Qualifier("bookDao2")
    private BookDao bookDao;

                                注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用

                                使用@Value实现简单类型注入

                        

                        加载properties文件:

                                使用@propertySource注解加载properties文件

@Configuration
@ComponentScan("com.zsb")
@PropertySource("{jdbc.properties}")
public class SpringConfig {
}

                                注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*

                        第三方bean管理:

                                使用@Bean配置第三方bean

                                       

public class JdbcConfig {
    //1、定义一个方法获得要管理的对象
    //添加@Bean,表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("${jdbc.driverClassName}");
        ds.setUrl("${jdbc.url}");
        ds.setUsername("${jdbc.username}");
        ds.setPassword("${jdbc.password}");
        return ds;
    }
}

                                将独立的配置类加入核心配置:

                                        方式一:导入式

public class JdbcConfig {
    //1、定义一个方法获得要管理的对象
    //添加@Bean,表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("${jdbc.driverClassName}");
        ds.setUrl("${jdbc.url}");
        ds.setUsername("${jdbc.username}");
        ds.setPassword("${jdbc.password}");
        return ds;
    }
}

                                        使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式

@Configuration
@PropertySource({"jdbc.properties"})
@Import({JdbcConfig.class})
public class SpringConfig {

    
}

                                        方式二:扫描式:

@Configuration

public class JdbcConfig {
    //1、定义一个方法获得要管理的对象
    //添加@Bean,表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("${jdbc.driverClassName}");
        ds.setUrl("${jdbc.url}");
        ds.setUsername("${jdbc.username}");
        ds.setPassword("${jdbc.password}");
        return ds;
    }
}

                                        使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息

                

@Configuration
@PropertySource({"jdbc.properties"})
@ComponentScan({"com.zsb.config","com.zsb.service","com.zsb.dao"})
public class SpringConfig {

    
}

                                        1、第三方bean管理        @Bean

                                        2、第三方bean依赖注入

                                                引用类型:方法形参

                                                简单类型:成员变量

                AOP:

                        AOP简介:AOP面向切面编程,一种编程范式,指导开发者如何组织程序结构

                                                OOP面向对象编程

                                作用:在不惊动原始设计的基础上为其进行功能增强

                                Spring理念:无入侵式/无侵入式

                        AOP核心概念:

                                连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

                                        在SpringAOP中,理解为方法的执行

                                切入点(Pointcut):匹配连接点的式子

                                        在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法

                                                一个具体方法:com.zsb.dao包下的BookDao接口中的无形参无返回值的save方法

                                                匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法

                                通知(Advice):在切入点处执行的操作,也就是共性功能

                                        在SpringAOP中,功能最终以方法的形式呈现

                                通知类:定义通知的类

                                切面(Aspect):描述通知与切入点的对应关系

                        入门案例:

                                1、导入aop相关坐标

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>

                                2、定义通知类,制作通知

public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

                                3、定义切入点

    @Pointcut("execution(void com.zsb.dao.BookDao.update())")
    private void pt(){}

                                4、绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

public class MyAdvice {

    @Pointcut("execution(void com.zsb.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

                        5、定义通知类受Spring容器管理,并定义当前类为切面类

@Component
@Aspect
public class MyAdvice {

    @Pointcut("execution(void com.zsb.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

                        6、开启Spring对AOP注解驱动支持

@Configuration
@ComponentScan("com.zsb")
@EnableAspectJAutoProxy
public class SpringConfig {
}

                        AOP工作流程:

                                1、Sprig容器启动

                                2、读取所有切面配置中的切入点

                                3、初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

                                        匹配失败,创建对象

                                        匹配成功,创建原始对象(目标对象)的代理对象

                                4、获取bean执行方法

                                        获取bean,调用方法并执行,完成操作

                                        获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

                        AOP核心概念:

                                目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

                                代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

                        AOP切入点表达式:

                                切入点:要进行增强的方法

                                切入点表达式:要进行增强的方法的描述方法

                                        切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

                                                动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点

                                                访问修饰符:public、private等,可以省略

                                                返回值

                                                包名

                                                类/接口名

                                                方法名        

                                                参数

                                                异常名:方法定义中抛出指定异常,可以省略

                                        可以使用通配符描述切入点,快速描述

                                                * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

execution (public * com.zsb.*.UserService.find*(*))

                                                .. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution (public User com..UserService.findById(..))

                                                +:专用语匹配子类类型

execution ( * *..*Service+.*(..))

                                        书写技巧:

                                                所有代码按照标准规范书写,否则以下技巧全部失效

                                                描述切入点通常描述接口,而不描述实现类

                                                访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)

                                                返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述

                                                包名书写尽量不适用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配

                                                接口名/类名书写名称与模块相关额采用*匹配,例如UserService书写成*Service,绑定业务层接口名

                                                方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll

                                                参数规则较为复杂,根据业务方法灵活调整

                                                通常不使用异常作为匹配规则

                                AOP通知类型

                                        AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置

                                        AOP通知共分为5种类型:

                                                前置通知

                                                后置通知

                                                环绕通知(重点)

 @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice...");
        //表示对原始操作的调用
        Object obj = pjp.proceed();
        System.out.println("around after advice...");
        return obj;
    }

                                                        1、环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知

                                                        2、通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

                                                        3、对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型

                                                        4、原始方法的返回值如果时void类型,通知方法的返回值类型可以设置成void,也可以设置成Object

                                                        5、由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象

                                                返回后通知(了解)

                                                抛出异常后通知(了解)

                                AOP通知获取数据:

                                        获取切入点方法的参数:        使用getArgs()方法

                                                JoinPoint:适用于前置、后置、返回后、抛出异常后通知

                                                ProceedJointPoint:适用于环绕通知

                                        获取切入点方法返回值:        

                                                返回后通知

 @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(JoinPoint jp, Object ret){

                                                环绕通知

                                        获取切入点方法运行异常信息

                                                抛出异常后通知

@AfterThrowing(value = "pt()", throwing = "t")
public void afterThrowing(Throwable t){

                                                环绕通知

                        Spring事务简介:

                                事务作用:在数据层保障一系列的数据库操作同成功同失败        

                                Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功或同失败

                        

                                案例:银行账户转账:

                                        1、在业务层接口上添加Spring事务管理

public interface AccountService {
    @Transactional
    public void transfer(String out, String in, Double money);
}

                                                注意:Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合

                                                        注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

                                        2、设置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

                                                注意:事务管理器要根据实现技术进行选择

                                                        MyBatis框架使用的是JDBC事务

                                        3、开启注解式事务驱动

@Configuration
@ComponentScan("com.zsb")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}

                        

                                Spring事务角色:

                                        事务角色:

                                                事务管理员:发起事务方,在Spring中通常指业务层开启事务的方法

                                                事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

                                事务传播行为:

                                        事务传播行为:事务协调员对事务管理员所携带事务的处理态度

                                案例:转账业务追加日志:

                                        1、在业务层接口上添加Spring事务,设置事务传播行为REQUIRES_NEW(需要新事务)

                                                

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值