Spring AOP及Spring声明式事务管理

Spring aop 及 Spring 声明式事务管理

1 什么是AOP

所谓面向切面编程,就是我希望在目标对象(有无接口都可以)上扩展原有代码的功能,但可以在不修改源码的基础上来完成!!!

  • AOP编程是什么?AOP编程有哪些应用场景?

    • AOP 面向切面编程
    • 日志的处理、权限处理、事务管理
  • 什么是连接点、切入点、通知、切面,代理对象、目标对象?

    • 连接点: 被代理对象的所有方法都被称作为连接点
    • 切入点: 被代理的方法就称作为切入点
    • 通知: 增强的代码我们就称作为通知
    • 切面: 切面= 切入点+ 通知

1. AOP编程有哪些步骤?

在这里插入图片描述

  • 导入依赖: aop\ aspectjweaver

  • 编写目标对象

  • .创建自定义的切面类(增强的代码)(切面类就是一些增强的方法

  • bean.xml配置,配置出一个切面

       <!--1. 创建目标对象与切面对象-->
            <!--  目标对象  -->
            <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
            <!--切面对象-->
            <bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/>
    
        <!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。-->
      <aop:config>
          <aop:aspect ref="切面对象">
              <!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法-->
              <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
              <!--2.2 切面 = 通知+切入点     -->
              <aop:before method="切面对象的方法" pointcut-ref="pt"/>
          </aop:aspect>
      </aop:config>
    
    <!--1. 创建目标对象与切面对象-->
        <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
        <bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/>
    
        <!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。-->
      <aop:config>
          <aop:aspect ref="logAspect">
            
              <!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法-->
              <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
              
              
              <!--2.2 切面 = 通知+切入点     -->
              <!--前置通知-->
              <aop:before method="before" pointcut-ref="pt"/>
              <!--后置通知-->
              <aop:after-returning method="afterReturn" pointcut-ref="pt"/>
              <!--异常通知-->
              <aop:after-throwing method="agfterException" pointcut-ref="pt"/>
              <!--最终的通知-->
              <aop:after method="afterFinal" pointcut-ref="pt"/>
              
                <!--配置环绕通知-->
              <aop:around method="around" pointcut-ref="pt"/>
              
              
          </aop:aspect>
      </aop:config>
    

2 Aop编程切入点表达式

1 切入点表达式

用于声明要在目标对象的哪些方法进行切入通知!!

法:
execution(修饰符 返回值类型 包名.类名.方法名(形参))

语法: execution( 修饰符 返回值类型 包路径.类名称.方法名称( 参数列表类型 ) )

  • execution(* com.itheima.service.impl.UserServiceImpl.*(…))
  • 修饰符的写法
    • 明确具体的修饰符
    • 省略不写
  • 返回值类型写法
    • 明确类型
    • 使用通配符
  • 包名
    • 明确具体的包结构
    • 匹配一级目录 *
    • 匹配多级目录 *…*
  • 类名
    • 明确类名
    • 使用通配符
  • 方法名
    • 明确方法名
    • 使用通配符
  • 形参类型
    • 明确形参的类型
    • 使用… 通配符
 2.1 切入点表达式 :
          语法:
             execution(修饰符 返回值类型  包名.类名.方法名(形参))

1. 修饰符:
a.明确修饰符,   <aop:pointcut id="pt" expression="execution(public * com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.省略修饰符的。  <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>

2. 返回值类型:(返回值类型是不能省略)
a.明确具体类型 : <aop:pointcut id="pt" expression="execution(void com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.通配符:       <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>

3. 包名:
.明确具体的包:  <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.使用通配符匹配一级目录 *:     <aop:pointcut id="pt" expression="execution(* com.*.service.impl.UserServiceImpl.*(..))"/>
c.使用通配符匹配多级目录  *..* :<aop:pointcut id="pt" expression="execution(* com.*..*.impl.UserServiceImpl.*(..))"/>


4. 类名:
a.明确具体的类名 :      <aop:pointcut id="pt" expression="execution(* com.*..*.impl.UserServiceImpl.*(..))"/>
b.使用通配符 :         <aop:pointcut id="pt" expression="execution(* com.*..*.impl.*Impl.*(..))"/>

5. 方法名:
a.明确具体的方法名 : <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.delete(..))"/>
 b.使用通配符 :    <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
 
6. 形参:
a. 明确具体的类型: <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.update(int,String))"/>

b. 使用通配符:    <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.update(..))"/>


3. Aop编程通知类型

01 通知的类型

前置通知:在执行目标对象方法之前执行 aop:before

后置通知:在执行目标对象方法之后执行 (异常不执行) aop:after-returning

异常通知:在执行目标对象方法里面发生异常时候执行 aop:after-throwing

