spring学习

spring学习

1.概述

2.核心知识

3.优点

4.注解开发

4.1组件注册

1.新建maven项目,导入依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

2.创建一个类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private String name;
    private int age;

}

3.创建一个配置类

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
public class MainConfig {

    //给容器中注册一个Bean,类型为返回值的类型,id默认是是使用方法名作为id
    @Bean
    public User user(){
        return new User("吴杰",20);
    }
}

4.2@ComponentScan自动扫描组件和指定扫描规则

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
@ComponentScan(
        value = "com.wxit",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
        classes = {Controller.class})})
//ComponentScan   value  指定要扫描的包
//excludeFilter= Filter[],指定扫描的时候按照什么规则排除那些组件
//includeFilters=Filter[]  指定扫描的时候只需要包含那些组件
public class MainConfig {

    //给容器中注册一个Bean,类型为返回值的类型,id默认是是使用方法名作为id
    @Bean
    public User user(){
        return new User("吴杰",20);
    }
}

4.3自定义TypeFilter指定过滤规则

public class MyTypeFilter implements TypeFilter {


    /**
     *
     * @param metadataReader    读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory     可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

        //获取当前类的注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        //获取当前正在扫描的类的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();

        //获取当前类的资源 (类的路径)
        Resource resource = metadataReader.getResource();

        String className = classMetadata.getClassName();
        System.out.println("----" + className);

        if (className.contains("er")){
            return true;
        }
        return false;
    }
}

测试

public class IocTest {

    @Test
    @SuppressWarnings("resource")
    public void test(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

4.4@Scope设置组件作用域

@Configuration
public class MainConfig2 {

    /**
     *prototype : 多实例的,IOC容器启动并不会去调用方法创建对象放在容器中,每次获取的时候才会调用方法创建对象
     *singleton:单实例,IOC容器启动会调用方法创建对象到IOC容器中,以后每次获取就是直接从容器(map.get())中拿
     * request:同一次请求创建一个实例
     * session:同一个session创建一个实例
     * 
     */
    @Scope("prototype")
    @Bean("user")
    public User user(){
        System.out.println("给容器中添加user");
        return new User("吴昊",20);
    }
}

测试

    @Test
    public void test02(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//        String[] definitionName = applicationContext.getBeanDefinitionNames();
//        for (String name : definitionName) {
//            System.out.println(name);
//        }

        System.out.println("ioc 容器创建完成");

        Object bean1 = applicationContext.getBean("user");
        Object bean2 = applicationContext.getBean("user");
        System.out.println(bean1 == bean2);
    }

4.5@Lazy懒加载

@Configuration
public class MainConfig3 {

    /**
     *
     * 懒加载:
     *      单实例bean:默认在容器启动的时创建对象
     *      懒加载:容器启动不创建对象,第一次使用(获取)bean,创建对象,并初始化
     */

    @Bean("user")
    @Lazy
    public User user(){
        System.out.println("给容器中添加user对象");
        return new User("李婷",23);
    }
}

测试代码

    @Test
    public void test03(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);

        Object bean1 = applicationContext.getBean("user");
        System.out.println("ioc容器创建完成");
    }

4.6@Conditional按照条件注册bean

@Configuration
public class MainConfig3 {

    /**
     *
     * 懒加载:
     *      单实例bean:默认在容器启动的时创建对象
     *      懒加载:容器启动不创建对象,第一次使用(获取)bean,创建对象,并初始化
     *
     * @Condition:按照一定的条件进行判断,满足条件给容器中注册bean
     *
     *
     */

    @Bean("user")
    @Lazy
    public User user(){
        System.out.println("给容器中添加user对象");
        return new User("李婷",23);
    }

    @Bean("bili")
    @Conditional({WindowsCondition.class})
    public User user1(){
        return new User("Bill Gates",62);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public User user2(){
        return new User("linus",51);
    }
}

筛选条件为计算机系统

public class WindowsCondition implements Condition {

