Spring面试题

spring的优势?

1,spring通过DI,AOP来消除样板式代码来简化Java开发
2,spring框架之外还存在一个构建在核心框架之上的生态圈,它将spring扩展到不同领域,web服务、rest、nosql等
3,低侵入式设计,代码污染极低
4,spring的ioc容器降低了业务对象替换复杂性,由容器来创建、销毁,提高了组件之间的解耦
5,spring的面向切面aop支持将一些通用任务:安全、事务、日志等进行集中式处理,从而更好的复用
6,spring的orm和dao提供了与第三方持久层框架的良好整合,简化了底层数据库访问
7,spring的高度开放性,并不强制依赖于spring,开发者可以自由选用spring框架部分或全部

嵌入式服务器?

在springboot框架中,内嵌了Tomcat,在之前的开发中,每次写好代码之后必须要将项目部署到一个额外的web服务器中,只有这样才能运行,这样比较麻烦,而在springboot框架中内置了tomcat.jar,来通过main方法启动容器,达到一键开发部署的方式,不需要额外任何其他操作。

对Spring的理解?

spring是一个框架,一个容器,还是一个生态。

控制反转(IOC)

ioc是什么?

把对象的管理权限交给spring容器,应用程序如果需要使用某个对象,直接从ioc容器中获取就可以了。有了ioc之后,依赖的对象直接由ioc容器创建后注入到对象中,由主动创建变成被动接受。降低了对象与对象直接的耦合性,使得程序的体系结构更加灵活。

ioc的工作流程?

1,ioc容器的初始化阶段:根据程序里面定义的xml或者注解等bean的声明方式,通过解析和加载后生成BeanDefinition(保存着bean的定义信息),然后把BeanDefinition注册到ioc容器中,通过注解或者xml声明的bean都会得到一个BeanDefinition实体,这个实体会包含一些bean的定义和基础的一些属性,最后把BeanDefinition保存到一个map集合中,从而完成ioc的初始化。ioc容器的作用就是对这些注册的bean的定义信息进行处理和维护,是控制反转的核心。
2,bean的初始化和依赖注入:通过反射去针对没有设置lazy-init属性的单例bean,进行初始化,第二个就是完成bean的依赖注入。Spring中的Bean是在容器启动时即被实例化和初始化的。
3,bean的使用:通常我们会通过@Autowired注解,或者通过BeanFactory.getBean()从ioc容器中获取一个指定的bean实例。
另外非单例bean的一个实例化,每次请求该Bean时都创建一个新的实例。
针对设置了lazy-init=true的bean,第一次被请求时才进行实例化。这意味着它不会在容器启动时立即被实例化和注入到其他Bean中。