最终通知:在执行完目标对象方法后始终执行的方法就是最终通知(异常也执行) aop:after

  • 前置通知 aop:before
  • 后置通知 aop:afterTeturning
  • 异常通知 aop:throwing
  • 最终通知 aop:after

try{

​ 前置通知

​ 执行目标方法

​ 后置通知

}catch(Exception e){

​ 异常通知

}finally{

​ 最终通知

}

切面类

在这里插入图片描述

bean.xml配置文件

在这里插入图片描述




02 aop:around 环绕通知

环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
那么ProceedingJoinPoint则带update方法.

ProceedingJoinPoint代表了什么?如何让目标方法执行

  • 代表了当前的方法对象
  • ProceedingJoinPoint.proceed()

在LogAspect切面类添加环绕通知方法


public class LogAspect {

    
    /*
        环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
        那么ProceedingJoinPoint则带update方法。
     */
//    环绕通知
    public void around(ProceedingJoinPoint pj){
        
            pj.proceed();
      
     }
}



<!--1. 创建目标对象与切面对象-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>

    <bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/>

    <!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。-->
  <aop:config>
      <aop:aspect ref="logAspect">
          <!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法-->
          <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>

          <!--配置环绕通知-->
          <aop:around method="around" pointcut-ref="pt"/>

      </aop:aspect>
  </aop:config>

4 Aop(XML+注解)

在这里插入图片描述

注解方式的aop编程需要使用哪些注解, 每个注解的作用是什么?

  • @Aspect 代表是一个切面 切面= 通知+切入点
  • @PointCut() @Pointcut(“execution(* com.itheima.service.impl.UserServiceImpl.*(…))”)
  • @Before(“切入点方法名”) ,@AfterReturning (“切入点方法名”),@AfterThrowing(“切入点方法名”) ,@After(“切入点方法名”),@Around(“切入点方法名”)。
01 bean.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--1. 创建目标对象与切面对象-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>

    <!--component-scan这个标签只能扫描: @Respository、 @Service\@Controller、Component这些注解而已,对于aop的注解没法扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--开启aop的注解扫描-->
    <aop:aspectj-autoproxy/>

</beans>

02 切面类LogAspect上添加注解
/*
@Aspect: 代表是一个切面    切面= 通知+切入点

 */

@Component
@Aspect
public class LogAspect {

    //切入点表达式
            /*
                <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
                id:则为该方法的方法名,当前切入点的id为pt
             */
    @Pointcut("execution(* com.itheima.service.impl.UserServiceImpl.*(..))")
    public void pt(){}


    /*
        环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
        那么ProceedingJoinPoint则带update方法。
     */
    
    
    //    前置通知
    @Before("pt()")
    public void before(){
        System.out.println("前置通知..");
    }

//    后置通知
    @AfterReturning("pt()")
    public void afterReturn(){
        System.out.println("后置通知");
    }


//    异常通知
    @AfterThrowing("pt()")
    public void agfterException(){
        System.out.println("异常通知..");
    }


//    最终通知
    @After("pt()")
    public void afterFinal(){
        System.out.println("最终通知..");
    }


    
  /*   
    
    // 环绕通知
    @Around("pt()")
    public void around(ProceedingJoinPoint pj){
        try {
            System.out.println("====前置通知===");
            //让目标方法执行执行方法
            pj.proceed();
            System.out.println("获取方法执行的实参:"+ Arrays.toString(pj.getArgs()));
            System.out.println("======后置通知=======");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("=======异常通知========");
        } finally {
            System.out.println("=====最终通知=========");
        }
    }
    */
}

5 Aop纯注解开发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNBObAjH-1609830946803)(assets\1587291022015.png)]

@ComponentScan(“com.itheima”) 扫描 ioc 相关的注解的 @Respository @Service @Controller @component这些注解的

@EnableAspectJAutoProxy 扫描aop相关的注解的。

@Configuration 该注解的作用代表了该类是一个配置类,用于取代bean.xml文件的。

@PropertySource(“classpath:db2.propertes”) 用于指定加载配置文件的。

@Bean(“dataSource”) 方法上一旦添加 @Bean(“dataSource”)注解,那么该方法会自动执行,并且把该方法 的返回值存储到容器中。

/*
@ComponentScan("com.itheima")  扫描@Respository @Service @Controller @component这些注解的。
@EnableAspectJAutoProxy 扫描aop相关的注解的。
 */
@Configuration    //代表该类是个配置类。用于取代Bean.xml文件
@ComponentScan("com.itheima") 
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

6. 事务的概念

什么是事务,事务的四大特性是什么?

  • A(原子性)C(一致性) I(隔离性) D( 持久性)
1. 什么是事务?

事务,一组最小单位的执行单元(一组SQL语句),这个最小单位要么一起成功,要么一起失败!!

