Spring

Spring 简介

1. 是什么

1. 轻量级,最受欢迎的企业级 Java 应用程序开发框架
2. Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。

2. Spring Framework特性 :

  • 非侵入式 : 使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅
  • 控制反转 : IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入
  • 面向切面编程 : 在不修改源代码的基础上增强代码功能
  • 容器 : Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率 (eg. bean 的生命周期hhh)
  • 组件化 : Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统
  • 声明式 : 很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现 (eg.事务)
  • 一站式 : 在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 SpringFramework 的基础上全部使用 Spring 来实现

3. 具体功能模块

功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器
AOP&Aspects面向切面编程
Testing提供了对 junit 或 TestNG 测试框架的整合
Data Access/Integration提供了对数据访问/集成的功能
Spring MVC提供了面向Web应用程序的集成功能

IOC

1. demo

1、什么是 IOC
    (1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
    (2)使用 IOC 目的: 为了耦合度降低

2、DI
      DI:Dependency Injection,翻译过来是依赖注入。
      DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
      所以结论是:
          IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

3、IOC 底层原理
    (1)xml 解析、工厂模式、反射
    
4、IOC容器在Spring中的实现
	 Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。
	 Spring 提供了 IOC 容器的两种实现方式:
	 
           (1)BeanFactory :
                    这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
        
           (2)ApplicationContext :
                    BeanFactory 的子接口, 提供了更多高级特性. 
                    面向 Spring 的使用者, 几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory.

ApplicationContext的主要实现类 :
在这里插入图片描述

2. coding

2.1 引入需要的 jar

<dependencies> 
	<!--springMVC jar-->
	<!--会引入 Spring 的很多相关 jar-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>5.3.16</version>
	</dependency>

	<!--lombok jar-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>

	<!--test jar--> 
	<dependency> 
		<groupId>junit</groupId> 
		<artifactId>junit</artifactId>
		<version>4.13.2</version> 
		<scope>test</scope> 
	</dependency> 
	
</dependencies>

2.2 JavaBean

  • Address
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private String address;
}

  • Student
@Data
@ToString
public class Student {
    private String name;
    private Address address;
    private String[] hobbies;
    private List<String> books;
    private List<Address> list;
    private Map<String, String> info;
    private Object obj;
    private Properties nuo;
}

2.3 xml


	<!-- 引入外部属性文件 --> 
	<context:property-placeholder location="classpath:Db.properties"/>
	
    <!--scope="" 作用域-->
    <!--默认单例 singleton-->
    <!--原型类型 prototype-->
    <!--在创建容器的时候并没有实例化,而是当 getBean() 时才会去创建一个对象,而且每次获取到的对象都不是同一个对象-->
    <!--...-->
    
    <!--p(属性: properties)命名空间-->
    <bean id="address" class="com.nuo.pojo.Address" p:address="nuo"/>

    <!--c(构造: Constructor)命名空间-->
    <!--<bean id="address" class="com.nuo.pojo.Address" c:address="nuo"/>-->
    
    <!--setter 注入-->
    <bean id="student" class="com.nuo.pojo.Student">
    <!--autowire="byName"按名字自动装配-->
    <!--autowire="byType"按类型自动装配-->
    
    	<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --> 
    	<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
    	
        <!--<property name="name" value="nuo"/>-->
        <!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 --> 
        <!-- 解决方案一:使用XML实体来代替 -->
        <!--<property name="name" value="a &lt; b"/>-->
        <property name="name">
         	<!-- 解决方案二:使用CDATA节(CD) --> 
         	<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 --> 
         	<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 --> 
         	<!-- 所以CDATA节中写什么符号都随意 --> 
         	<value><![CDATA[a < b]]></value> 
         </property>
         
         <!--1. ref-->
        <!--<property name="address" ref="address"/>-->
        
        <!--2. 内部 bean-->
        <property name="address"> 
        	<!-- 在一个bean中再声明一个bean就是内部bean --> 
        	<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 --> 
        	<bean id="address" class="com.nuo.pojo.Address">
        		<property name="address" value="nuo"/>
        	</bean>
        </property>
        
        <!--3. 级联属性赋值-->
        <!--一定先引用某个bean为属性赋值,才可以使用级联方式更新属性-->
        <!--相当于重赋值-->
        <!--<property name="address" ref="address">-->
        <!--<property name="address.address" value="nuo"></property>-->
        
        <!--list-->
        <property name="books">
            <list>
                <value>Chinese</value>
                <value>math</value>
                <value>English</value>
            </list>
        </property>

        <!--arr-->
        <property name="hobbies">
            <array>
                <value>run</value>
                <value>swimming</value>
            </array>
        </property>


        <!--null-->
        <!--<property name="obj" value=""/>-->
        <property name="obj">
            <null/>
        </property>

        <!--map-->
        <property name="info">
            <map>
                <entry key="age" value="3"/>
            </map>
        </property>

        <!--prop-->
        <property name="nuo">
            <props>
                <prop key="nuo1">1</prop>
                <prop key="nuo2">2</prop>
            </props>
        </property>
        
        <!--list集合类型的bean--> 
        <util:list id="list"> 
        	<ref bean="address"></ref> 
        </util:list>

    </bean>
    