实例化是创建Bean对象的过程,即在内存中分配空间并初始化其实际的Java对象。
初始化是在Bean实例化后,Spring容器对Bean执行各种初始化操作的阶段,如调用生命周期回调方法、应用BeanPostProcessor等。
在Spring的生命周期中,实例化是必然发生的,而初始化是可选的,可以由开发者根据需求进行自定义配置。
Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。
1,先通过createBeanFactory创建出一个bean工厂(DefaultListableBeanFactoryBeanDefinition保存着bean的定义信息
BeanFactoryPostProcessor中将比如xml中数据源的${username}信息替换为真正的值

2,开始循环创建对象,因为容器中的bean默认都是单例的,所以优先通过getBean从容器中查找

3,如果找不到,再通过createBean方法,通过反射的方式创建对象,一般情况下使用的是无参的构造方法(getDeclaredConstructor)
//获取Class对象
Class clazz = Class.forName("包名+类名");
//获取构造器
Constructor constructor = clazz.getDeclaredConstructor();
//创建对象
Object obj = constructor.newInstance();

4,进行对象的属性填充populateBean
5,进行其他的初始化操作(InitializingBean
容器:

存放对象,使用map结构来存储,在spring中一般存在三级缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理。

依赖注入(DI)

Spring使用Java Bean对象的Set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的基本思想。

面向切面编程(AOP)

aop是ioc整体流程中的一个扩展点。
在面向切面编程中,我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事务管理,日志记录等公用操作处理的过程就是面向切面编程的思想。
存在很多与业务无关的组件,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,aop正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原有的业务逻辑基础之上做一些增强功能即可。
bean的创建过程中在BeanPostProcessor的后置处理方法中进行aop实现,aop本身就是一个扩展功能。

springAOP&AspectJ

springAop是属于运行时增强,而AspectJ是编译时增强。springAop基于代理,而AspectJ基于字节码操作。
springAop已经集成了AspectJ,AspectJ算得上Java生态系统中最完整的aop框架了,AspectJ相比于springAop功能更加强大,但是springAop相对来说更简单。

AOP实现步骤

AOP实现的三大步:以JavaConfig举例
当@EnableAspectJAutoProxy会通知@Import注册一个BeanPostProcessor处理AOP
1,解析切面:bean的后置处理器会解析切面,一个Aspect切面就会解析成一个通知Advice(需要增强的逻辑)、切点Pointcut(需要增强的方法)
2,创建动态代理:正常的bean初始化之后调用BeanPostProcessor拿到切点Pointcut,判断当前bean是否与切点表达式匹配,如果匹配,就会为bean创建动态代理(jdk动态代理、cglib动态代理)
3,调用:拿到动态代理对象,调用方法就会判断当前方法是否是增强的方法,就会通过调用链的方法依次去执行通知Advice(需要增强的逻辑)

SpringAOP 实现底层就是对动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编
写,并通过配置的方式完成指定目标的方法增强。
Target(目标对象):代理的目标对象
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方
法类型的连接点
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
Aspect(切面):是切入点和通知(引介)的结合
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
通知(Advice)
“切面”对于某个“连接点”所产生的动作。比如,切面类对项目中service包下的所有类的方法进行日志记录的动作就是一个Advice。一个切面可以包含多个Advice:

前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知。无论增强方式执行是否有异常都会执行

通知执行顺序:
前置通知 → 环绕通知连接点之前 → 连接点执行 → 环绕通知连接点之后 → 返回通知 → (如果发生异常)异常通知 → 后通知。

Spring AOP - 自定义注解实现共性需求

spring中aop的底层是怎么实现的

Spring中AOP底层的实现是基于动态代理进行实现的。
常见的动态代理技术有两种:JDK的动态代理和CGLIB。
两者的区别如下所示:
1,JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2,Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法进行增强,但是因为采用的是继承,所以该类或方法最好不要声明为final,对于final类或方法,是无法继承的。

Spring如何选择是用JDK还是cglib?

1,目标类实现了至少一个接口,则Spring默认使用JDK的动态代理
2,如果目标类没有实现任何接口,或者明确要求使用cglib动态代理,Spring将使用cglib
3,可以强制使用cglib

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // 配置其他 Bean 等
}
JDK的动态代理

基于接口的动态代理技术

public interface TargeInterface {
    public void save();
}

public class Targe implements TargeInterface{
    public void save() {
        System.out.println("save...");
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        final Targe targe = new Targe();
        Enhance enhance = new Enhance();
        enhance.beforeEnhance();
        //返回值 就是动态生成的代理对象
        TargeInterface proxy = (TargeInterface)Proxy.newProxyInstance(
                targe.getClass().getClassLoader(),//目标对象类加载器
                targe.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    //调用代理对象的任何方法,实质执行的都是invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object invoke = method.invoke(targe, args);
                        return invoke;
                    }
                }
        );
        //调用代理对象的方法
        proxy.save();
        enhance.afterEnhance();
    }
}

执行结果
beforeEnhance
save...
afterEnhance
cglib 的动态代理

基于父类的动态代理技术

public class ProxyTest {

    public static void main(String[] args) {

        //目标对象
        final Target target = new Target();

        //增强对象
        final Advice advice = new Advice();

        //返回值 就是动态生成的代理对象  基于cglib
        //1、创建增强器
        Enhancer enhancer = new Enhancer();
        //2、设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                advice.before(); //执行前置
                Object invoke = method.invoke(target, args);//执行目标
                advice.afterReturning(); //执行后置
                return invoke;
            }
        });
        //4、创建代理对象
        Target proxy = (Target) enhancer.create();

        proxy.save();
    }
}

执行结果:
前置增强....
save running.....
后置增强....

spring bean的生命周期