2.事务的四个特性(ACID)?

原子性(A):如果使用事务控制对操作进行控制,这些操作要么一起成功,要么一起失败!!

一致性A):事务可以让数据库从一个一致性状态切换到另一个一致性的状态(转账总额不变)

隔离性A):多个并发事务之间应该相互隔离。

​ 如果多个并发事务隔离出问题,可能导致以下现象:

​ 1)脏读:一个事务读到另一个未提交的数据(必须防止的)

​ 2)不可重复读:一个事务读到另一个已经提交的更新(update)数据

​ 3)幻读(虚读) : 一个事务读到另一个已经提交的插入(insert)数据

​ 可以通过设置数据库的隔离级别,防止以上的现象的发现:

​                            脏读 不可重复读 幻读(虚读)

​ read uncommited   N          N         N

​ read committed      Y          N         N

​ repeatable read      Y         Y         N

​ serilizable                Y         Y         Y

持久性(D):事务一旦提交(commit),数据应该永久保存下来。

注意:

​ 1)mysql的默认隔离级别:repeatable read

​ oracle的默认隔离级别: read committed

​ 2)隔离级别越高,事务并发性能越差

7 Spring事务管理

Spring事务管理:

第一: JavaEE 体系进行分层开发, 事务控制位于业务层, Spring 提供了分层设计业务层
的事务处理解决方案。

第二: spring 框架为我们提供了一组事务控制的接口。

第三:Spring的事务管理,分为 声明式事务管理 与 编程式事务管理器。

​ Spring事务管理分为:

​ 声明式事务管理,底层是AOP思想(重点掌握)

​ 编程式事务管理,底层不是AOP思想(了解)

  • spring要求大家进行事务控制在service层
  • spring事务的类别
    • 声明式事务管理
    • 编程式事务管理
01 回顾JDBC事务管理:

​ 开启事务: connection.setAutoCommit(false)

​ 提交事务:connection.commit()

​ 回滚事务: connection.rollback();

JDBC业务操作

public void UserServiceImpl{
    
    public void save(){
        //获取连接
        Connection conn = JDBCUtils.getConnection();
        
        //====开启事务(取消自动提交)======
        conn.setAutoCommit(false);
        
        try{
            //预编译sql语句
            String sql = "insert into user(username,age) vales(?,?)";
            PreparedStatement stmt = conn.preparedStatement(sql);

            //设置参数
            stmt.setParameter(1,'张三');
            stmt.setParameter(2,20);

            //执行SQL
            stmt.executeUpdate();
            
            //====提交事务====
            conn.commit()
        }catch(Exception e){
            e.printStackOver();
             
            === 回滚事务======
            conn.rollback();    
        }finally{
               //连接放回连接池
           JDBCUtils.releaseConn(conn);
        }
     
    }
    
    
}



8 Spring 声明式事务 XML方式(重点)

propagation:事务传播行为,REQUIRED代表了一定要有事务, SUPPORTS代表事务可有可无

01 创建事务管理器的对象

(切面对象,增强的代码):



<!--创建切面对象,spring已经为我们提供好了事务管理器,我们目标是要把事务管理器的代码添加到service方法上面-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>



02 配置事务管理的 通知规则

(指定哪些方法需要事务,哪些方法不需要事务)

 <!--2. 是事务管理器配置通知的规则:为哪些方法配置事务-->
    <tx:advice id="adive 引用通知规则" transaction-manager="transactionManager" >
        <tx:attributes>
            
  <!--  &lt;!&ndash;spring在匹配一个方法事务要事务的时候是从上往下匹配的,只有匹配到其中一个,那么就不会再往下去执行了。&ndash;&gt;

            <tx:method name="find*" propagation="SUPPORTS"/>
            <tx:method name="get*" propagation="SUPPORTS"/>
            <tx:method name="query*" propagation="SUPPORTS"/>
            <tx:method name="select*" propagation="SUPPORTS"/>

            &lt;!&ndash;所有的方法必须要有事务&ndash;&gt;
            <tx:method name="*" propagation="REQUIRED"/>
-->
            <!--事务的规则:哪些方法是需要事务管理的。-->
            <tx:method name="save" isolation="DEFAULT" propagation="REQUIRED" />
         <!--  propagation:事务传播行为,REQUIRED代表了一定要有事务, SUPPORTS代表事务可有可无-->
            <tx:method name="findAll" isolation="DEFAULT" propagation="SUPPORTS" />
        </tx:attributes>
    </tx:advice>

03 配置一个切面
    <!-- 3.事务切面 = 通知+切入点  -->
    <aop:config>

      <!--切入点表达式-->
      	 <aop:pointcut id="pt"  expresssion="execution(切入点表达式)"/>
        
     <!-- 通知+切入点 -->
     	 <aop:advisor   adivise-ref="adive  引用通知规则" 
                      	pointcut-ref="切入点表达式引用  pt"/>
	</aop:config>




