Spring原理篇笔记

bean的加载方式 

 

bean的加载方式(一)

 ●XML方式声明bean-applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--声明自己开发的bean-->
        <bean id="cat" class="com.chj.bean.Cat"/>
        <bean class="com.chj.bean.Dog"/>
       <!-- 声明第三方开发的bean-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>

bean的加载方式(二)

●XML+注解方式声明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"
       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">
      <!-- 指定价值bean的位置-->
        <context:component-scan base-package="com.chj.bean,com.chj.config"/>
</beans>

●使用@Component及其衍生注解@Controller、@Service、@Repository.定义bean

@Service
public class BookServiceImpl implements BookService {
}

●使用@Bean定义第三方bean,并将所在类定义为配置类或Bean

@Configuration //配置类的定义bean
public class DbConfig {
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds=new DruidDataSource();
        return ds;
    }
}

bean的加载方式(三)

●注解方式声明配置类

@Configuration
@ComponentScan(value = {"com.chj.bean","com.chj.config"})
public class SpringConfig3 {
}

     ■ @Configurationi配置项如果不用于被扫描可以省略

bean的加载方式一扩展1

●初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作

public class DogFactoryBean implements FactoryBean<Dog> {
    @Override
    public Dog getObject() throws Exception {
        return new Dog();
    }
    @Override
    public Class<?> getObjectType() {
        return Dog.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}
@Configuration
public class SpringConfig3 {
    @Bean
    public DogFactoryBean dog(){
        return new DogFactoryBean();
    }
}

bean的加载方式一扩展2

●加载配置类并加载配置文件(系统迁移)

@ImportResource("applicationContext.xml")
public class SpringConfig32 {
}

bean的加载方式一扩展3

● 使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的

@Configuration(proxyBeanMethods = true)
public class SpringConfig33 {
    @Bean
    public Cat cat(){
        return new Cat();
    }
}
public class App33 {
    public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig33.class);
        SpringConfig33 springConfig33 = context.getBean("springConfig33", SpringConfig33.class);
        System.out.println(springConfig33.cat());
        System.out.println(springConfig33.cat());
    }
}

● 输出

 当proxyBeanMethods = true时获取的是容器中同一个对象

 当proxyBeanMethods = false时调用一次创建一个对象

 bean的加载方式(四)

● 使用@Import注解导入要注入的bean对应的字节码

@Import(Dog.class)
public class SpringConfig4 {

}

● 被导入的bean无需使用注解声明为bean

public class Dog {
    public void say(){
        System.out.println("汪汪汪。。。。。。");
    }
}

此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用

public class App4 {
    public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig4.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        Dog dog = context.getBean(Dog.class);
        dog.say();
    }
}

bean的加载方式—扩展5

●使用上下文对象在容器初始化完毕后注入bean

public class App5 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig4.class);
        //上下文容器对象初始化完毕后,手动加载bean
        context.registerBean("tom", Cat.class);
        context.register(Mouse.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

bean的加载方式(六)

●导入实现了ImportSelector:接口的类,实现对导入源的编程式处理

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        boolean flag = annotationMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag){
            return new String[]{"com.chj.bean.Dog"};
        }
        return new String[]{"com.chj.bean.Cat"};
    }
}

bean的加载方式(七)

●导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果

public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //
        BeanDefinition beanDefinition= BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        registry.registerBeanDefinition("旺财",beanDefinition);
    }
}

bean的加载方式(八)

●导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //使用元数据去判定
        BeanDefinition beanDefinition= BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService",beanDefinition);
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

bean的加载控制

●bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标。

        ●AnnotationConfigApplicationContext调用register方法


●@Import导入ImportSelector接口

        ●根据任意条件确认是否加载bean

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        try {
            Class<?> aClass = Class.forName("com.chj.bean.Mouse");
            if (aClass != null) {
                return new String[]{"com.chj.bean.Cat"};
            }
        } catch (ClassNotFoundException e) {
            //e.printStackTrace();
            return new String[0];
        }
        return null;
    }
}

       ●使用注解 @ConditionalOnClass和@ConditionalOnMissingClass添加条件确认是否加载bean