Spring Bean的生命周期如下图所示:
在这里插入图片描述
--------------------------------1,创建bean--------------------------------
1,实例化Bean
createBeanInstance(),通过反射的方式进行对象的创建。此时的创建只是在堆空间中申请空间,属性都是默认值

public class Dog{
    public Dog() {
        System.out.println("1,实例化");
    }
 }

2,设置对象自定义属性(依赖注入)
实例化后的对象被封装在BeanWrapper(Wrapper包装)对象中,紧接着Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。

public class Dog{
    public void setName(String name) {
        System.out.println("2,依赖注入");
        this.name = name;
    }
 }

3,处理Aware相关接口,容器对象属性赋值
实现BeanNameAware,BeanFactoryAware,BeanClassLoaderAware接口,invokeAwareMethod()完成BeanName、BeanFactory、BeanClassLoader容器对象属性设置
如果对象中需要引用容器内部的对象,那么需要调用aware接口的子类方法来进行统一的设置,可以认为是依赖注入的拓展

public class Dog implements BeanNameAware,BeanFactoryAware {
	//实现 BeanNameAware 接口的 bean 可以感知到自己在容器中的名称
	@Override
    public void setBeanName(String s) {
        System.out.println("3,Aware接口执行了,BeanName "+s);
    }
    
    //实现 BeanFactoryAware 接口,bean 可以获取到当前所属的 BeanFactory 对象,从而可以访问容器的其他 bean 以及容器本身的功能
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("3,Aware接口执行了,beanFactory "+beanFactory);
    }
 }

4,bean的增强器(BeanPostProcessor)的前置处理方法
bean依赖注入之后,初始化之前进行前置的处理工作,对bean进行自定义处理的扩展点
比如ApplicationContextPostProcessor设置ApplicationContext、Environment、ResourceLoader等对象

public class DogProcessor implements BeanPostProcessor {
	//用于实现各种预处理、扩展等功能
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("dog".equals(beanName)){
            System.out.println("4,前置处理器BeanPostProcessor before方法执行");
        }
        return bean;
    }
}

5,检查是否实现InitializingBean接口,重写afterPropertiesSet方法
提供了一种与 Spring 容器无关的初始化方式,同时也能与其他初始化方式init-method或@PostConstruct兼容

public class Dog implements InitializingBean {
	@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5,InitializingBean 接口执行了,检查是否是InitializingBean的子类");
    }
 }

6,检查是否配置有自定义的init-method方法
如果当前Bean对象定义了初始化方法,那么在此处调用初始化方法。

public class Dog{
	@PostConstruct
    private void init(){
        System.out.println("6,初始化");
    }
 }

7,bean的增强器(BeanPostProcessor)的后置处理方法
对生成的Bean对象进行后置的处理工作。
在 bean 初始化完成后对 bean 进行自定义处理的扩展点
spring的aop就是在此处实现的

public class DogProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("dog".equals(beanName)){
            System.out.println("7,前置处理器BeanPostProcessor after方法执行 AOP操作");
        }
        return bean;
    }
}

-------------------------------2,使用bean--------------------------------
8,使用bean

public class DogUse {
    @Autowired
    private Dog dog;
	
    public void use() {
        dog.getName();
        System.out.println("8,使用bean");
    }
}

-------------------------------3,销毁bean--------------------------------
9,实现DisposableBean接口
为了方便对象的销毁,在此处调用销毁的回调接口,方便对象进行销毁操作。

public class Dog implements DisposableBean {
	@Override
    public void destroy() throws Exception {
        System.out.println("9,DisposableBean接口执行了,销毁前可做回收资源等操作");
    }
}

10,销毁bean

public class Dog{
    @PreDestroy
    private void destoryMethod(){
        System.out.println("10,销毁bean");
    }
 }   

在Spring中有几种配置Bean的方式?

Spring的配置Bean的方式