04 完整 applicationContext.xml
  <!-- Spring声明式事务  -->
    <!-- 1.事务管理器  封装事务管理代码 commit,rollbac-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 2.事务通知  -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 事务特性 -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 3.事务切面 = 通知+切入点  -->
    <aop:config>
         <!-- 3.1 切入点 -->
        <aop:pointcut id="pt" expression="execution(* com.itheima.service.*ServiceImpl.*(..))"/>
        
        <!-- 3.2 通知+切入点 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

9 Spring声明式事务(XML+注解)

Spring声明式事务改造为注解方式

注意:事务管理器依然还是bean来配置!!!

在业务类(service层)上添加@Transactional 注解

修改bean.xml开启事务注解扫描.

在这里插入图片描述

01 实现步骤

1.事务管理器依然还是bean来配置!!!

2.在业务类上添加 @Transactional 注解 英[trænˈzækʃn]

​ service层

3.修改applicationContext.xml开启事务注解扫描.

 <!--扫描事务管理器   专门用于扫描transactional-->
    <tx:annotation-driven transaction-manager="配置文件上 事务管理的id(事务切面对象)"/>
02 @Transactional注解的细节:
/**
 * 账户Service实现
 *  @Transactional 注解的使用
 *      1.位置问题
 *         1.1 业务类上: 对类的所有方法添加事务控制(推荐使用)
 *         1.2 业务类的方法上: 对某个类的方法添加事务控制
 *         1.3 业务接口上: 对接口声明的所有方法添加事务控制
 *         1.3 业务接口的方法上: 对接口声明的指定方法添加事务控制
 *
 *         注意:类和方法同时存在,优先使用方法上的
 *      2.属性问题
 *           transactionManager: 引用的事务管理器的id
 *               默认@Transactional根据类型进行注入
 *               如果有多个类型才会按照名称进行注入
 *           value: 和transactionManager的值一样的
 *
 *           isolation: 设置事务的隔离级别
 *           propagation: 设置事务的传播行为
 *           rollbackFor: (理论)针对某些异常进行事务回滚  (实际测试,所有异常都可以进行事务控制 )
 *           noRollbackFor:  针对某些异常不进行事务回滚 (实际测试是OK的)
 */
@Transactional(isolation = Isolation.DEFAULT,
              propagation = Propagation.REQUIRED,
              noRollbackFor = NullPointerException.class )  // 事务注解
public class AccountServiceImpl implements AccountService{
    
}

10. Spring声明式事务管理 零配置(纯注解)

@EnableTransactionManagement //开启事务管理的扫描 扫描@Transactional
@Import(配置类名.class) // 导入其他的配置类的。

在这里插入图片描述

01 编写配置类,添加相应注解
@Configuration
@ComponentScan("com.itheima")//
@EnableTransactionManagement    //开启事务管理的扫描 扫描@Transactional
@Import(JdbConfig.class)        // 导入其他的配置类的。
public class SpringConfigration {

}
02 编写JdbcConfig配置类
@PropertySource("classpath:db2.properties")
public class JdbConfig {

    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;


    @Bean("dataSource")
    public DataSource createDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(username);
        dataSource.setUrl(url);
        dataSource.setPassword(password);
        return  dataSource;
    }

    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    //    创建事务管理器
    @Bean
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }


}

11 Spring编程式事务【了解】

使用Spring编程式事务,意思就是用写代码的方式直接在业务类加入Spring事务控制!

bean.xml

<!--1. 创建目标对象+切面对象-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--事务管理模板-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="TransactionManager"/>
    </bean>

在业务方法上编码实现事务控制



@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    //创建事务管理模板类
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    public void save(Account account) {
        //事务控制器业务逻辑 ,  在doInTransaction方法内部的代码会收到事务管理器的管理
        TransactionCallback callback = new TransactionCallback(){

            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                accountDao.save(account);
                int result = 1/0;
                accountDao.save(account);
                return null;
            }
        };
        transactionTemplate.execute(callback);
    }


    @Override
    public void findAll() {
        System.out.println("查找所有..");
    }



    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}

测试



@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    //创建事务管理模板类
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    public void save(Account account) {
        //事务控制器业务逻辑 ,  在doInTransaction方法内部的代码会收到事务管理器的管理
        TransactionCallback callback = new TransactionCallback(){

            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                accountDao.save(account);
                int result = 1/0;
                accountDao.save(account);
                return null;
            }
        };
        transactionTemplate.execute(callback);
    }


    @Override
    public void findAll() {
        System.out.println("查找所有..");
    }



    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值