public class SpringConfig {
    /*@Bean
    //匹配指定类
    @ConditionalOnClass(name="com.chj.bean.Dog")
    //匹配指定环境:判断是否为web环境,true加载
    @ConditionalOnWebApplication
    public Cat tom(){
        return new Cat();
    }*/
    @Bean
    @ConditionalOnBean(name = "jerry")
    //未匹配指定类
    @ConditionalOnMissingClass("com.chj.bean.Dog")
    public Dog kate(){
        return new Dog();
    }
    @Bean
    //匹配指定环境:是否为mysql环境,否,不加载
    @ConditionalOnClass(name="com.mysql.jdbc.Driver")
    public DruidDataSource dataSource(){
        return new DruidDataSource();
    }
}


   ●@Import导入ImportBeanDefinitionRegistrar接口
   ●@Import导入BeanDefinitionRegistryPostProcessor接口

bean依赖的属性配置 

将业务功能bean运行需要的资源抽取成独立的属性类(****Properties),设置读取配置文件信息

@Component
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
    private Cat cat;
    private Mouse mouse;
}
application.yml
cartoon:
  cat:
    age: 5
    name: "judy"
  mouse:
    age: 4
    name: "kate"
@Component
@EnableConfigurationProperties(CartoonProperties.class)
public class Cartoon {
    private Cat cat;
    private Mouse mouse;

    private CartoonProperties cartoonProperties;
    public Cartoon(CartoonProperties cartoonProperties){
        this.cartoonProperties=cartoonProperties;
        cat=new Cat();
        mouse=new Mouse();
        cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ?  cartoonProperties.getCat().getAge() :3);
        cat.setName(cartoonProperties.getCat()!=null&&StringUtils.hasText(cartoonProperties.getCat().getName()) ?  cartoonProperties.getCat().getName() : "tom");
        mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ?  cartoonProperties.getMouse().getAge() :4);
        mouse.setName(cartoonProperties.getMouse()!=null&&StringUtils.hasText(cartoonProperties.getMouse().getName()) ?  cartoonProperties.getMouse().getName() :"jerry");
    }
    public void play(){
        System.out.println(cat.getAge()+"岁的"+cat.getName()+"抓住了"+mouse.getAge()+"岁的"+ mouse.getName());
    }
}

1.业务bean的属性可以为其设定默认值
2,当需要设置时通过配置文件传递属性
3.业务bean应尽量避免设置强制加载,而是根据需要导入后加载,降低spring容器管理bean的强度

自动配置原理 

       1.收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表一>(技术集A)
        2.收集常用技术(技术集A)的使用参数,整理开发过程中每个技术的常用设置列表一>(设置集B)
        3.初始化SpringBoota基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
        4.将技术集A包含的所有技术都定义出来,在Spring/SpringBoot.启动时默认全部加载
        5.将技术集中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对)
        6.将设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量                                      7.开放设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置

public final class SpringFactoriesLoader{
    public static final String FACTORIES_RESOURCE_LOCATION "META-INF/spring.factories";
}

总结

1.先开发若干种技术的标准实现
2.SpringBoot启动时加载所有的技术实现对应的自动配置类
3.检测每个配置类的加载条件是否满足并进行对应的初始化
4.切记是先加载所有的外部资源,然后根据外部资源进行条件比对

变更自动配置

 ●自定义自动配置(META-INF/spring.factories)

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 com.chj.bean.Cartoon


控制SpringBoot内置自动配置类加载 

        1.yml中配置

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
      - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

     2.使用@EnableAutoConfiguration(excludeName = "")注解

@Component
@EnableAutoConfiguration(excludeName = {"org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration",
        "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration"})
@EnableConfigurationProperties(CartoonProperties.class)
public class Cartoon implements ApplicationContextAware {
    private Cat cat;
    private Mouse mouse;
    @Autowired
    private CartoonProperties cartoonProperties;
    public Cartoon(CartoonProperties cartoonProperties){
        this.cartoonProperties=cartoonProperties;
        cat=new Cat();
        mouse=new Mouse();
        cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ?  cartoonProperties.getCat().getAge() :3);
        cat.setName(cartoonProperties.getCat()!=null&&StringUtils.hasText(cartoonProperties.getCat().getName()) ?  cartoonProperties.getCat().getName() : "tom");
        mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ?  cartoonProperties.getMouse().getAge() :4);
        mouse.setName(cartoonProperties.getMouse()!=null&&StringUtils.hasText(cartoonProperties.getMouse().getName()) ?  cartoonProperties.getMouse().getName() :"jerry");
    }
    public void play(){
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        System.out.println(cat.getAge()+"岁的"+cat.getName()+"抓住了"+mouse.getAge()+"岁的"+ mouse.getName());
    }
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
}

 ●变更自动配置:去除tomcat自动配置(条件激活),添加jetty自动配置(条件激活)

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--web起步依赖环境中,排除Tomcat起步依赖,匹配自动配置条件-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <version>2.7.3</version>
        </dependency>

