【SSM框架】之Spring

SSM框架笔记(自用)-- Spring

Spring Framework系统架构

在这里插入图片描述

Spring程序开发步骤

在这里插入图片描述

核心概念

IoC( Inversion of Control)控制反转

使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转

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

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

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

DI ( Dependency Injection)依赖注入

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

在这里插入图片描述

**目标:**充分解耦

使用IoC容器管理bean (IoC)

在IoC容器内将有依赖关系的bean进行关系绑定(DI)

最终效果

使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

bean配置

创建Spring配置文件,配置对应类作为Spring管理的bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    1、导入spring的坐标spring-context,对应版本是5.2.7.RELEASE-->

<!--    2、配置bean-->
<!--    bean标签表示配置bean-->
<!--    id属性表示给bean起名字-->
<!--    class属性表示给bean定义类型-->
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
    <bean id="bookService" name="service service2 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
    <!--7.配置server与dao的关系-->
    <!--property标签表示配置当前bean的属性
    name属性表示配置哪一个具体的属性
    ref属性表示参照哪一个bean-->
        <property name="bookDao" ref="dao"/>
    </bean>
</beans>

注意事项:bean定义时id属性在同一个上下文中不能重复。

bean基础配置

类别描述
名称bean
类型标签
所属beans标签
功能定义Spring核心容器管理的对象
属性列表id : bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一;class : bean的类型,即配置的bean的全路径类名
范例bean id=“bookDao” class=“com.itheima.dao.imp1.BookDaoImpl” />
范例bean id=“bookService” class=“com.itheima. service.impl.BookServiceImpl”>

bean别名配置

类别描述
名 称name
类 型属性
所 属bean标签
功 能定义bean的别名,可定义多个,使用迹号(,)分号(;)空格()分隔
范 例bean id=“bookDao” name=“dao bookDaoImpl” class=“com.itheima.dao.imp1.BookDaoImpl” />
范 例bean name=“service,bookServiceImpl” class=“com.itheima. service.imp1.BookServiceImpl” />

注意事项:获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException

NoSuchBeanDefinitionException: No bean named ‘bookServiceImpl’ available

bean作用范围配置

类别描述
名 称scope
类 型属性
所 属bean标签
功 能定义bean的作用范围,可选范围如下:singleton:单例(默认) prototype :非单例
范 例bean id=“bookDao” class=“com.itheima.dao.imp1.BookDaoImpl” scope=“prototype”/>

bean相关

在这里插入图片描述

Bean实例化三种方式

使用无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
工厂静态方法实例化

工厂的静态方法返回Bean实例

public class StaticFactoryBean {
	public static UserDao createUserDao(){
		return new UserDaoImpl();
	}
}
<bean id="userDao" class="com.itheima.factory.StaticFactoryBean" factory-method="createUserDao" />
工厂实例方法实例化

工厂的非静态方法返回Bean实例

public class DynamicFactoryBean {
	public UserDao createUserDao(){
		return new UserDaoImpl();
	}
}
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>

bean的依赖注入

概念

依赖注入(Dependency Injection):它是 Spring 框架核心 IoC 的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

分析

在这里插入图片描述

在这里插入图片描述

构造方法

(1)setter注入

引用数据类型

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
	<property name="userDao" ref="userDao"></property>
</bean>

简单数据类型

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
</bean>

集合数据类型

public class BookDaoImpl implements BookDao {
    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String,String> map;
    private Properties properties;