    /**
     *
     * @param context  判断条件,能使用的上下文
     * @param annotatedTypeMetadata     注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {

        //1.能获取到ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        //2.获取类加载器
        ClassLoader classLoader = context.getClassLoader();

        //3.获取当前环境信息
        Environment environment = context.getEnvironment();

        //4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        String property = environment.getProperty("os.name");
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {

        Environment environment = context.getEnvironment();

        String property = environment.getProperty("os.name");
        if (property.contains("Linux")){
            return true;
        }
        return false;
    }
}

测试代码

    @Test
    public void test04(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
        String[] namesForType = applicationContext.getBeanNamesForType(User.class);
        //动态获取环境变量的值
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        String property = environment.getProperty("os.name");
        System.out.println(property);
        for (String name : namesForType) {
            System.out.println(name);
        }

        Map<String, User> ofType = applicationContext.getBeansOfType(User.class);
        System.out.println(ofType);
    }

4.7使用FactoryBean注册组件

待补充

5.生命周期

bean的生命周期:

​ bean创建 -----初始化-------销毁的过程

容器管理bean的生命周期:

​ 我们可以自定义初始化初始化和销毁方法,容器bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法

5.1指定初始化和销毁方法

构造(对象创建)

​ 单实例 : 在容器启动的时候创建对象

​ 多实例: 在每次获取的时候创建对象

初始化:

​ 对象创建完成,并赋值好,调用初始化方法

销毁:

​ 单实例:容器关闭的时候

​ 多实例: 容器不会管理这个bean,容器不会调用销毁方法

代码示例

@Configuration
public class MainConfigOfLifeCycle {

    @Bean(initMethod = "init",destroyMethod = "destroy")
    @Scope("prototype")
    public Car car(){
        return new Car();
    }
}


public class Car {

    public Car(){
        System.out.println("car -----");
    }

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

    public void destroy(){
        System.out.println("car  destroy");
    }
}

//测试代码
public class IocTestLifeCycle {

    @Test
    public void test01(){
        //1创建ioc容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完成");

        Object bean = applicationContext.getBean("car");

        applicationContext.close();
    }
}

5.2让bean实现InitializingBean(定义初始化逻辑)DisposableBean(定义销毁)

@Component
public class Cat implements InitializingBean, DisposableBean {

    public Cat(){
        System.out.println("cat 创建了");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("我会在容器关闭的时候调用");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("我会在bean创建完成,并且属性都赋好之后调用");
    }
}


@Configuration
@ComponentScan("com.wxit.bean")
public class MainConfigOfLifeCycle {

    @Bean(initMethod = "init",destroyMethod = "destroy")
    @Scope("prototype")
    public Car car(){
        return new Car();
    }
}

5.3@PostConstruct和@PreDestroy

@PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化方法

@PreDestroy:在容器销毁bean之前通知我们进行清理工作

代码示例

@Component
public class Dog {

    public Dog(){
        System.out.println("dog构造器");
    }

    //对象创建并赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("Dog-----@PostConstruct");
    }

    //容器移除对象之前
    @PreDestroy
    public void detory(){
        System.out.println("dog-----@PreDestroy");
    }
}

5.4BeanPostProcessor后置处理器

BeanPostProcessor:为接口,bean的后置处理器

postProcessBeforeInitialization:在初始化之前工作

postProcessAfterInitialization:在初始化之后工作

示例代码

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

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

5.5BeanPostProcessor原理

遍历得到容器中所有的BeanPostProcessor,挨个执行BeforeInitialization,一旦返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessBeforeInitialization

5.6BeanPostProcessor在spring底层的使用

bean赋值: 注入其他组价,@Autowired,生命周期注解功能,@Async,BeanPostProcessor

代码示例

@Component
public class Dog implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Dog(){
        System.out.println("dog构造器");
    }

    //对象创建并赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("Dog-----@PostConstruct");
    }

    //容器移除对象之前
    @PreDestroy
    public void detory(){
        System.out.println("dog-----@PreDestroy");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

6.属性赋值

使用@Value赋值

1.基本数值

2.可以写SpEl,#{}

3.可以写${},取出配置文件中的值(在运行环境变量里面的值)

public class User {

    @Value("吴杰")
    private String name;

    @Value("#{22-2}")
    private int age;

}

4.加载外部配置文件

@Configuration
@PropertySource(value = {"classpath:/user.properties"},encoding = "gbk")
public class MainConfigOfPropertyValues {

    @Bean
    public User user(){
        return new User();
    }
}

public class User {

    @Value("吴杰")
    private String name;

    @Value("#{22-2}")
    private int age;

    @Value("${user.nickName}")
    private String nickName;

}

7.自动装配

7.1自动装配:

​ spring利用依赖注入,完成对ioc容器中各个组件的依赖关系赋值

@Autowired:自动注入

​ 1.默认优先按照类型去容器中找对应的组件,

​ 2.如果找到多个相同类型的组件,再将属性的名称作为组件id去容器中查找

​ 3.@Qualifier:指定需要装配的组件id,而不是属性名

​ 4.自动装配默认一定要将属性赋值好,没有就会报错

​ 可以使用@Autowired(required = false)

​ 5.@Primar让spring自动进行自动装配的时候,默认使用首选的bean,

@Configuration
@ComponentScan({"com.wxit.service","com.wxit.dao","com.wxit.controller"})
public class MainConfigOfAutowired {

    @Bean("bookDao2")
    @Primary
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLable("2");
        return bookDao;
    };
}


public class TestAutowired {

    @Test
    public void test01(){
        //创建ioc容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
        System.out.println("容器创建完成");

        BookService bean = applicationContext.getBean(BookService.class);
        System.out.println(bean);

//        BookDao bookDao = applicationContext.getBean(BookDao.class);
//        System.out.println(bookDao);

        applicationContext.close();
    }
}

7.2方法,构造器位置的自动装配

@Autowired:位置:构造器,方法,属性,都是从容器中获取参数组件的值

1.标在方法位置

2.标在构造器上:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还可以自动从容器中获取

3.放在参数位置

代码示例

@Component
public class Boss {

    private Car car;

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }

    @Autowired
    public Boss(Car car){
        this.car = car;
        System.out.println("Bose有参构造器");
    }

    public Car getCar() {
        return car;
    }

    //@Autowired
    //标注在方法,spring容器创建当前对象,就会调用方法,完成赋值
    //方法使用的参数,自定义类型的值从ioc容器中获取
    public void setCar(Car car) {
        this.car = car;
    }
}


7.3@Profile环境搭建

profile:spring为我们提供的可以根据当前环境,动态,的激活和切换一系列组件的功能

首先导入maven依赖

<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>

编写配置文件dbconfig.properties

db.user = root
db.password = root
db.driverClass = com.mysql.jdbc.Driver

配置类代码

@Configuration
@PropertySource("classpath:/dbconfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")
    private String user;

    private StringValueResolver valueResolver;

    private String driverClass;

    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssmbuild");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("proDataSource")
    public DataSource dataSourcePro(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/wujie");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }
}

测试代码

public class TestProfile {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);

        String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name : namesForType) {
            System.out.println(name);
        }

        applicationContext.close();
    }

7.4@Profile根据环境注册bean

@Profile:指定组件在哪个环境情况下才能被注册到容器中,不指定,任何环境都能注册到这个组件

加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境

写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能生效

没有标注环境标识的bean,在任何时候都是加载的

方法一:使用命令行动态参数:这个方法略过

方法二:使用代码来实现

​ 1.创建一个applicationContext

​ 2.设置需要激活的环境,可以设置多个

​ 3.注册主配置类

​ 4.启动刷新容器

代码演示

@Configuration
@PropertySource("classpath:/dbconfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")
    private String user;

    private StringValueResolver valueResolver;

    private String driverClass;

    @Bean("testDataSource")
    @Profile("test")
    public DataSource dataSourceTest(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("devDataSource")
    @Profile("dev")
    public DataSource dataSourceDev(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssmbuild");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("proDataSource")
    @Profile("pro")
    public DataSource dataSourcePro(@Value("${db.password}") String password) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/wujie");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }
}

//测试代码

public class TestProfile {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new
                AnnotationConfigApplicationContext();

        //1.创建一个applicationContext
        //2.设置需要激活的环境
        applicationContext.getEnvironment().setActiveProfiles("test","dev");

        //3.注册主配置类
        applicationContext.register(MainConfigOfProfile.class);
        //4.启动刷新容器
        applicationContext.refresh();

        String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name : namesForType) {
            System.out.println(name);
        }

        applicationContext.close();
    }
}

8.ioc总结

1.组件添加的注解

2.组件赋值

3.组件注入

9.AOP面向切面编程(重中之重)

9.1AOP功能测试

AOP:动态代理

​ 指在程序运行期间动态的将某段代码切入到指定的方法指定位置进行运行的编程方式

1.导入依赖

2.定义一个业务逻辑类,在业务逻辑运行的时候将日志进行打印

3.定义一个日志切面类,切面类里的方法需要动态感知MathCalculator.div

​ 通知方法:

​ 前置通知:@Before 在目标方法运行之前

​ 后置通知:@After 在目标方法运行结束之后运行

​ 返回通知:@AfterReturning 在目标方法正常返回之后运行

​ 异常通知:@AfterThrowing 在目标方法出现异常以后运行

​ 环绕通知:动态代理,手动推动目标方法运行()

4.给切面类的目标方法标注何时运行(通知注解)

5.将切面类和业务逻辑类(目标方法所在类)都加入到容器中

6.必须告诉Spring,哪个类是切面类(给类上面加一个注解:@Aspect)

7.给配置类中加@EnableAspectJAutoProxy【开启注解的Aop模式】

总结:

三步:

​ 1.将业务逻辑组件和切面类都加入到容器中,告诉Spring哪个是切面类

​ 2.在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)

​ 3.开始基于注解的aop模式

代码示例

//配置类
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {

    //业务逻辑类加入到容器中
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    //切面类加入到容器中
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

public class MathCalculator {

    public int div(int i,int j){
        System.out.println("开始计算------");
        return i / j;
    }
}

@Aspect
public class LogAspects {

    //抽取公共的切入点表达式
    //1.本类引用
    //2.其他切面引用
    @Pointcut("execution(public int com.wxit.aop.MathCalculator.*(..))")
    public void pointCut(){};

    @Before("pointCut()")
    //JoinPoint一定要出现在参数的第一位
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("" + joinPoint.getSignature().getName() + "运行-----参数列表:{" + Arrays.asList(args) +"}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println("" + joinPoint.getSignature().getName() + "结束----");
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturn(Object result){
        System.out.println("除法正常返回---运行结果" + result);
    }

    @AfterThrowing("pointCut()")
    public void logException(){
        System.out.println("除法异常---异常信息---");
    }
}

//测试代码
public class TestAop {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);

        mathCalculator.div(1,1);

        applicationContext.close();
    }
}

9.2AOP原理

流程:

1.传入配置类,创建ioc容器

2.注册配置列,调用refresh()刷新容器

3.registerBeanPostProcessors(beanFactory),注册bean的后置处理器来方便拦截bean的创建

​ 1.先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor

​ 2.给容器中加别的BeanPostProcessor

​ 3.优先注册实现了PriorityOrder接口的BeanPostProcessor

​ 4.再给容器中注册实现了Orderd接口的BeanPostProcessor

​ 5.注册没有实现优先级接口的BeanPostProcessor

​ 6.注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中

9.3创建AOP代理

10.声明式事务

1.环境搭建

导入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

2.配置数据源,JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据库

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

让美好继续发生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值