总结 

1,通过配置文件exclude属性排除自动配置
2,通过注解@EnableAutoConfiguration属性排除自动配置项
3,启用自动配置只需要满足自动配置条件即可
4,可以根据需求开发自定义自动配置项

自定义starter

基础版

1.业务功能

public class IpCountService {
    private Map<String,Integer> ipCountMap=new HashMap<>();
    @Autowired
    private HttpServletRequest request;
    public void count(){
        String ip=request.getRemoteAddr();
        System.out.println("--------------------------"+ip);
        Integer count = ipCountMap.get(ip);
        if (count==null){
            ipCountMap.put(ip,1);
        }else {
            ipCountMap.put(ip,ipCountMap.get(ip)+1);
        }
    }
}

2.自动配置类

public class IpAutoConfiguration {
    @Bean
    public IpCountService service(){
        return new IpCountService();
    }
}

3.自动配置文件spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 cn.chj.ipstarter.autoconfig.IpAutoConfiguration

 4.安装配置的包

 5.在运行的项目的pom文件中导入

 6.调用方法

 @Autowired
    private IpCountService ipCountService;
    @GetMapping("{currentPage}/{pageSize}")
    public Result getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){
        ipCountService.count();
        String key="books/"+currentPage+"/"+pageSize;
        IPage<Book> books = (IPage<Book>) redisTemplate.opsForValue().get(key);
        if (books!=null){
            return new Result(true,books);
        }
        IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
        if(currentPage>page.getPages()){
            page=bookService.getPage((int)page.getPages(), pageSize,book);
        }
        redisTemplate.opsForValue().set(key,page);
        return  new Result(true,page);
    }

升级版

●定义属性类,加载对应属性

@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    /**
     * 日志显示周期
     */
    private Long cycle=5L;
    /**
     *  是否周期内重置数据
     */
    private Boolean cycleReset=false;
     /**
      *  日志输出模式 detail:详细模式  simple:简单模式
      */
    private  String model="detail";
    public enum LogModel{
        DETAIL("detail"),
        SIMPLE("simple");
        private String value;
        LogModel(String value){
            this.value=value;
        }
        public String getValue(){
            return value;
        }
    }

    public Long getCycle() {
        return cycle;
    }

    public void setCycle(Long cycle) {
        this.cycle = cycle;
    }

    public Boolean getCycleReset() {
        return cycleReset;
    }

    public void setCycleReset(Boolean cycleReset) {
        this.cycleReset = cycleReset;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }
}

●设置加载Properties类为bean

@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import({IpProperties.class,SpringMvcConfig.class})
public class IpAutoConfiguration {
    @Bean
    public IpCountService service(){
        return new IpCountService();
    }
}

●根据配置切换设置

public class IpCountService {
    private Map<String,Integer> ipCountMap=new HashMap<>();
    @Autowired
    private HttpServletRequest request;
    public void count(){
        String ip=request.getRemoteAddr();
        Integer count = ipCountMap.get(ip);
        if (count==null){
            ipCountMap.put(ip,1);
        }else {
            ipCountMap.put(ip,count+1);
        }
    }
    @Autowired
    private IpProperties ipProperties;
    @Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
    public void print(){
        if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){
            System.out.println("         ip访问控制");
            System.out.println("+----ip-address-----+--num--+");
            for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(String.format("|%18s  |%5d  |",key,value));
            }
            System.out.println("+                   +       +");
        }else if (ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
            System.out.println("     ip访问控制");
            System.out.println("+----ip-address-----+");
            for (String key : ipCountMap.keySet()) {
                System.out.println(String.format("|%18s  |",key));
            }
            System.out.println("+--------------------+");
        }
        if(ipProperties.getCycleReset()){
            ipCountMap.clear();
        }
    }
}