    public void setArray(int[] array) {
        this.array = array;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public void setSet(Set<String> set) {
        this.set = set;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <!--数组注入-->
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <!--list集合注入-->
        <property name="list">
            <list>
                <value>itcast</value>
                <value>itheima</value>
                <value>boxuegu</value>
                <value>chuanzhihui</value>
            </list>
        </property>
        <!--set集合注入-->
        <property name="set">
            <set>
                <value>itcast</value>
                <value>itheima</value>
                <value>boxuegu</value>
                <value>boxuegu</value>
            </set>
        </property>
        <!--map集合注入-->
        <property name="map">
            <map>
                <entry key="country" value="china"/>
                <entry key="province" value="henan"/>
                <entry key="city" value="kaifeng"/>
            </map>
        </property>
        <!--Properties注入-->
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="province">henan</prop>
                <prop key="city">kaifeng</prop>
            </props>
        </property>
    </bean>
</beans>

(2)构造器注入

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
	<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>

依赖注入方式选择

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

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

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

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

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

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

Spring的重点配置

在这里插入图片描述

创建容器

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

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

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

ApplicationContext ctx = new FileSystemxm1ApplicationContext("D:\\applicationContext.xm1");

加载多个配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext ("bean1.xm1","bean2.xml");

容器相关

在这里插入图片描述

依赖注入相关

在这里插入图片描述

Spring注解开发

Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。

配置数据源

数据源(连接池)的开发步骤:
① 导入数据源c3p0和druid的坐标和数据库驱动坐标,步骤略。
② 创建数据源对象;
③ 设置数据源的基本连接数据;
④ 使用数据源获取连接资源和归还连接资源。

applicationContext.xml加载jdbc.properties配置文件获得连接信息。

首先,需要引入context命名空间和约束路径:
● 命名空间:xmlns:context=“http://www.springframework.org/schema/context”
● 约束路径:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

<?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: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">

Spring容器加载properties文件

<context:property-placeholder location="classpath*:*.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

纯注解开发

使用一个添加了 @configuration的config配置类来进行配置。

普通配置类注入

在这里插入图片描述

原始注解

在这里插入图片描述

使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。

<!--注解的组件扫描-->
<context:component-scan base-package="com.itheima"></context:componentscan>
  • 使用@Component或@Repository标识UserDaoImpl需要Spring进行实例化。
//@Component("userDao")
@Repository("userDao")   //@Component衍生注解
public class UserDaoImpl implements UserDao {
	@Override
	public void save() {
		System.out.println("save running... ...");
	}
}
  • 使用@Compont或@Service标识UserServiceImpl需要Spring进行实例化。
  • 使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入。
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
	/*
	@Autowired //按照数据类型从Spring容器中进行匹配的
	@Qualifier("userDao")  //是按照id值从容器中进行匹配的 但是注意@Qualifier要结合@Autowired一起使用
	*/
	@Resource(name="userDao")
	private UserDao userDao;
	@Override
	public void save() {
		userDao.save();
	}
}
  • 使用@Value进行字符串的注入。
@Repository("userDao")
public class UserDaoImpl implements UserDao {
	@Value("注入普通数据")
	private String str;
	@Value("${jdbc.driver}")
	private String driver;
	@Override
	public void save() {
		System.out.println(str);
		System.out.println(driver);
		System.out.println("save running... ...");
	}
}
  • 使用@Scope标注Bean的范围。
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
	//此处省略代码
}
  • 使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法。
@PostConstruct
public void init(){
	System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
	System.out.println("销毁方法.....");
}
Spring新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:

注解说明
@Configuration用于指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定Spring在初始化容器时要扫描的包。作用和在Spring 的xml 配置文件中的<context:component-scan base-package=“com.itheima” />一样
@Bean使用在方法上,标注将该方法的返回值存储到Spring 容器中
@PropertySource用于加载.properties文件中的配置
@lmport用于导入其他配置类
  • @Configuration
    @ComponentScan
    @Import
@Configuration  //标志该类是Spring的核心配置类
@ComponentScan("com.itheima")
@Import({DataSourceConfiguration.class})
public class SpringConfiguration {
}
  • @PropertySource
    @value
    @Bean
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {  //简单型依赖注入
	@Value("${jdbc.driver}")
	private String driver;
	@Value("${jdbc.url}")
	private String url;
	@Value("${jdbc.username}")
	private String username;
	@Value("${jdbc.password}")
	private String password;
}
//引用型依赖注入
@Bean(name="dataSource")  //Spring会将当前方法的返回值以指定名称存储到Spring容器中
public DataSource getDataSource() throws PropertyVetoException {
	ComboPooledDataSource dataSource = new ComboPooledDataSource();
	dataSource.setDriverClass(driver);
	dataSource.setJdbcUrl(url);
	dataSource.setUser(username);
	dataSource.setPassword(password);
	return dataSource;
}

Spring整合MyBatis

MyBatis程序核心对象分析

在这里插入图片描述

整合MyBatis

在这里插入图片描述

Spring整合JUnit

整合步骤

① 导入spring集成Junit的坐标(spring5 及以上版本要求 junit 的版本必须是 4.12 及以上–>
② 使用@Runwith注解替换原来的运行期
③ 使用@ContextConfiguration指定配置文件或配置类
④ 使用@Autowired注入需要测试的对象
⑤ 创建测试方法进行测试

代码实现

① xml配置文件导入spring和junit坐标,代码略。

② 使用@Runwith注解替换原来的运行期

@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}

③ 使用@ContextConfiguration指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}

④ 使用@Autowired注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
	@Autowired
	private UserService userService;
}

⑤ 创建测试方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
	@Autowired
	private UserService userService;
	@Test
	public void testUserService(){
		userService.save();
	}
}