基于注解的配置(自动装配)基于Java的配置基于XML的配置
自动装配是Spring容器根据Bean之间的依赖关系,自动将需要的Bean注入到目标Bean中。这是一种非常简便和快捷的配置方式,不需要手动指定依赖关系。在Spring中,可以通过在类、方法、属性等上面使用注解的方式来声明依赖关系,例如使用@Autowired注解来自动装配Bean。基于Java的显式配置是通过Java代码来定义和配置Bean。这种方式需要使用Spring提供的Java配置类,例如@Configuration注解和@Bean注解。在Java配置类中,可以使用@Bean注解来声明一个Bean,并使用方法来定义Bean的创建过程。基于XML的显式配置是通过XML文件来定义和配置Bean。这种方式需要使用Spring提供的XML配置语法,例如在XML文件中使用元素来声明一个Bean,并使用元素来定义Bean的属性。

Spring中所使用的设计模式

1,工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
2,单例模式:Bean默认为单例模式
3,策略模式:XmlBeanDefinitionReader、PropertiesBeanDefinitionReader
4,代理模式:Spring的AOP功能用到了JDK的动态代理和cglib的动态代理
5,模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。RedisTemplate,RestTemplate(一个 HTTP 请求工具),JmsTemplate,JpaTemplate
6,适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,MethodBeforeAdviceAdapter
7,观察者模式:Spring事件驱动模型、listener
8,桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
9,装饰者模式:依赖注入时,实例化后的对象被封装在BeanWrapper对象中

Spring中所使用的设计模式

Spring框架中的单例bean是线程安全的吗?

Spring中的bean对象默认是单例的,Spring框架并没有对单例bean进行任何多线程的封装处理。
如果bean是有状态的,就需要开发者来保证线程安全,最浅显的解决办法就是将bean的作用域由“singleton”变更为“prototype”,这样每次请求bean对象就相当于是创建新的对象来保证线程安全。
有状态就是有数据存储的功能,无状态就是不会存储数据,我们的controller、service、dao本身并不是线程安全的,只是调用里面的方法,那么多线程调用一个实例方法,会在内存中复制遍历,这时自己线程的工作内存,才是最安全的。
因此在进行使用时,不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,也推荐使用ThreadLocal把变量变成线程私有,如果bean的实例变量或类变量需要多个线程之间共享,就只能使用synchronized、lock、cas等实现线程安全。

//简单的服务类 UserService,它包含一个计数器属性 counter 和一个方法 incrementCounter(),每次调用该方法都会将计数器加一
@Service
public class UserService {
    private int counter = 0;
    public void incrementCounter() {
        counter++;
    }
    public int getCounter() {
        return counter;
    }
}
//测试类 Test,并启动多个线程来并发调用 incrementCounter() 方法
public class Test{
	@Autowired
	private UserService userService;
	
    @PostConstruct
	public void sum() {
		List<Thread> list = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			Thread thread = new Thread(() -> {
				for (int j = 0; j < 1000; j++) {
					userService.incrementCounter();
				}
			});
			thread.start();
			list.add(thread);
		}
		// 等待所有线程执行完毕
		for (Thread thread : list) {
			//子线程加入主线程,主线程等子线程执行完毕再执行
			thread.join();
		}
		System.out.println("Counter value: " + userService.getCounter());//Counter value: 9958
	}
}

Spring中Bean的五大作用域

Singleton(单例):默认的作用域Prototype(原型)Request(请求)Session(会话)Global Session(全局会话)
在整个应用程序中只创建一个Bean实例每次对Bean的请求都会创建一个新的实例每个HTTP请求中创建一个新的Bean实例在每个用户会话(Session)中创建一个新的Bean实例在整个应用程序的全局会话中创建一个新的Bean实例。所有的session共享一个bean实例。

可以通过@Scope注解指定

@Scope(scopeName="prototype")
public interface UserDao{}

Singleton(单例):默认的作用域

在整个应用程序中只创建一个Bean实例。
所有对该Bean的请求都将返回同一个实例。
Bean是全局共享的,适用于无状态的Bean或者需要在多个组件之间共享数据的情况

@Component
public class CalculatorService {
    
    public int add(int a, int b) {
        return a + b;
    }
    public int subtract(int a, int b) {
        return a - b;
    }
    // 其他方法...
}

Prototype(原型)

每次对Bean的请求都会创建一个新的实例。
没有共享状态,适用于有状态的Bean或者需要频繁创建新实例的情况