2.4 获取bean

  1. 通过 id
  2. 通过 type
  3. 通过 id & type
  4. if 同类型 bean 不唯一,通过 type 获取时会报错 : org.springframework.beans.factory.NoUniqueBeanDefinitionException
  5. 如果组件类实现了接口,根据接口类型可以获取 bean , 前提是bean唯一
  6. 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型不可以获取 bean ,因为bean不唯一
  7. 结论 => 根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到
public class TestDemo {
    @Test
    public void demo(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student);
    }
}

2.5 Bean 的生命周期

4、bean 的后置处理器,bean 生命周期有七步

(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
 --(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
 --(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("☆☆☆" + beanName + " = " + bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("★★★" + beanName + " = " + bean);
        return bean;
    }
}

<!-- bean的后置处理器要放入IOC容器才能生效 -->
 <bean id="myBeanProcessor" class="com.nuo.process.MyBeanProcessor"/>

2.6 FactoryBean

	FactoryBean是Spring提供的一种整合第三方框架的常用机制。
	和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。
	通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
	将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

3. 自动装配

3.1 基于 xml 的自动装配

<bean id="cat" class="com.nuo.pojo.Cat"/>
<bean id="dog" class="com.nuo.pojo.Dog"/>

<bean id="people" class="com.nuo.pojo.People" autowire="byType"/>

3.2 基于注解管理bean

  1. 开启注解支持
 <!--组件扫描-->
	<context:component-scan
            base-package="com.nuo"> 
        <!-- context:exclude-filter标签:指定排除规则 --> <!--type:设置排除或包含的依据 type="annotation",根据注解排除,expression中设置要排除的注解的全类名 type="assignable",根据类型排除,expression中设置要排除的类型的全类名 -->
        <!--include-filter 扫描什么 -->
        <!--exclude-filter 不扫描什么 -->
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/> 
        <!--<context:exclude-filter type="assignable" expression="com.nuo.controller.UserController"/>-->
    </context:component-scan>
    
<context:annotation-config/>

3.3 基于 annotation 的自动装配

@Autowired
3.3.1 @Autowired工作流程
1. bytype
2. byname
3. if 没有该类型的 bean : 装配失败
4. if 有该类型的 bean, 不止一个
	-> 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
		-> 能够找到:执行装配
		->找不到:装配失败 
	-> 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配
		-> 能够找到:执行装配
		-> 找不到:装配失败

【PS】:
	1. @Autowired 中有属性 required,默认值为 true,因此在自动装配无法找到相应的bean时,会装配失败, 并报错

	2. 可以将属性 required 的值设置为false,则表示能装就装,装不上就不装,此时自动装配的属性为默认值

	3. 但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。

AOP => 代理模式

1. 静态代理

代理模式
    - 静态代理

        抽象角色 : 一般使用接口或者抽象类来实现
        真实角色 : 被代理的角色
        代理角色 : 代理真实角色 ; 代理真实角色后,一般会做一些附属的操作.
        客户     : 使用代理角色来进行一些操作.

        eg.真实角色:房东 代理:中介 接口:出租 我:客户

            分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代
        理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

        静态代理的好处:
            - 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
            - 公共的业务由代理来完成 . 实现了业务的分工 ,
            - 公共业务发生扩展时变得更加集中和方便 .

        缺点 :
            - 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

        我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !


2. 动态代理

public class ProxyFactory {
    private final Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {

        /* newProxyInstance():
         * 创建一个代理实例
         * 其中有三个参数:
         *     1、classLoader:加载动态生成的代理类的类加载器
         *     2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         *     3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接 口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /* proxy:代理对象 
                 * method:代理对象需要实现的方法, 即其中需要重写的方法 
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] " + method.getName() + ",参 数:" + Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] " + method.getName() + ",结 果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] " + method.getName() + ",异 常:" + e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] " + method.getName() + ",方法 执行完毕");
                }
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

3. 基于注解的 AOP

动态代理(InvocationHandler):
	1. JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
	2. cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
	3. AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
<!-- spring-aspects会帮我们传递过来aspectjweaver --> 
<dependency> 
	<groupId>org.springframework</groupId> 
	<artifactId>spring-aspects</artifactId> 
	<version>5.3.1</version> 
</dependency>
// @Aspect表示这个类是一个切面类 
@Aspect 
// @Component注解保证这个切面类能够放入IOC容器
@Component

public class LogAspect {
    @Before("execution(public int com.nuo.aop.annotation.CalculatorImpl.* (..))")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:" + methodName + ",参 数:" + args);
    }

    @After("execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:" + methodName);
    }

    @AfterReturning(value = "execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:" + methodName + ",结 果:" + result);
    }

    @AfterThrowing(value = "execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
    }

    @Around("execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前"); //目标对象(连接点)方法的执行 
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
}
<!--基于注解的AOP的实现: 
1、将目标对象和切面交给IOC容器管理(注解+扫描) 
2、开启AspectJ的自动代理,为目标对象自动生成代理 
3、将切面类通过注解@Aspect标识 
--> 
<context:component-scan base-package="com.nuo.aop.annotation"> 
</context:component-scan> <aop:aspectj-autoproxy />

3. 基于 xml 的 AOP

<context:component-scan base-package="com.nuo.aop.xml"></context:component- scan>
    <aop:config> <!--配置切面类-->
        <aop:aspect ref="loggerAspect">
            <aop:pointcut id="pointCut" expression="execution(* com.nuo.aop.xml.CalculatorImpl.*(..))"/>
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturningMethod" returning="result"
                                 pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
        <aop:aspect ref="validateAspect" order="1">
            <aop:before method="validateBeforeMethod" pointcut-ref="pointCut"></aop:before>
        </aop:aspect>
    </aop:config>

事务

1. 编程式


Connection conn = ...; 
try {

	// 开启事务: 关闭事务的自动提交 
	conn.setAutoCommit(false); 
	
	// 核心操作: 提交事务 
	conn.commit(); 
	
}catch(Exception e){
 
	// 回滚事务 
	conn.rollBack(); 
	
}finally{

	// 释放数据库连接 
	conn.close(); 
	
}

2. 声明式

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
	<property name="dataSource" ref="dataSource"></property>
</bean> 
<!--开启事务的注解驱动 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 --> 
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就 是这个默认值,则可以省略这个属性 --> 
<tx:annotation-driven transaction-manager="transactionManager" />

// 只读
@Transactional(readOnly = true)
// 超时回滚,释放资源
@Transactional(timeout = 3)

3. 事务隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

隔离级别一共有四种:

1. 读未提交:READ UNCOMMITTED:
	允许Transaction01读取Transaction02未提交的修改。

2. 读已提交:READ COMMITTED:
	要求Transaction01只能读取Transaction02已提交的修改。

3. 可重复读:REPEATABLE READ:
	确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

4. 串行化:SERIALIZABLE:
	确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力各种数据库产品对事务隔离级别的支持程度

@Transactional(isolation = Isolation.DEFAULT)			//使用数据库默认的隔离级别 
@Transactional(isolation = Isolation.READ_UNCOMMITTED)	//读未提交 
@Transactional(isolation = Isolation.READ_COMMITTED)	//读已提交 
@Transactional(isolation = Isolation.REPEATABLE_READ)	//可重复读 
@Transactional(isolation = Isolation.SERIALIZABLE)		//串行化

4. 事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本

5. 基于XML的声明式事务

	<aop:config> 
        <!-- 配置事务通知和切入点表达式 -->
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.nuo.service.impl.*.*(..))"></aop:advisor>
    </aop:config> 
    <!-- tx:advice标签:配置事务通知 --> 
    <!-- id属性:给事务通知标签设置唯一标识,便于引用 --> 
    <!-- transaction-manager属性:关联事务管理器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes> 
            <!-- tx:method标签:配置具体的事务方法 --> 
            <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="find*"
                       read-only="true"/> 
            <!-- read-only属性:设置只读属性 --> 
            <!-- rollback-for属性:设置回滚的异常 --> 
            <!-- no-rollback-for属性:设置不回滚的异常 --> 
            <!-- isolation属性:设置事务的隔离级别 --> 
            <!-- timeout属性:设置事务的超时属性 --> 
            <!-- propagation属性:设置事务的传播行为 -->
            <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
            <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
            <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        </tx:attributes>
    </tx:advice>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值