●导入yml提示

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

●添加拦截器 

public class IpCountInterceptor implements HandlerInterceptor {
    @Autowired
    private IpCountService ipCountService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ipCountService.count();
        return true;
    }
}

●添加拦截器配置

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor()).addPathPatterns("/**");
    }
    @Bean
    public IpCountInterceptor interceptor(){
        return new IpCountInterceptor();
    }
}

●添加yml提示

  1.导入坐标

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

  2.自定义提示

 springboot启动流程

1.初始化各种属性,加载成对象
        ●读取环境属性(Environment)
        ●系统配置(spring.factories)
        ●参数(Arguments、application.properties)
2.创建Spring容器对象ApplicationContext,加载各种配置
3.在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
4.容器初始化过程中追加各种功能,例如统计时间、输出日志等

总结 1 初始化数据

        2  创建容器

StartupApplication-> SpringApplication.run(StartupApplication.class, args)
   SpringApplication-> return run(new Class[]{primarySource}, args);
      SpringApplication-> return (new SpringApplication(primarySources)).run(args);
        #加载各种配置信息,初始化各种配置对象
        SpringApplication(primarySources))
            SpringApplication-> this((ResourceLoader)null, primarySources);
                SpringApplication-> public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                                        #初始化资源加载器
                                        this.resourceLoader = resourceLoader;
                                        #初始化配置类的类名信息(格式转换)
                                        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
                                        #确认当前容器加载的类型
                                        this.webApplicationType = WebApplicationType.deduceFromClasspath();
                                        #获取系统配置引导信息
                                        this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
                                        #获取ApplicationContextInitializer.class对应的实例
                                        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
                                        #初始化监听器,对初始化过程及运行过程进行干预
                                        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
                                        #初始化了引导类类名信息,备用
                                        this.mainApplicationClass = this.deduceMainApplicationClass();
                                        }
        #初始化容器,得到applicationContext对象
        (new SpringApplication(primarySources)).run(args)
            #初始化一个计时器
            StopWatch stopWatch = new StopWatch();
            #计时开始
            stopWatch.start();
            #系统引导信息对应的上下文对象
            DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
            ConfigurableApplicationContext context = null;
            #模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标....)
                java.awt.headless=true
            this.configureHeadlessProperty();
            #获取所有的监听器
            SpringApplicationRunListeners listeners = this.getRunListeners(args);
            #监听器执行了对应的操作
            listeners.starting(bootstrapContext, this.mainApplicationClass);

            try {
                #获取参数
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                #将前期读取的数据加载成一个环境对象,用来描述信息
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                #做了一个配置,备用
                this.configureIgnoreBeanInfo(environment);
                #初始化log
                Banner printedBanner = this.printBanner(environment);
                #创建容器对象,根据前期配置的容器类型进行判断并创建
                context = this.createApplicationContext();
                #设置启动模式
                context.setApplicationStartup(this.applicationStartup);
                #对容器进行设置参数来源于前期的设定
                this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                #刷新容器环境
                this.refreshContext(context);
                #刷新完毕做后处理
                this.afterRefresh(context, applicationArguments);
                #计时结束
                stopWatch.stop();
                #打印计时日志
                if (this.logStartupInfo) {
                    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
                }
                #监听器执行了对应的操作
                listeners.started(context);
                this.callRunners(context, applicationArguments);
            } catch (Throwable var10) {
                this.handleRunFailure(context, var10, listeners);
                throw new IllegalStateException(var10);
            }

            try {
                #监听器执行了对应的操作
                listeners.running(context);
                return context;
            } catch (Throwable var9) {
                this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
                throw new IllegalStateException(var9);
            }

监听器类型

1.在应用运行但未进行任何处理时,将发送ApplicationStartingEvent。
2.当Environment被使用,且上下文创建之前,将发送ApplicationEnvironmentPreparedEvent。
3.在开始刷新之前,bean定义被加载之后发送ApplicationPreparedEvent。
4.在上下文刷新之后且所有的应用和命令行运行器被调用之前发送ApplicationStartedEvent。
5.在应用程序和命令行运行器被调用之后,将发出ApplicationReadyEvent,用于通知应用已经准备处理请求。
6.启动时发生异常,将发送ApplicationFailedEvent。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值