在这里插入图片描述

面向切面编程AOP

简介

AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

作用及优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
优势:减少重复代码,提高开发效率,并且便于维护。

核心概念

  • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的

  • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行

  • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述

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

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

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

  • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法

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

  • 目标对象(Target):被代理的原始对象成为目标对象

在这里插入图片描述

切入点表达式

表达式标准格式
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
访问修饰符(描述通配符)

● 访问修饰符可以省略
● 返回值类型、包名、类名、方法名可以使用星号* 代表任意
● 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
● 参数列表可以使用两个点 …表示任意个数,任意类型的参数列表

例如:

execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
AOP切入点表达式书写技巧
  1. 所有代码按照标准规范开发,否则以下技巧全部失效;

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

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

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

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

  6. 接口名/类名书写名称与模块相关的采用*匹配,

    例如UserService书写成*Service,绑定业务层接口名

  7. 方法名书写以动词进行精准匹配﹐名词采用*配﹐

    例如getByld书写成getBy*,selectAll书写成selectAll;

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

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

基于注解的AOP开发

开发步骤

① 创建目标接口和目标类(内部有切点)
② 创建切面类(内部有增强方法)
③ 将目标类和切面类的对象创建权交给 spring
④ 在切面类中使用注解配置织入关系
⑤ 在配置文件中开启组件扫描和 AOP 的自动代理
⑥ 测试

代码实现

① 创建目标接口和目标类(内部有切点)

public interface TargetInterface {
    public void save();
}
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running.....");
    }
}

② 创建切面类(内部有增强方法)

public class MyAspect {
	//前置增强方法
    public void before(){
        System.out.println("前置增强..........");
    }
}

③ 将目标类和切面类的对象创建权交给 spring

@Component("target")
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running.....");
        //int i = 1/0;
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置增强..........");
    }
}

④ 在切面类中使用注解配置织入关系

@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
    //配置前置通知
    @Before("execution(* com.itheima.anno.*.*(..))")
    public void before(){
        System.out.println("前置增强..........");
    }
}

⑤ 在配置文件中开启组件扫描和 AOP 的自动代理

<!--组件扫描-->
<context:component-scan base-package="com.itheima.anno"></context:component-scan>

<!--aop自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

⑥ 测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

⑦ 测试结果

前置增强..........
save running.....
注解配置AOP
注解通知类型
名称注解说明
前置通知@Before用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知@AfterReturning用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知@Aronud用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知@AfterThrowing用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知@After用于配置最终通知。无论增强方式执行是否有异常都会执行
切点表达式的抽取

在切面内定义方法,在该方法上使用@Pointcut 注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
    //Proceeding JoinPoint:  正在执行的连接点===切点
    //@Around("execution(* com.itheima.anno.*.*(..))")
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强....");
        Object proceed = pjp.proceed();//切点方法
        System.out.println("环绕后增强....");
        return proceed;
    }

    //@After("execution(* com.itheima.anno.*.*(..))")
    @After("MyAspect.pointcut()")
    public void after(){
        System.out.println("最终增强..........");
    }

    //定义切点表达式
    @Pointcut("execution(* com.itheima.anno.*.*(..))")
    public void pointcut(){}
}
@Around注意事项

1.环绕通知必须依赖形参ProceedingloinP?oint才能实现对原始方法的调用﹐进而实现原始方法调用前后同时添加通知;

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

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

4.原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Ooject;

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

Spring事务

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

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

编程式事务控制相关对象

PlatformTransactionManager

Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager:

在这里插入图片描述

方法说明
Transactionstatus getTransaction (TransactionDefination defination)获取事务的状态信息
void commit (Transactionstatus status)提交事务
void rollback (Transactionstatus status)回滚事务

注意:

PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,

例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager

Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

TransactionDefinition

TransactionDefinition 是事务的定义信息对象,里面有如下方法:

方法说明
int getIsolationLevel()获得事务的隔离级别
int getPropogationBehavior ( )获得事务的传播行为
int getTimeout ()获得超时时间
boolean isReadOnly ()是否只读
TransactionStatus

TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

方法说明
boolean hasSavepoint ()是否存储回滚点
boolean isCompleted ()事务是否完成
boolean isNewTransaction ()是否是新事务
boolean isRollbackonly ()事务是否回滚

事务角色

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

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

进行提交与回滚,事务(转账操作中:A钱-,B钱+)作为一个整体,一旦部分执行不成功,能够整个回滚,从而确保若:A-成功,B+失败后,A,B都将回滚,恢复原状态。

在这里插入图片描述

事务传播行为

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

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科研达人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值