@Component
public class ShoppingCart {
    private List<String> items = new ArrayList<>();
    public void addItem(String item) {
        items.add(item);
    }
    public void removeItem(String item) {
        items.remove(item);
    }
    public List<String> getItems() {
        return items;
    }
    // 其他方法...
}

Request(请求)

在每个HTTP请求中创建一个新的Bean实例。
每个请求的Bean实例对于该请求是唯一的。
仅在Web应用程序的上下文中有效,适用于处理HTTP请求的控制器或服务

Session(会话)

在每个用户会话(Session)中创建一个新的Bean实例。
对于同一用户的所有请求,都将使用相同的Bean实例。
仅在Web应用程序的上下文中有效,适用于保存用户特定的数据或状态。

Global Session(全局会话)

在整个应用程序的全局会话中创建一个新的Bean实例。所有的session共享一个bean实例。

spring bean循环依赖?

Spring解决循环依赖是通过Singleton(单例)三级缓存,对应的三级缓存如下所示:
1,当依赖注入通过属性注入、setter方法注入spring可以解决循环依赖
2,通过构造方法注入spring也不能解决循环依赖,因为解决循环依赖的思路是实例化与初始化分开执行,提前暴露不完整的bean对象
3,Prototype(原型)多例循环依赖spring也不能解决循环依赖,因为每次调用生成新的bean不走缓存

Singleton(单例)三级缓存

缓存map名称作用
一级缓存singletonObjects单例池; 缓存已经经历了完整声明周期, 已经初始化完成的bean对象
二级缓存earlySingletonObjects缓存早期的bean对象(生命周期还没有走完,未依赖注入的代理对象或普通对象)
三级缓存singletonFactories缓存的是ObjectFactory, 表示对象工厂,存放获取对象的lambda表达式
三级缓存解决循环依赖问题:
1,a对象b属性,b对象属性a来实例化+初始化
2,a对象实例化:a三级缓存lambda表达式
3,a对象b属性实例化:b三级缓存lambda表达式
4,b对象属性a依赖注入: 根据三级缓存lambda表达式,生成a二级缓存未依赖注入的bean,
	也就是提前暴露不完整的bean,如果有aop返回代理bean,如果没aop就返回普通bean
5,b对象和a属性都有了:b一级缓存完整的bean
6,a对象b属性都有了:a一级缓存完整的bean
7,一旦循环依赖被解除,Spring会清理二级缓存、三级缓存中的对象

@Service
public class A {
    @Autowired
    private B b;
}
@Service
public class B {
    @Autowired
    private A a;
}

两级缓存,能否解决循环依赖?

没有代理对象的时候,当不使用aop的时候,两个缓存map就可以解决循环依赖问题。
对象在什么时候被暴露出去或者被其他对象引用时没办法提前确定好的,所以只有在被调用的那一刻才可以进行原始对象还是代理对象的判断,使用lambda表达式类似于一种回调机制,不暴露的时候不需要调用执行,当需要被调用的时候,才真正执行lambda表达式,来判断返回的到底是代理对象还是原始对象。

BeanFactory与FactoryBean有什么区别?

相同点:BeanFactory与FactoryBean都是用来创建bean对象的

BeanFactory

使用BeanFactory创建对象的时候,必须严格遵循bean的生命周期流程,BeanFactory通常通过XML配置文件或Java注解进行配置,定义和管理Bean对象。

FactoryBean接口

如果想要简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactoryBean接口了。
1,实现FactoryBean接口

@Component
public class MyBeanFactory implements FactoryBean<MyBean> {
    @Override
    public MyBean getObject() throws Exception {
        // 在这里执行创建和配置 MyBean 对象的逻辑
        MyBean myBean = new MyBean();
        myBean.setName("Example");
        myBean.setValue(123);
        return myBean;
    }
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

2,@Autowired注解将MyBean注入到其他类

@Component
public class MyBeanConsumer {
    private MyBean myBean;
    @Autowired
    public MyBeanConsumer(MyBean myBean) {
        this.myBean = myBean;
    }
    // 使用 myBean 对象进行其他操作
}

BeanFactory和ApplicationContext有什么区别?

1,BeanFactory:BeanFactory在启动的时候不会去实例化Bean,当从容器中拿Bean的时候才会去实例化;
2,ApplicationContext:ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值